网络扫描基础:TCP/UDP端口扫描、SYN扫描、FIN扫描等技术原理

做 DSPM 扫描分类实战,网络扫描是绕不开的一块。说白了,扫描就是搞清楚“目标机器上到底开了哪些口子”。我刚开始搞安全那会儿,以为扫描就是简单发个包等回包,后来发现门道多得很。今天咱们就把 TCP 和 UDP 那几种扫描方式掰开揉碎了讲清楚。

端口扫描的本质

端口就是服务的入口。你想知道一台服务器有没有开 Web 服务,就看 80 或 443 端口开着没。操作系统对端口的响应行为能给咱们透露出大量信息。

关键点: 扫描不是瞎发包,而是利用 TCP/IP 协议栈的行为差异来推断端口状态。目标端口无非三种状态:开放、关闭、过滤(被防火墙屏蔽了)。

核心逻辑: 不同扫描方式就是靠不同标志位组合,让目标根据 RFC 793 标准做出特定响应,然后你根据响应判断状态。

TCP 扫描家族

TCP 扫描是最常用的。因为 TCP 是面向连接的,三次握手给了我们大量操作空间。下面几种我挨个讲,顺便说说项目里踩过的坑。

完整连接扫描(TCP Connect Scan)

最朴素的方法:和端口完成完整的三次握手。如果连上了,说明端口开放;如果收到 RST,说明端口关闭。

  • 优点: 简单可靠,操作系统帮你完成握手,不需要自己构造原始包。
  • 缺点: 太容易被发现。我曾在客户内网测试,刚跑完一次 connect 扫描,SOC 那边报警就响了——这种行为太明目张胆了。

SYN 扫描(半开扫描)

这是我个人最常用的方式。发一个 SYN 包,如果收到 SYN+ACK,说明端口开放——这时候你直接发 RST 断开,不完成第三次握手,所以叫“半开”。如果收到 RST,说明关闭;没响应或者收到 ICMP 不可达,说明被过滤了。

实战技巧: 我记得有一次渗透测试,目标内网有 IDS 监控。用 connect scan 秒挂,换成 SYN 扫描并把发包间隔调到 500ms,全程没有被触发告警。因为 IDS 对不完整的 TCP 连接没那么敏感。

SYN 扫描需要构造原始 IP 包,一般用 scapy 或 raw socket。看个简单例子:

from scapy.all import *
def syn_scan(ip, port):
    pkt = IP(dst=ip)/TCP(dport=port, flags="S")
    resp = sr1(pkt, timeout=2, verbose=0)
    if resp and resp.haslayer(TCP):
        if resp[TCP].flags == 0x12:  # SYN+ACK
            print(f"Port {port}: OPEN (SYN+ACK)")
            send(IP(dst=ip)/TCP(dport=port, flags="R"), verbose=0) # 礼貌断开
        elif resp[TCP].flags == 0x14: # RST+ACK
            print(f"Port {port}: CLOSED")
    else:
        print(f"Port {port}: FILTERED or no response")

FIN 扫描

这招有点“反直觉”。你给目标发一个 FIN 包(正常是用于关闭连接的),根据 RFC,如果端口关闭,目标应该回一个 RST;如果端口开放,目标直接忽略这个包,什么都不回。所以只要没收到 RST,就推断端口开放。

为什么要用 FIN 扫描?因为它能绕过一些只拦截 SYN 包、放过 FIN 包的防火墙。我几年前在某运营商做过测试:他们边界防火墙只配置了 SYN flood 防护,FIN 包直接放行。结果我用 FIN 扫描扫出了一大片开放端口,对方完全没察觉——直到他们查流量日志才发现异常。

注意: FIN 扫描对 Windows 系统基本无效。Windows 不管端口开没开,只要是 FIN 包都回 RST。说白了,这种方法只对 Linux/Unix 类系统好用。

Xmas Scan 与 NULL Scan

