socket 库建立 TCP 连接并抓取服务 Banner,用 ThreadPoolExecutor 编写多线程端口扫描器,用 argparse 为工具添加命令行参数——这是你在模块 1 学习 nmap 之前,理解端口扫描底层原理的最佳实践。回顾模块 0-2,你用 Wireshark 抓包分析了 TCP 三次握手。但安全工程师不能只当旁观者——你需要用代码主动探测网络、验证漏洞、构建自动化工具。Python 是安全领域最常用的编程语言,原因有三:标准库自带 socket 网络库和 threading 并发库,无需安装任何依赖就能编写网络工具;语法简洁,10 行代码就能完成 C 语言 50 行的工作;生态丰富,Scapy(数据包构造)、Requests(HTTP 请求)、Paramiko(SSH)等库覆盖了安全测试的各个环节。
Python 当前稳定版本为 3.14(2025 年 10 月发布,截至 2026 年 8 月最新维护版本 3.14.7)。Kali Linux 默认预装 Python 3,打开终端输入 python3 --version 即可确认。本篇使用的 socket、threading、argparse、json 全部是标准库,在任何安装了 Python 3 的机器上都能直接运行。
| 库/模块 | 用途 | 标准库 |
|---|---|---|
socket |
TCP/UDP 网络通信 | 是 |
threading |
多线程并发 | 是 |
argparse |
命令行参数解析 | 是 |
scapy |
数据包构造与嗅探 | 否(pip install scapy) |
requests |
HTTP 请求 | 否(pip install requests) |
本篇从标准库 socket 入手,这是所有 Python 网络编程的基石。掌握了 socket,你就理解了 nmap、Masscan 等专业扫描工具的底层逻辑——它们只是把同样的原理做得更快、更隐蔽。
模块 0-2 你学了 TCP 三次握手:客户端发 SYN,服务端回 SYN-ACK,客户端再发 ACK,连接建立。Python 的 socket 库把这三步封装成一个 connect() 调用。端口扫描的本质就是向目标端口发起 TCP 连接:连接成功说明端口开放,连接被拒绝说明端口关闭。
先写一段最简单的代码:检测本地 80 端口是否开放。你可以把 127.0.0.1 换成你自己搭建的靶场 IP。
代码中有两个关键细节。第一,connect_ex() 是 connect() 的"安静版"——连接失败时返回错误码而非抛出异常,方便批量检测。返回 0 表示连接成功(端口开放),非 0 表示失败(端口关闭或被过滤)。第二,settimeout(3) 设置 3 秒超时,防止连接目标无响应时程序永久卡死。
端口开放只是第一步。安全工程师更关心端口上跑的是什么服务。很多服务在 TCP 连接建立后会主动发送欢迎信息(Banner),读取这段信息就能识别服务类型和版本——这个过程叫 Banner 抓取。
连接本地 22 端口(SSH),你会看到类似 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.5 的输出。这个 Banner 暴露了 SSH 版本号和操作系统信息——对防御方来说是信息泄露,对攻击方来说是漏洞利用的切入点。后续模块 1 学习 nmap 的 -sV 参数时,你会发现它底层做的就是这件事,只不过更全面、更智能。
sshd_config 中的 Banner none)。此时需要主动发送探测数据(如 HTTP 请求)再读取响应。nmap 的服务指纹库(nmap-service-probes)收录了数百种探测规则,这就是专业工具比手写脚本强大的地方。你已经能检测单个端口了。安全评估通常需要扫描几十甚至上百个端口,把单端口检测放进循环就得到了端口扫描器。下面的代码扫描本机的 6 个常见端口:
运行后输出类似:
代码逻辑清晰,但有个致命问题:速度太慢。每个端口最多等待 1 秒超时,扫描 6 个端口最多需要 6 秒。如果扫描 1-1024 号端口,最坏情况需要 1024 秒(约 17 分钟)。原因在于单线程串行执行——前一个端口扫描完才开始下一个。这就像逐个敲门,每扇门没人应答要等满 1 秒才去敲下一扇。
| 端口 | 服务 | 安全关注点 |
|---|---|---|
| 22 | SSH | 弱口令、旧版本漏洞 |
| 80 / 443 | HTTP / HTTPS | Web 漏洞(模块 2 重点) |
| 3306 | MySQL | 未授权访问、弱口令 |
| 3389 | RDP | BlueKeep 漏洞、弱口令 |
| 8080 | HTTP Proxy / Alt | 管理后台暴露 |
OSError: [Errno 24] Too many open filessock.close(),或使用 with 上下文管理器自动释放解决单线程慢的办法是并发:同时"敲门"100 扇。Python 有两种多线程方案。threading.Thread 需要手动创建线程、管理生命周期、处理共享变量的线程安全,代码复杂。concurrent.futures.ThreadPoolExecutor 是 Python 3.2+ 引入的高层封装,自动管理线程池和结果收集,是当前推荐的做法。
| 特性 | threading.Thread | ThreadPoolExecutor |
|---|---|---|
| 线程池管理 | 手动创建、启动、join | 自动管理,with 语句自动清理 |
| 结果获取 | 共享 list + Lock | Future.result() 直接返回 |
| 线程数量控制 | 需自己限制 | max_workers 参数直接设定 |
| 推荐度 | 学习理解用 | 生产环境首选 |
下面用 ThreadPoolExecutor 重写扫描器,同时扫描 1-1024 号端口:
关键变化:max_workers=100 表示线程池最多同时运行 100 个线程。executor.submit() 把扫描任务提交到线程池,返回 Future 对象。as_completed() 按完成顺序(而非提交顺序)遍历结果,哪个端口先扫完就先输出。实测扫描 1024 个端口约需 1-2 秒,比单线程快了近 500 倍。
max_workers 并非越大越好。线程过多会导致系统上下文切换开销增大、socket 文件描述符耗尽。经验值:局域网扫描设 100-200,公网扫描设 50-100(公网延迟高,过多并发反而触发目标防火墙限速)。nmap 默认使用 SYN 半开扫描且能动态调节速率,这是它比 Python 脚本更高效的原因之一。前面的代码每次运行都要修改源文件来更改目标 IP 和端口范围。真正的安全工具应该通过命令行参数接受输入——就像 nmap -p 1-1000 192.168.1.1 那样。Python 标准库的 argparse 模块就是为此而生。下面把前面学到的所有知识组装成一个完整的端口扫描器:
保存为 scanner.py 后,使用方式如下:
运行后输出示例:
代码逐段解读:SERVICES 字典映射常见端口号到服务名,扫描到开放端口时自动标注服务类型。argparse 定义了四个参数:target(必填,目标 IP)、-p(端口范围,默认 1-1024)、-t(线程数,默认 100)、-o(JSON 输出文件)。args.ports.split("-") 把 "1-1024" 拆成起止端口。-o 参数把结果存为 JSON,方便后续工具消费——在真实渗透测试中,扫描结果通常需要导入报告或传递给下一步工具。
ulimit -n 65535 提升限制,或将线程数控制在 200 以内端口扫描是渗透测试的第一步,也是法律风险最高的一步。connect_ex() 发起的是完整的 TCP 三次握手,目标服务器的日志会记录你的 IP 地址和扫描行为。未经授权扫描他人网络违反《网络安全法》第 27 条和《刑法》第 285 条。合法的练习方式有三种:扫描 127.0.0.1(本机)、扫描自己搭建的虚拟机靶场(如 Metasploitable)、使用 scanme.nmap.org(Nmap 官方提供的合法扫描目标)。
python3 -m http.server 8080),然后用本篇的 scanner.py 扫描 127.0.0.1 的 8080 端口,验证扫描器能正确发现该端口开放。b"GET / HTTP/1.0\r\n\r\n",对 SSH 端口直接 recv()),将 Banner 信息一并输出。提示:在 scan_port 函数中,连接成功后追加 recv 逻辑。sr1(IP(dst=target)/TCP(dport=port, flags="S")) 用法,对比两种扫描方式的隐蔽性差异。注意:SYN 扫描需要 root 权限。