IP 报头中 IPID 的历史与反思
IP 脱胎于 1970 年代的 TCP,在 1981 年的 RFC791 定为 IPv4。IP 报头里有个 ID 字段,用来唯一标识一个被分片的 IP 报文。本来希望 IP 可以自适应异构网络 MTU 的分片特性,随着 MTU 探测的使用,IP 分片已不多见,但几乎用不到的 ID 字段带来的安全问题却越来越多。
先看 ID 是如何生成的,系统一般会从最简单的生成方式开始,比如维护一个全局计数器,每发送一个 IP 报文计数器递增,将它作为 ID 值。但疯狂的事情来了,ID 泄露了信息:ID 字段连续或者准连续的报文来自同一主机。
这涉及到一个信息安全领域的原则,最小通用原则,祸首是共享数据。共享数据往往是不同用户之间的信息交换途径,它连接了整体和局部,攻击者很容易建立全局关联,而信息的本质就是关联。
前文 TCP off-path exploits 举了一例:为解决 Blind In-Win Attacks => 引入 Challenge ACK => 造成潜在 DDoS => 引入 ACK Throttling => 为 Blind In-Win Attack 提供了有效信息 => Blind In-Win Attack 实施更容易,不再 Blind!根源在于 ACK Throttling Threshold 泄露了信息,IPID 也一样。
可以通过观测 IPID 字段获取主机的流量信息。
最简单的一个例子,在一个旁路上,攻击者为嗅探目标流量大小,可以与目标主机建立常规连接或单纯地 ping,通过观察 IPID 字段的分布和自己发送数据的频率之间的关系就知道目标主机的流量大小,但这不是本文的重点,具体的事看下面函数的注释就够了:
static inline void ip_select_ident_segs(struct net *net, struct sk_buff *skb,
struct sock *sk, int segs)
{
struct iphdr *iph = ip_hdr(skb);
/* We had many attacks based on IPID, use the private
* generator as much as we can.
*/
if (sk && inet_sk(sk)->inet_daddr) {
iph->id = htons(inet_sk(sk)->inet_id);
inet_sk(sk)->inet_id += segs;
return;
}
if ((iph->frag_off & htons(IP_DF)) && !skb->ignore_df) {
iph->id = 0;
} else {
/* Unfortunately we need the big hammer to get a suitable IPID */
__ip_select_ident(net, iph, segs);
}
}
因为 “We had many attacks based on IPID”,所以有了第 9 行那个 “private generator” IPID,但有没有想过,如此这般会违背 IPID 的初衷:唯一标识 MSL 范围的 IP 报文!
16bit 的 IPID 非常容易碰撞冲突,如果冲突了,IP 分片重组就会出问题(虽然它并不常用),这又是一例 “设计带来问题,解决该问题引入另一个问题,解决这另一个问题又推倒了设计”。
既然 per-socket IPID 会冲突,那么全局 hash 也会冲突,还得指望全局变量,比如 16bit 的高 10bit 全局递增,低 6bit 随机化,这是不是能缓解 Attack 成功概率呢?能也不能。说能因为确实不好猜了,说不能因为攻击者也能控频,比如加大发包密度就可以检测跳跃度。
但纠结这些重要吗,是不是要再设计一个更 “安全” 的 IPID 生成算法呢?回到本质,IP 分片的唯一性标识,直接用五元组不好吗?
IPID 这个字段本身就有问题,五元组已经唯一标识报文了,没必要在为 IP 层准备个 ID,这一切根源于 IP 早期从 TCP 分离时职责没有分清,但要分层,IP 是固然不能用 TCP 信息。ID 字段没有硬性规定如何实现,更有只为优化数据面的意味。如今的观点看,直接用五元组作唯一性标识固然低效,但 IP 分片本不多见,不值得为异常流付出太多,而对于非分片的本地 IP 报文,逐层往上通过 protocol,sport,dport 就自然唯一解复用了,早期 IPID 字段按照 per-protocol 递增也是这个意思,很明显,在 IP 报文唯一性保证方面,IPID 是一个重复冗余的设计。
所以,IPv6 没了这个字段。分片问题很容易解决,不让分片就行了,一下子解决两个问题。不让分片简化了协议,不让分片也规避了异常流的低效处理。
这里的故事讲的依然是为了覆盖 1% 罕见场景引入了 10% 的问题,开销和问题利滚利,协议逐渐臃肿。
最后说个题外话,我曾经在职期间通过 IPID 字段抓住过针对前司的 DDoS 攻击。通过分析 IPID 的分布,结合源 IP 地址,定位了这批次的攻击来自北欧某三个机房,吊不吊。
浙江温州皮鞋湿,下雨进水不会胖。