对 tcp out-of-window 的安全建议
TCP 收到一个 out of window 报文后会立即回复一个 ack,这是 RFC793 中 SEGMENT ARRIVES 段的要求。但这是为什么?难道不是默默丢弃才对吗?
对 oow 报文回复 ack,岂不是把正确的 ack 号回过去了吗,这样攻击者盲打一番就能拿到正确的 seq(至少 in-order) 实施数据劫持篡改。所以为 oow 报文回复 ack 目的是什么?
别扯 Keepalive,原始 TCP 规范没有 Keepalive。Keepalive 本身非标,它或许歪打正着利用了 TCP 的该原始漏洞:为 oow 报文回复 ack。
如果一开始 TCP 规范根本不对 oow 报文回复 ack,Keepalive 就必须想别的办法了,再也无法以 seq = max_seq - 1 作为序列号了。
无论如何,安全考虑,不与陌生人说话。
说完针对 receiver 的恶意 oow 报文,再看针对 sender 的恶意 in-window 报文。如果 sender 收到一个恶意 in-window 报文,该报文 ack > snd.una 会怎样?sender 会清理掉 ack - snd.una 之间的数据,如果这部分数据有丢失,丢失的数据将永远无法被重传。
如果 sender 只是单向发送,从不接收数据,通告一个 zero window 是高尚的。又或者 sender 只是接收少量数据,不 care 吞吐,就别通告太大的窗口。家里阳台越大,被烟花误窜的概率越大。
下面详细解释该 case。
oow(out-of-window) 不更新 receiver 的 una,这保护了 receiver 的 send queue 不会被 blind attacker 轻易篡改推进,而我们知道,una 被篡改推进会导致丢失的数据永远不会被重传。
但反过来呢?in window 但 out-of-order 岂不是可以随意更新 una?是的。但这会带来下面的问题:
不想同时作为 receiver 的 sender 如果打开一个过大的 rwin,就更容易被 blind attacker 构造的 in window 报文篡改推进 una,造成丢失的数据无法被重传,最终 receiver 的 hole 永远不会被填充,耗尽 rwin,跌入 zero window 万劫不复,一条连接就这样被打死。
如果不想接收数据,一定注意将 rwin 缩小甚至关闭。
TCP 是全双工连接,但 ack 方向可统一在反向 data 报头被捎带,两个方向之间的影响不容忽视。
无论如何,安全考虑,别把大门敞太大。
总之,对于 receiver,抵制随意的 out-of-window 报文,保护 rcv queue 数据,对于 sender,抵制随意的 in-window 报文,保护 rtx queue 数据。
再提一下 Keepalive,如果 TCP 可扩展,Keepalive 何必使用这种 max_seq - 1 如此怪异的 oow 报文来探测,单独一个 probe request flag 更自然,receiver 只需立即回复携带 probe response flag 但不设置 A flag 的 ack 报文。
标准并不一定一开始就正确,但标准在无伤大雅的情况下很难发生变化。
此类安全问题到底应不应该由 TCP 负责?在我看来数据篡改问题不是 TCP 的职责,应用程序发觉后直接报错断开即可,但 una 被篡改造成丢包不能重传就是 TCP 的问题了,它足以形成一种新的 DDoS,并且对于应用程序,在发现这种情况之前,浪费了很多时间。
此外,状态防火墙要是丢掉 oow,Keepalive 就用不了。Linux nf_conntrack 采用另一种宽松的方式判定 tcp_in_window 足以支持 Keepalive,挺好。
周中帮忙看了一个关于 Keepalive 的问题,其实我是一直觉得 Keepalive 本就是恰巧擦边生效的机制,若不是对 oow 报文回复一个不合理的 ack,Keepalive 根本就不会得到回应。而从 Linux 4.x 开始,对于 oow 报文的 ack 就是可回可不回的,取决于 oow 报文到达的 rate,这意味着对标准理解的松动,在我看来,与其不 care,不如 MUST NOT,不要对 oow 报文进行任何响应,连计数器都不更新!这篇短文给出相关的安全建议。
浙江温州皮鞋湿,下雨进水不会胖。