Python编写端口扫描器:从零开始

Python编写端口扫描器:从零开始

端口扫描作为网络安全中最基础且关键的技术之一,广泛应用于安全评估、漏洞挖掘和网络态势感知。本文将从理论到实战,逐步深入讲解如何使用Python语言从零实现一个高效灵活的端口扫描器。文中内容面向具备网络基础和Python编程能力的技术人员,力求在专业和实用之间达到平衡,帮助读者掌握端口扫描的核心原理与实战技巧。


一、概述

端口扫描器主要目的是探测目标主机上哪些TCP或UDP端口处于开放状态,从而推断服务情况和可能的漏洞入口。市面上已有诸如Nmap、Masscan等高性能扫描工具,但自研端口扫描器能够:

  • 深入理解端口扫描背后逻辑与网络机制
  • 灵活定制扫描策略,满足特殊需求
  • 在红蓝对抗、安全渗透测试中具备更强定制化拓展能力

Python因其丰富的网络库、简洁语法及多线程、多协程支持,成为实现此类工具的理想语言。

本文将重点讲解基于Python标准库socket的TCP端口扫描技术,涵盖扫描原理、扫描方法、并发优化、扫描策略设计,以及核心代码示例。


二、原理分析

在深入编码前,有必要理清端口扫描的网络层基本原理及相关知识:

1. TCP端口扫描原理

TCP端口的开放与否通过三次握手过程判断:

  • 连接建立成功(端口开放)
    欲连接的目标端口若有服务监听,会响应TCP SYN-ACK包。扫描器发送TCP SYN包后收到SYN-ACK,即判定端口开放。

  • 连接被拒绝(端口关闭)
    当目标端口无服务时,TCP会返回RST包表示拒绝,扫描器接收RST即判定端口关闭。

  • 无响应(端口过滤/丢包)
    部分防火墙可能丢弃请求,导致无响应,此时端口状态判定为过滤或超时。

由于三次握手中SYN-ACK响应确认了端口状态,因此基于socket的TCP连接尝试,是最直接的端口扫描方法。

2. 扫描方式对比

  • 全连接扫描(Connect Scan)
    使用操作系统的connect()函数尝试三次握手完成。优点是稳定、易用,缺点是连接完成后还需立即关闭连接,效率较低且容易被监测。

  • 半开扫描(SYN Scan)
    发送SYN包,若回应SYN-ACK则端口开放,然后发送RST包断开连接,避免完全握手。效率更高且隐蔽性较强,但实现复杂,需要原始套接字权限。

本文重点实现简洁高效的全连接扫描,兼顾实用性与广泛适用性(无需管理员权限)。

3. 并发扫描与性能

单线程扫描受网络延迟、系统调用阻塞限制,速度较慢。多线程、多进程、甚至异步协程可极大提升扫描效率,核心思路是并行发起多个连接请求,减少等待时间。

优化点包括:

  • 合理设置超时时间,避免长时间阻塞
  • 控制线程池大小,防止系统资源耗尽
  • 结果异步回写或缓存,提升吞吐率

三、实战步骤

构建一个简单实用的Python端口扫描器,一般包含以下步骤:

1. 设计需求

  • 支持目标主机IP或域名输入
  • 支持指定扫描端口范围(如1-1024)
  • 使用TCP全连接扫描检测端口状态
  • 支持多线程并发扫描加速
  • 扫描结果包含端口号及状态(开放/关闭/过滤)
  • 提供基本异常捕获和日志输出

2. 环境准备

Python3环境,使用标准库:

  • socket:网络连接操作
  • threadingconcurrent.futures:多线程管理
  • queue(可选):任务调度
  • argparse(可选):参数解析

无需安装额外依赖。

3. 核心代码示例

以下是一个基于线程池的TCP全连接端口扫描器示例,代码简洁清晰,适合作为实战参考。

import socket
import concurrent.futures

def tcp_scan(target: str, port: int, timeout=1.0) -> (int, bool):
    """
    扫描目标主机指定端口,判断端口是否开放。
    
    :param target: 目标主机IP或域名
    :param port: 端口号
    :param timeout: 连接超时时间(秒)
    :return: (端口号, 是否开放)
    """
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.settimeout(timeout)
            result = sock.connect_ex((target, port))
            if result == 0:
                return port, True  # 端口开放
            else:
                return port, False # 端口关闭或过滤
    except socket.gaierror:
        print(f"[错误] 无法解析域名: {target}")
        return port, False
    except socket.error as e:
        print(f"[错误] 连接异常: {e}")
        return port, False

def scan_ports(target: str, start_port: int, end_port: int, max_threads=100):
    """
    多线程扫描指定端口范围。
    
    :param target: 目标IP或域名
    :param start_port: 起始端口
    :param end_port: 结束端口
    :param max_threads: 最大并发线程数
    :return: None
    """
    print(f"扫描目标:{target},端口范围:{start_port}-{end_port}")
    open_ports = []

    with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
        futures = []
        for port in range(start_port, end_port + 1):
            futures.append(executor.submit(tcp_scan, target, port))

        for future in concurrent.futures.as_completed(futures):
            port, is_open = future.result()
            if is_open:
                print(f"端口 {port} - 开放")
                open_ports.append(port)

    print(f"扫描完成,共发现开放端口: {len(open_ports)}个")
    if open_ports:
        print("开放端口列表:", open_ports)

if __name__ == "__main__":
    target_host = input("请输入目标IP或域名: ").strip()
    port_start = int(input("起始端口: "))
    port_end = int(input("结束端口: "))

    scan_ports(target_host, port_start, port_end)

4. 代码说明

  • tcp_scan函数使用socket.connect_ex尝试连接,返回0表示连接成功,端口开放。
  • 使用ThreadPoolExecutor最大化利用多核、多线程,提升扫描效率。
  • 设置合理的网络超时(1秒),避免因无响应端口造成阻塞。
  • 代码结构清晰,易于扩展(如增加结果输出格式、日志记录、扫描策略等)。

四、防御建议

针对端口扫描攻击,网络防御应采取多层手段降低企业资产被扫描的风险:

1. 网络层防御

  • 部署防火墙策略
    严格限制非必要端口入站访问。采用白名单模式托管服务端口。防火墙应支持状态检测(Stateful Inspection)和异常流量过滤。

  • 速率限制
    对单IP同一时间内多个连接尝试进行限制,防止暴力扫描。结合IDS/IPS进行异常流量报警。

  • 端口隐蔽性
    移动默认服务端口(如SSH从22换成非标准端口),提升攻击成本。

2. 主机层防御

  • 关闭无用端口和服务
    及时清理系统中不必要的监听服务,减少攻击面。

  • 防范信息泄露
    关闭自动响应(如TCP RST包)的详细信息,减少扫描者识别端口状态的精度。

3. 应用层防御

  • 入侵检测系统(IDS)
    部署专用IDS(如Snort、Suricata)监测异常扫描行为,及时报警。

  • 日志审计
    加强对连接日志的审计,分析扫描相关日志,辅助溯源。

  • 动态端口和跳板技术
    结合代理和动态端口