bigywater.tech
网络协议 · 44

SYN Flood 与 SYN Cookie:防御原理

没有伪造源地址的能力,怎么研究 SYN Flood?这篇用 netlab 把握手拖慢,让两万个并发连接同时卡在半连接状态,看服务端的队列、SYN-ACK 的选项有没有变化,再用一个计算演示讲清 SYN cookie 怎么做到不保存状态。

20000 个并发
用 netlab 拖慢握手后同时发起的连接数(小一个数量级时 SYN-ACK 选项全部完整)
4243 个
20000 个并发里出现的只有 MSS + SACK 的精简 SYN-ACK(没有时间戳和窗口缩放)
24 位哈希
SYN cookie 演示里编进服务端序号的哈希位数(加 5 位时间戳、3 位 MSS 索引)

SYN 洪水

实测 · 本机执行输出
  net.inet.tcp.syncookie: 1
  net.inet.tcp.syncookie_hmac_sha256: 0
  net.inet.tcp.drop_synfin: 1
  kern.ipc.somaxconn: 128
  netstat -s -p tcp 里和 SYN / cookie 有关的行:
    0 challenge ACK sent due to unexpected SYN
    0 listen queue overflow
    0 time our SYN with data has been acknowledged
    0 syncache entries aborted
    0 SYN cookie sent
    0 SYN cookie received

伪造源地址发大量 SYN,服务端为每个半连接保存状态,队列耗尽后正常用户连不上。没有原始套接字,所以用延迟握手近似。

本机的反应

实测 · 本机执行输出
  6074 ms 内发起了 20000 个 connect;netstat -L 的 qlen/incqlen/maxqlen 随时间:
  抓到服务端发出的 SYN-ACK 131962 个(去掉 dummynet 重复抓到的后按序号 / 确认号去重 22787 个不同的连接);SYN-ACK 选项组合:MSS+SACK+TS+WScale × 127719;MSS+SACK × 4243
  按发出顺序:前 128 个 SYN-ACK 的选项组合 {('MSS', 'SACK', 'TS', 'WScale'): 128};之后的 22659 个 {('MSS', 'SACK', 'TS', 'WScale'): 18935, ('MSS', 'SACK'): 3724}
  3 秒后客户端侧:连上 527 个、没连上(仍在等或被拒)19473 个;服务端全连接队列 128/27/128(maxqlen 128,所以最多 128 个连接会被保留)
  netstat -s -p tcp 里这段时间增加的 SYN / cookie 相关计数:没有变化

小一个数量级的并发时所有 SYN-ACK 都完整;20000 个并发时出现一批只有 MSS + SACK 的 SYN-ACK,全连接队列封顶 128,多出的连接握手完成后被 RST。

SYN cookie

实测 · 本机执行输出
  客户端 192.0.2.7:40000 → 198.51.100.1:80,客户端 ISN = 123456789:服务端把 SYN-ACK 的序号(ISN)算成 999260433(= 时间戳低 5 位 7 · MSS 索引 3(=1460)· 哈希 24 位),不保存任何状态
  客户端回第三个 ACK,确认号 = 999260434:服务端用确认号 − 1 = 999260433 反推出 时间戳 7、MSS 索引 3(= 1460),重算哈希一致 → 接受连接;伪造的 ACK(123456790)的重算结果不一致:攻击者没有密钥无法凑出正确的哈希
  代价:没处存 SYN 里带来的其它选项(窗口缩放、SACK、时间戳都丢了,只能编进少量比特的 MSS),所以 cookie 只在队列快满时才启用
  攻击面:SYN 洪水用伪造的源地址发大量 SYN,服务端为每个半连接保存状态直到超时(这里 SYN_RCVD 的状态),耗尽队列后正常客户端连不上;SYN cookie 让半连接不占内存

把时间戳、MSS 索引和 HMAC 编进 SYN-ACK 序号,第三个 ACK 回来时反推验证,服务端不保存半连接状态;代价是丢掉窗口缩放等选项。

分步演示

SYN Flood 与 SYN Cookie
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。没有原始套接字权限,不能伪造源地址:这里的“洪水”只是同一个地址发出的大量并发 connect,握手被 netlab 拖慢到 800 ms;第二节先后运行过较小规模和 20000 个并发,取最后一次(20000 个)的数字;dummynet 会让同一个包被抓到多次,已按 (序号, 确认号) 去重;第三节是计算演示,不是 macOS 内核的 cookie 格式
    • 没有验证:SYN-ACK 选项变少是否确实是 SYN cookie(本机 netstat -s -p tcp 的 TCP 计数全是 0,不能对账);真实攻击下 syncache 的容量与淘汰策略;syncookie_hmac_sha256 开关的效果;syncache 与 so_incqlen 的关系;防火墙 / 负载均衡上的 SYN 代理
    京ICP备2026067502号-1京公网安备11010502063487号