Xmas 扫描是把 FIN、URG、PSH 三个标志位都设为 1(像圣诞树一样挂满灯),NULL 扫描则是所有标志位都是 0。它们的逻辑跟 FIN 扫描一样:收到 RST 表示关闭,没响应表示开放。

这些奇技淫巧现在越来越不好用了。现在的防火墙和 IDS 基本上是个扫描方式都能识别——除非你把发包速度压到极其缓慢,看起来像正常流量。

UDP 扫描

UDP 是无连接的,扫描方式跟 TCP 完全不同。你发一个空的 UDP 包到某个端口:

  • 如果端口开放,通常什么也不回(或者回对应的应用层数据)。
  • 如果端口关闭,目标会回一个 ICMP Port Unreachable 报文。

所以 UDP 扫描的判定是:收到 ICMP 不可达 = 关闭;没收到 = 可能开放或过滤。你想想看,这种“没回复就当作开放”的判断非常不可靠,丢一个包就误判了。

我的经验: 实际扫描时,我对每个端口至少发 2-3 次探测,并且把 ICMP 超时等待拉到 10 秒以上。即便如此,UDP 扫描还是耗时很长。有次扫描一个 C 段(254个地址),每个地址扫 100 个常见 UDP 端口(DNS、SNMP、DHCP),跑了三个多小时才完成。如果需要快速摸清资产,UDP 扫描性价比很低,建议只扫常见端口列表。

下面我把几种主流扫描方式的差异整理成一个表格,方便对比:

扫描方式传输层发送标志位开放响应关闭响应是否完成握手隐蔽性
TCP ConnectTCPSYNSYN+ACKRST是低
SYN ScanTCPSYNSYN+ACKRST否中
FIN ScanTCPFIN无响应RST—较高(对 *nix)
Xmas ScanTCPFIN+URG+PSH无响应RST—较高(对 *nix)
NULL ScanTCP无无响应RST—较高(对 *nix)
UDP ScanUDP—无响应ICMP Port Unreachable—低(耗时高)

看完这个表你应该能理解,为什么实战中 SYN 扫描是最主流的——它在隐蔽性和可靠性之间取得了平衡。

扫描知识体系一览

下面我画了一张流程图,把整个网络扫描的不同分支和判定逻辑串起来。嗯,画图时我把“防火墙过滤”也加了进去,因为现在扫描多半都会遇到防火墙。

网络扫描技术体系 扫描目标(IP:Port) 协议选择 TCP 扫描 UDP 扫描 Connect SYN FIN Xmas/NULL UDP 空包探测 根据响应判断端口状态 匹配规则 开放 关闭 过滤/未知 说明:TCP 扫描的开放/关闭判定因扫描方式而异(例如 FIN 扫描 无响应判为开放,RST 判为关闭);UDP 扫描无响应判为可能开放。

这张图里我把 TCP 四种扫描和 UDP 都列出来了,最终指向“根据响应判断端口状态”。记住:不同的扫描方式适合不同的场景,没有银弹。

实战避坑指南

  • 防火墙多试试 FIN 和 Xmas: 我曾经在渗透测试中,SYN 扫描全被拦掉,换成 FIN 扫居然扫出了 300 多个开放端口。但别高兴太早,Windows 环境别用 FIN。
  • 速度控制很重要: 不要一上来就全速扫。我记得有一次调错了参数,每秒发 5000 个包,直接把目标交换机 CPU 打满,对方运维直接关掉了我们实验室的端口。后来我每次扫描先算好目标带宽和防火墙限流,保守设置发包间隔。
  • UDP 扫描只扫关键端口: 比如 53(DNS)、123(NTP)、161/162(SNMP)。全量 UDP 扫描会疯掉,耗时长、结果还不准。
  • 结合 ICMP 判断目标存活: 做扫描前先 Ping 一下,避免往死主机上白费力气。

好了,这一套扫描技术原理你心里有数了吧?下次做 DSPM 扫描分类实战的时候,可以根据目标环境灵活选择扫描方式。别死板,多试几种,你会发现有些网络环境里的“小漏洞”就是靠这些非常规扫描发现的。