承接前面几篇文章的观点,本文用一个安全攻击的例子说明为了解决一个伤害很低的低概率问题,会引入多么大的麻烦,这次是可怕的被攻击 (⊙o⊙)。
TCP 端口号只有 16bit,序列号只有 32bit,这意味着在强大攻击算力面前,它很容易会被 Blind In-Window Attacks 伤害。比如,如果一个针对知名服务器 80 端口的攻击者恰好猜对了 sport,并且使用 in_window 的序列号伪造了 SYN 报文(or RST,Data…)发给服务端,服务器将重置正常的连接,这种攻击随着主机内存的增加从而 window 的增加而更加容易。
为了让这种 Blind In-Window Attacks 更难以实施,RFC5961 引入了 Challenge ACK 的概念,如果收到莫名其妙的异常报文(比如异常 SYN,RST),将重置的责任推给发送方:
- 向发送方发一个 Challenge ACK,如果果真是发送方发生了异常,收到 Challenge ACK 后自然会自行 RST 连接,否则就是遭受了 Blind In-Window Attack。
多么好的创意,却弄巧成拙。
设计者意识到 Blind In-Window Attacks 可能会带来海量的 Challenge ACK 报文注入网络占用带宽资源,从而导致另一类 DDoS Attack,于是提出 ACK Throttling 方案:在固定时间段(比如 1 分钟)只发送固定数量(比如 100)个 Challenge ACK。
是不是很完美?是也不是,说 “是” 因为想到的问题都解决了,说 “不是” 因为总有想不到的。下面是一个闭环:
- 为解决 Blind In-Win Attacks => 引入 Challenge ACK => 造成潜在 DDoS => 引入 ACK Throttling => 为 Blind In-Win Attack 提供了有效信息 => Blind In-Win Attack 实施更容易,不再 Blind!
下面是一个攻击图示:
如 Linux 常规配置的 Throttling thresh 是 1 分钟 100 次,攻击者只需要与熟知端口服务器创建正常连接,然后发送一个猜测的 sport,seq 携带 SYN 的报文,紧接着在 1 分钟内在合法连接 conn-2 中发送 100 个 In-Win 的 RST,如果攻击者收到了 100 个 Challenge ACK,说明猜错了,继续改变 sport,seq,如果它收到了 99 个 Challenge ACK,说明猜对了,因为有一个 Challenge ACK 发给了被攻击者 A,是不是很有趣?
紧接着,知道了 sport,攻击者便可以用同样的方法猜测 seq 和 ack 来注入数据了。是不是机关算计太聪明,反误了卿卿性命?
针对这(又一个,yet another)个问题,我曾经本想着提交一个派池,不再用固定的 threshold,而是用随机数,一会儿换一个,当我查代码时,发现在 4.7 版本已经更正了:
/* RFC 5961 7 [ACK Throttling] */
static void tcp_send_challenge_ack(struct sock *sk, const struct sk_buff *skb)
{.../* Then check host-wide RFC 5961 rate limit. */now = jiffies / HZ;if (now != challenge_timestamp) {u32 half = (sysctl_tcp_challenge_ack_limit + 1) >> 1;challenge_timestamp = now;WRITE_ONCE(challenge_count, half +prandom_u32_max(sysctl_tcp_challenge_ack_limit));}count = READ_ONCE(challenge_count);if (count > 0) {WRITE_ONCE(challenge_count, count - 1);NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPCHALLENGEACK);tcp_send_ack(sk);}
}
而在此之前,它长这样:
/* RFC 5961 7 [ACK Throttling] */
static void tcp_send_challenge_ack(struct sock *sk, const struct sk_buff *skb)
{.../* Then check the check host-wide RFC 5961 rate limit. */now = jiffies / HZ;if (now != challenge_timestamp) {challenge_timestamp = now;challenge_count = 0;}if (++challenge_count <= sysctl_tcp_challenge_ack_limit) {NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPCHALLENGEACK);tcp_send_ack(sk);}
}
但天知道会不会有引入另一个问题,一定会。
讲这个故事的技术细节不是本意,我想表达的是,不要为解决一件小事而引入复杂的机制。回到 RFC5961 之前,SYN,RST 的 Blind In-Win Attack 发生多吗?成功概率大吗?不是因为它有可能发生就一定要去解决它。此外,针对安全问题,我一向的观点是 “不与陌生人说话”,你说的每句话都在透露信息,如果非要说,那么 “说谎,并且每次说不同的谎”,真相不重要,重要的是指纹,不要让人猜到你的特征。
浙江温州皮鞋湿,下雨进水不会胖。