网络扫描基础:TCP/UDP端口扫描、SYN扫描、FIN扫描等技术原理
做 DSPM 扫描分类实战,网络扫描是绕不开的一块。说白了,扫描就是搞清楚“目标机器上到底开了哪些口子”。我刚开始搞安全那会儿,以为扫描就是简单发个包等回包,后来发现门道多得很。今天咱们就把 TCP 和 UDP 那几种扫描方式掰开揉碎了讲清楚。
端口扫描的本质
端口就是服务的入口。你想知道一台服务器有没有开 Web 服务,就看 80 或 443 端口开着没。操作系统对端口的响应行为能给咱们透露出大量信息。
关键点: 扫描不是瞎发包,而是利用 TCP/IP 协议栈的行为差异来推断端口状态。目标端口无非三种状态:开放、关闭、过滤(被防火墙屏蔽了)。
TCP 扫描家族
TCP 扫描是最常用的。因为 TCP 是面向连接的,三次握手给了我们大量操作空间。下面几种我挨个讲,顺便说说项目里踩过的坑。
完整连接扫描(TCP Connect Scan)
最朴素的方法:和端口完成完整的三次握手。如果连上了,说明端口开放;如果收到 RST,说明端口关闭。
- 优点: 简单可靠,操作系统帮你完成握手,不需要自己构造原始包。
- 缺点: 太容易被发现。我曾在客户内网测试,刚跑完一次 connect 扫描,SOC 那边报警就响了——这种行为太明目张胆了。
SYN 扫描(半开扫描)
这是我个人最常用的方式。发一个 SYN 包,如果收到 SYN+ACK,说明端口开放——这时候你直接发 RST 断开,不完成第三次握手,所以叫“半开”。如果收到 RST,说明关闭;没响应或者收到 ICMP 不可达,说明被过滤了。
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 扫描扫出了一大片开放端口,对方完全没察觉——直到他们查流量日志才发现异常。
Xmas Scan 与 NULL Scan
Xmas 扫描是把 FIN、URG、PSH 三个标志位都设为 1(像圣诞树一样挂满灯),NULL 扫描则是所有标志位都是 0。它们的逻辑跟 FIN 扫描一样:收到 RST 表示关闭,没响应表示开放。
这些奇技淫巧现在越来越不好用了。现在的防火墙和 IDS 基本上是个扫描方式都能识别——除非你把发包速度压到极其缓慢,看起来像正常流量。
UDP 扫描
UDP 是无连接的,扫描方式跟 TCP 完全不同。你发一个空的 UDP 包到某个端口:
- 如果端口开放,通常什么也不回(或者回对应的应用层数据)。
- 如果端口关闭,目标会回一个 ICMP Port Unreachable 报文。
所以 UDP 扫描的判定是:收到 ICMP 不可达 = 关闭;没收到 = 可能开放或过滤。你想想看,这种“没回复就当作开放”的判断非常不可靠,丢一个包就误判了。
下面我把几种主流扫描方式的差异整理成一个表格,方便对比:
| 扫描方式 | 传输层 | 发送标志位 | 开放响应 | 关闭响应 | 是否完成握手 | 隐蔽性 |
|---|---|---|---|---|---|---|
| TCP Connect | TCP | SYN | SYN+ACK | RST | 是 | 低 |
| SYN Scan | TCP | SYN | SYN+ACK | RST | 否 | 中 |
| FIN Scan | TCP | FIN | 无响应 | RST | — | 较高(对 *nix) |
| Xmas Scan | TCP | FIN+URG+PSH | 无响应 | RST | — | 较高(对 *nix) |
| NULL Scan | TCP | 无 | 无响应 | RST | — | 较高(对 *nix) |
| UDP Scan | UDP | — | 无响应 | ICMP Port Unreachable | — | 低(耗时高) |
看完这个表你应该能理解,为什么实战中 SYN 扫描是最主流的——它在隐蔽性和可靠性之间取得了平衡。
扫描知识体系一览
下面我画了一张流程图,把整个网络扫描的不同分支和判定逻辑串起来。嗯,画图时我把“防火墙过滤”也加了进去,因为现在扫描多半都会遇到防火墙。
这张图里我把 TCP 四种扫描和 UDP 都列出来了,最终指向“根据响应判断端口状态”。记住:不同的扫描方式适合不同的场景,没有银弹。
实战避坑指南
- 防火墙多试试 FIN 和 Xmas: 我曾经在渗透测试中,SYN 扫描全被拦掉,换成 FIN 扫居然扫出了 300 多个开放端口。但别高兴太早,Windows 环境别用 FIN。
- 速度控制很重要: 不要一上来就全速扫。我记得有一次调错了参数,每秒发 5000 个包,直接把目标交换机 CPU 打满,对方运维直接关掉了我们实验室的端口。后来我每次扫描先算好目标带宽和防火墙限流,保守设置发包间隔。
- UDP 扫描只扫关键端口: 比如 53(DNS)、123(NTP)、161/162(SNMP)。全量 UDP 扫描会疯掉,耗时长、结果还不准。
- 结合 ICMP 判断目标存活: 做扫描前先 Ping 一下,避免往死主机上白费力气。
好了,这一套扫描技术原理你心里有数了吧?下次做 DSPM 扫描分类实战的时候,可以根据目标环境灵活选择扫描方式。别死板,多试几种,你会发现有些网络环境里的“小漏洞”就是靠这些非常规扫描发现的。