在互联网上传输的每一个 IPv4 数据包都在其二进制报头中包含一个 16 位的标识字段(IP ID)。该字段于 1981 年在 RFC 791 规范中确立,旨在允许接收方操作系统重新组装超过网络路径最大传输单元(MTU)的分片数据报。在长达数十年的时间里,内核开发者通常将该字段实现为一个简单的全局自增整数:主机每发出一枚 IP 数据包,内部计数器便自动加一。
这一原本用于网络组装的常规设计,演变成了计算机网络史上极为精妙的侧信道安全缺陷之一。通过远端观察第三方主机的 IP ID 计数器变动,外部探测者可以在不暴露自身真实 IP 地址的前提下,映射目标服务器在防火墙背后的端口开放情况,推测私有子网间的主机通信,甚至监控远程主机的发包速率。
现代网络协议标准(尤其是 RFC 6864)与基于 WireGuard 架构的现代加密隧道,对操作系统处理网络报头标识符的方式进行了系统性重构。探究 IP ID 侧信道的工作机理,能够帮助工程师深刻理解传输层安全、操作系统内核状态隔离与加密隧道防御的核心逻辑。
+-------------------------------------------------------------------------+
| 经典 IDLE SCAN 攻击三角拓扑 |
+-------------------------------------------------------------------------+
| |
| 1. 探测初始 IP ID (SYN/ACK) 2. 伪造 SYN 探测 (源IP=僵尸主机) |
| 僵尸主机 IP ID = 31337 目标端口 443 |
| ┌─────────────────────────┐ ┌─────────────────────────┐ |
| │ ▼ │ ▼ |
| [探测者] [僵尸主机] [目标服务器] |
| ▲ │ ▲ │ |
| │ └───────┼─────────────────────────┘ |
| 3. 探测新 IP ID (SYN/ACK) │ 目标向僵尸主机回应 SYN/ACK |
| 僵尸主机 IP ID = 31339 │ 僵尸主机回应 RST (IP ID +1!) |
| 差值 Delta = 2 -> 端口开放! └─────────────────────────┘ |
+-------------------------------------------------------------------------+
IPv4 标识字段的起源与分片机制(RFC 791)
根据最初的 RFC 791 规范,当中间路由器转发的数据包尺寸大于下一跳网络的 MTU 时,必须对数据报进行分片。为了让目标主机能够准确区分属于不同原始数据报的分片,发送端操作系统会为每个原始数据包分配一个 16 位的数值标识。
接收端协议栈通过四元组重新拼装分片:
- 源 IP 地址(Source IP)
- 目的 IP 地址(Destination IP)
- 协议标识符(Protocol ID)(如 TCP、UDP 或 ICMP)
- 标识值(
IP ID)
由于 16 位整型仅能表示 65,536 个不同取值,早期操作系统(包括历史版本的 Windows、BSD 以及早期的 Linux 内核)均使用一个单体全局变量进行递增:
// 早期操作系统的典型实现:全局单调自增
static unsigned short global_ip_id = 0;
void build_ip_header(struct iphdr *iph) {
iph->id = htons(++global_ip_id);
}
这种完全可预测的工程实现无意中创建了一个公开的遥测通道:任何外部节点均可通过定期采样,推算出目标主机在过去一段时间内累计发送的数据包总量。
Idle Scan 核心机理:借助僵尸主机的盲测扫描
1998 年,安全研究员 Salvatore Sanfilippo(网名 antirez)发现,利用全局可预测的 IP ID 计数器可以实现隐蔽的盲测端口探测。该技术被称为 Idle Scan(后由 Nmap 以 -sI 参数正式实现),它允许探测者在不向目标服务器发送任何包含自身真实地址的数据包的情况下,查明目标的端口状态。
该探测过程涉及三个角色:
- 探测端(Attacker): 发起探测指令并收集计量数据的控制主机。
- 僵尸主机(Zombie Host): 公网上处于空闲状态、采用全局递增 IP ID 且自身背景流量极低的第三方主机。
- 目标服务器(Target Server): 正在被审计防火墙策略与端口监听状态的目标系统。
整个勘测过程依赖于诱导目标服务器与僵尸主机产生网络交涉,随后通过探测端测量僵尸主机 IP ID 的增量变化。
Idle Scan 的分步技术实现过程
Idle Scan 在检测目标的每一个端口时,均按照严密的三个步骤执行:
- 第一步:建立基线探测: 探测端向僵尸主机发送一个非预期的
SYN/ACK数据包。由于僵尸主机并没有对应的 TCP 连接,其协议栈会回复一个RST数据包,其中携带了当前的标识值($IPID_1 = 1000$)。 - 第二步:伪造源地址探测目标: 探测端向目标主机的特定端口(如 443)发送原始
SYN请求,但在 IPv4 报头中将源地址篡改为僵尸主机的真实 IP。 - 第三步:测量僵尸主机差值: 探测端向僵尸主机再次发送一枚
SYN/ACK包,触发其产生第二个RST响应,从而记录更新后的标识值($IPID_2$)。
端口状态转换分析:开放、关闭与过滤
两次测量之间的差值($\Delta = IPID_2 - IPID_1$)直接映射了目标端口的状态:
+-------------------------------------------------------------------------+
| 依据增量差值测定目标端口状态表 |
+-------------------------------------------------------------------------+
| 目标端口物理状态 | 目标对伪造 SYN 的网络响应 | 僵尸主机计数增量 (Δ)|
| :--- | :--- | :--- |
| 开放 (OPEN) | 向僵尸主机发送 SYN/ACK | Δ = 2(端口开放) |
| | 僵尸主机回送 RST(消耗计数) | |
| 关闭 (CLOSED) | 向僵尸主机发送 RST | Δ = 1(端口关闭) |
| | 僵尸主机静默丢弃(不发包) | |
| 过滤 (FILTERED) | 防火墙静默丢弃该请求 | Δ = 1(目标无响应) |
| | 僵尸主机未收到任何流量 | |
+-------------------------------------------------------------------------+
- 端口处于开放状态($\Delta = 2$): 目标服务器接受连接请求并向僵尸主机回发
SYN/ACK。僵尸主机在未建立连接的情况下收到此包,会立即向目标发出RST进行重置,该动作消耗并推进了一个 IP ID。当探测端在第三步采样时,观察到的 IP ID 比初始值增加了 2 个单位。 - 端口处于关闭或过滤状态($\Delta = 1$): 若端口关闭,目标直接向僵尸主机回送
RST(僵尸主机不会对 RST 作出应答);若端口被防火墙静默丢弃,目标无响应。在这两种场景下,僵尸主机未产生额外数据包,其计数器仅因探测端的第三步查询递增了 1。
目标服务器的入侵检测系统(IDS)与防火墙日志中仅记录有僵尸主机的 IP 地址,探测端的网络位置得以隐蔽。
流量速率推断与跨网络主机追踪风险
IP ID 侧信道所能透露的系统情报远不止端口状态:
- 监控服务器吞吐速率: 外部节点以固定毫秒间隔对目标系统的 IP ID 持续轮询,便能描绘出其出站数据包的时序曲线,从而发现规律性的数据库备份或系统维护时段。
- 推断内网通信关联: 当两台内部服务器在未加密的网络中通信时,两者 IP ID 计数器同步跃升的特征能够证实节点间存在数据交互。
- 移动终端设备指纹关联: 早期部分便携式设备采用了相对固定的随机数种子,导致设备在不同无线接入点之间漫游时,依然能被根据其特征关联识别。
RFC 6864 标准化:消除原子数据报中的 IP ID
针对 IP ID 全局自增带来的隐患,互联网工程任务组(IETF)于 2013 年正式发布了 RFC 6864: Updated Specification of the IPv4 ID Field。
RFC 6864 基于现代网络架构的核心事实:现代互联网全面普及了路径 MTU 发现机制(PMTUD, RFC 1191),绝大部分 TCP 与 UDP 流量均设置了不分片标志(DF = 1)。凡是设置了 DF = 1 且本身非分片的 IPv4 数据包,均被定义为原子数据报(atomic datagrams)。
RFC 6864 做出了几项关键标准变革:
- 标识符清零: 明确允许并倡导主机将所有原子数据报的 IP ID 字段直接填充为常量
0x0000。 - 接收端免检: 当数据包带有
DF = 1标识时,接收方操作系统不得要求其具备全局唯一的 IP ID 序列。 - 废弃全局自增器: 操作系统内核严禁在无关通信目的地之间维护统一的单调递增计数器。
+-------------------------------------------------------------------------+
| RFC 6864 原子数据报报头结构示意 |
+-------------------------------------------------------------------------+
| IPv4 报头: |
| [ Version: 4 ] [ IHL: 5 ] [ DSCP/ECN ] [ Total Length: 1500 ] |
| [ Identification: 0x0000 ] <--- RFC 6864 规定原子包置零 |
| [ Flags: DF=1, MF=0 ] [ Fragment Offset: 0 ] |
| [ TTL: 64 ] [ Protocol: 6 (TCP) ] [ Header Checksum ] |
| [ Source IP ] [ Destination IP ] |
+-------------------------------------------------------------------------+
现代操作系统的防御架构:Linux 与 BSD 实现
当前主流操作系统的网络协议栈均已彻底剥离全局状态共享机制。
单连接哈希表与密码学 PRNG 随机生成机制
操作系统主要通过以下架构实现状态隔离:
- Linux 内核设计: 现代 Linux 内核对所有原子数据包默认置
iph->id = 0。对于极少数非原子包,内核采用siphash算法,将源地址、目的地址、传输协议与内核随机密钥进行哈希运算,映射至分布式哈希表中,实现严格按对端通信隔离计数。 - BSD 与 macOS 体系: 引入密码学安全的伪随机数生成器(PRNG),确保每个新建网络连接生成的序列在数学上彼此独立,无法被推导分析。
- Windows 协议栈: 从 Windows 10 与 Windows Server 2016 开始,彻底抛弃了跨主机的全局单调序列,改为按对端随机生成。
三层 VPN 隧道与 RiceGuard 如何消除 IP ID 泄漏
工作在三层 TUN 模式下的加密隧道(如 WireGuard)能够在物理层面上彻底隔绝 IP ID 侧信道探测。
VPNRice 的网络架构遵循严谨的密码学封装标准。基于 RiceGuard 协议,客户端的所有业务流量在进入公网传输前,均被完整封装在通过身份鉴别的 UDP 数据报中。
+-------------------------------------------------------------------------+
| RICEGUARD 数据封装与 IP ID 隔离架构 |
+-------------------------------------------------------------------------+
| 客户端操作系统协议栈: |
| [ 内层原始 IP 报文: 目标 TCP 握手 | 内层 IP ID: 本地生成 ] |
| │ |
| ▼ |
| RiceGuard 隧道引擎: |
| [ ChaCha20-Poly1305 加密载荷 (内层网络报头被完整混淆保护) ] |
| │ |
| ▼ |
| 外层公网传输物理报文: |
| [ 外层 IPv4 报头: UDP 51820 | 外层 IP ID = 0x0000 (遵循 RFC 6864) ] |
+-------------------------------------------------------------------------+
RiceGuard 通过三重防护彻底封堵了侧信道隐患:
- 网络报头全量封装: 内层 IPv4 报头及其原始 IP ID 被完整加密在 Noise 协议载荷内,公网节点与中间网络提供商无法观测或关联其内部标识。
- 外层规范遵循 RFC 6864: 隧道客户端发出的外部 UDP 报文始终启用
DF = 1,且外层IP ID固定置为0x0000,外部探测无法获取任何有效的增量信息。 - 基于公钥的仿冒阻断: 网关节点在处理任何报文前均执行公钥密码学校验,未经授权的伪造探测包会被静默丢弃,不会引起任何计数器变动。
需要防护网络侦测、提升通信安全的用户,可前往 VPN 下载页面 获取官方客户端。
实践检测:利用 Nmap 识别易受攻击的僵尸主机
网络管理人员可以使用 Nmap 脚本检查网络设备是否存在可预测的计数器递增风险:
# 评估候选主机是否采用单调自增的 IP ID 生成策略
nmap -p 80,443 --script ipidseq 198.51.100.25
# 易受攻击主机输出示例:
# Host script results:
# |_ipidseq: Incremental! (存在被利用为 Idle Scan 僵尸主机的风险)
# 遵循 RFC 6864 的安全输出示例:
# Host script results:
# |_ipidseq: All zeros! (安全: 严格遵循 RFC 6864 规范)
若某台机器返回 Incremental,安全人员可在经过授权的实验室内验证其扫描可行性:
# 使用指定的僵尸主机执行 Idle Scan 验证
nmap -Pn -sI 198.51.100.25:80 203.0.113.50
当选用符合规范的主机作为中间节点时,Nmap 将提示:Cannot use zombie host: IP ID sequence is not incremental。
架构对比:全局递增与随机哈希生成机制
| 技术评估维度 | 全局单调自增机制 | 按对端独立哈希表 | RFC 6864 常量置零 (RiceGuard) |
|---|---|---|---|
| Idle Scan 被利用风险 | 极高风险 | 免疫(对端状态完全隔离) | 免疫(报头无动态标识) |
| 外网推算发包速率 | 外部可轻易推断 | 第三方无法测定 | 第三方无法测定 |
| 跨网络设备指纹风险 | 较高(存在连续特征) | 极低 | 零风险(报头字段恒定) |
| 数据包分片重组支持 | 完整支持 | 完整支持(按对端维护) | 基于 PMTUD 完整支持 |
| 内核内存开销 | 2 字节全局变量 | 适中(动态哈希表条目) | 零开销(固定常量) |
| CPU 运算损耗 | 极低(自增指令) | 较低(SipHash 运算) | 最低(硬编码 0x0000) |
向 IPv6 的演进:基础报头彻底剔除标识字段
针对 IP ID 侧信道问题的终极解决途径来自新一代协议 IPv6(RFC 8200)的架构设计。
鉴于分片在现代网络传输中的比例已不足千分之一,IPv6 彻底取消了 40 字节固定基础报头中的 Identification 字段。在 IPv6 中,沿途路由器被严格禁止对数据包实施分片;所有分片动作仅能由发送端主机通过可选的“分片扩展报头”(Fragment Extension Header)按需生成:
+-------------------------------------------------------------------------+
| IPV6 基础报头规范:完全剥离静态标识 |
+-------------------------------------------------------------------------+
| IPv6 固定基础报头 (40 字节): |
| [ Version: 6 ] [ Traffic Class ] [ Flow Label: 20 bits ] |
| [ Payload Length: 16 bits ] [ Next Header: 6 (TCP) ] [ Hop Limit: 64 ] |
| [ Source IPv6 Address: 128 bits ] |
| [ Destination IPv6 Address: 128 bits ] |
| |
| * 基础报头中不存在 IDENTIFICATION 字段! * |
+-------------------------------------------------------------------------+
由于原子数据报在 IPv6 中不包含分片扩展报头,外部探测者在报文中根本找不到可供采样的标识字段。这使得 Idle Scan 在原生 IPv6 环境下在物理结构层面彻底失效。
系统管理员应对 IP ID 侧信道的防护清单
系统管理员可通过以下配置强化基础网络防护:
- 推行 RFC 6864 规范: 生产环境采用 Linux 4.0 以上版本内核,确保系统原子数据报的 IP ID 严格置零。
- 保持 PMTUD 畅通: 确保边缘防火墙放行 ICMP Type 3 Code 4(需要分片)报文,避免出现分片黑洞。
- 部署源地址防伪过滤(BCP 38): 在边界路由器开启单播反向路径转发(uRPF),拦截伪造源 IP 的非法语音与探测流量。
- 管理流量隧道化: 将跨数据中心运维通信全面接入 RiceGuard 加密隧道,消除传输元数据外泄。
- 退役陈旧操作系统: 加快淘汰仍在使用全局递增 IP ID 的陈旧嵌入式固件与历史版本服务器系统。
现代网络协议的规范化演进与端到端加密封装,已成功使传统的 IP ID 侧信道失去效用,为数据传输提供了可靠的底层隔离保障。



