TCP的FIN/RST Cookie


写在前面

TCP在网络时代的古代被设计出来,在中世纪被大量铺开,那是一个绅士的时代,几乎不存在网络安全问题。TCP的诸多问题都是时代的遗毒。现在时代变了,TCP/IP技术也不再由绅士们垄断,任何小朋友都可以利用现代技术在古代的城堡上轰出一个口子,和保护文物不同,目前TCP依然在使用中,因此我们需要做的不是靠政策去保护,而是加固TCP!但是我并不对这种加固报有乐观的态度,唐吉坷德穿的盔甲属于重甲,挡得住矛却挡不住火枪,真正需要的是进化,协议的进化。

在平时的工作和周末的探索中,我也一直在想如何能让TCP更健壮,即不是那么容易被干掉,说白了就是可以识别一个FIN或者RST包的发送是伪造的攻击行为还是连接中的正常行为。借鉴SSL协议以及TCP本身的SYN cookie机制,我设想并初步实现了一个FIN/RST cookie,和以往的一贯的思路一样,事后我要看看是不是已经有了更好的实现,发现了IETF已经解决了这个问题,叫做TCP Authentication option(RFC 5925),其总体思路和我的想法差不多,只是实现思路不同,以下是我和标准不同的地方:

1.我将一个TCP连接分为了控制通道和数据通道,并且只在控制通道上作Authentication,这样可以大大提高效率;

2.我将Auth机制按照安全性需求的高低分了层次;

3.我在TCP状态机内部又实现了一个Auth DH状态机;

4.我由于太懒没有实现用户接口。

我的实现和RFC 5925相同的地方在于,它免除了好多既有的攻击效果,代价就是降低了效率!诸如那些由于FIN/RST包而引入的技术全部都可以免去了,比如time-wait之类的。

一点评论

Fin Cookie?RST Cookie?确认没有拼写错误?是的,就是这两个cookie!如果我不小心把SYN cookie写成了FIN cookie,那么在更正的时候怎么也不至于改为RST cookie吧!说说什么是FIN/RST cookie吧,它其实是我个人的一些想法,并不是什么标准化的东西,如果RFC上能找到这个建议,我也不用写这篇文章了,这个cookie是仿造SYN cookie做的,为TCP的断开提供一种认证机制,即不能像往常那样简单地伪造一个窗口内的FIN或者RST就可以断开一个健康的TCP连接,以及更难得的,旧的FIN把新的连接断开等。我认为TCP的这个脆弱性原本不是它本身所造成的,而是TCP/IP网络的设计原则造成的,武功再高也怕菜刀,再健康的人也抵不住暴力。正确的做法应该是为TCP营造一个良好的治安环境,而这正是网络安全的范畴。然而伪造的FIN/RST 看起来或者真实地,它就是属于一个它将要end掉的健康的TCP连接的,这就不是网络安全的范畴了,正所谓清官难断家务事啊!因此,正是TCP本身的机制造成了伪造如此简单,事实上应该说,TCP本身就不是健康的,或者说不是健壮的。一个健康的TCP连接,听起来更像是一个没有并发症的癌症病人一样。

原因

TCP的控制流缺乏认证机制,仅仅靠4-tuple将TCP数据包关联到一个连接,然后靠序列号和窗口机制判断是否接收并处理。TCP对待控制流和数据流的方式是一致的。TCP重要的控制流包括初始化连接的3次握手和断开连接的4次挥手。这种缺乏认证的带内控制通道导致一个TCP连接特别容易被劫持,劫持者只要满足几个很容易满足的要求即可,包括序列号落在窗口内,4-tuple符合等,而随着网络越来越好,速度越来越快,TCP的窗口会越来越大,导致实现TCP劫持会越来越容易。

数据通道和控制通道

由于TCP实现的是带内控制,就是说数据和控制都是附着在一个连接的,因此区分数据通道和控制通道的意义不是很大,但是按照网络通信的惯例-严格区分控制平面/数据平面/管理平面,还是把带有特殊TCP标志的TCP数据包看作是控制平面的包,而只有数据和ACK的看作是数据平面的包。

可以想见,数据通道的包我们不必理会,自有上层协议保证其安全性,我们关心的是控制通道的包。也许你会问,如果TCP连接仅仅数据通道被劫持,即攻击者没有劫持任何带有标志位的数据包,仅仅在一边等着,然后瞄准窗口中的某个位置,发送捣乱的数据,这样岂不是真实的端主机就没法发送数据了吗?确实是,但是这不是TCP层面的问题,用户socket发现数据迟迟发不到对端可以自行断开连接,接下来,它可以尝试躲到IP隧道里面...数据通道的安全性总是可以通过上层协议或者动作来保证,比如可以使用SSL连接或者使用VPN隧道等。但是控制通道本身却不能这么做,因为很多的控制数据流是不会展现在上层的。

以RST为例,当TCP栈处理了RST之后,连接就拆除了,留给上层的只是一个错误码,对于FIN来讲也是这样,即便是使用SSL,这些数据包也会使SSL连接断开,总的原因有二:第一,用户的应用直接和传输层接口,没有自己的会话保持机制;第二,即便是socket和会话层接口,其VFS文件操作方式也不足以控制网络这种很难映射到文件的IO类型。

引入一个cookie

一个cookie就像一个柔软的垫片,任何攻击都必须突破这个垫片,而这个垫片的破损就指示了攻击已经发生这件令人遗憾的事,需要做的就是铺好这个垫片,然后在关键操作前检查这个垫片是否被破坏,该技术现在已经用于防止栈溢出,防止半连接Dos攻击,SSL握手协议的Finished消息,关于这些技术就不再多说了。类似的,TCP在处理FIN/RST的时候也可以引入类似的机制,即只有保证这个cookie没有被破坏才继续处理,否则就认为是第三方伪造的攻击包,需要注意的是,该cookie机制的引入并没有对TCP标准协议头进行任何更改,只是增加了一个选项以及在更严格的情形下,增加了一个处于TCP标准状态机转换之上的内部DH状态机。

级别一:不安全的cookie

虽说是不安全的cookie,但是也是可以提供安全保护,最不安全是相对最安全来讲的。该cookie是一个摘要值,计算方法如下:

value=摘要算法(client-ISN, server-ISN, 伪IP头校验和, FIN/RST包的SEQ, FIN包的ACK)

其中,除了最后的两个字段,其它的字段在3次握手之后就保存在TCP连接的协议控制结构体中了,两端的值无疑是一致的,在保存了这些值之后,为该TCP连接的协议控制结构设置一个标志,表明必须认证FIN和RST。在发送FIN的时候,将上述的value保存在option中,传给对端,对端接收后,从本连接的协议控制结构体中取出前三个字段,从该packet中取出后两个字段,按照同样的算法计算value2,比较value和value2,若不同,则不触发FIN/RST序列,若相同,进入标准处理流程。

说一下关于RST的处理,本cookie机制仅仅针对处在ESTABLISHED状态的TCP连接,如果3次握手还没有完成,则对RST包进行正常处理。这就需要在两端同时引入一个timeout机制,在一端的ESTABLISHED状态的TCP连接进程crash掉之后,,在该timeout时限尚未过期时,保存TCP的的两个方向的ISN以及伪IP头的校验和等字段,超时后,进入标准的RST处理模式,重要的是,应该在每收到一个ACK时,重置该定时器,说明对端还活着,起码在RTT时间前还活着。当然,这个超时机制是可以优化的。

说它不安全意思是说,如果嗅探者嗅探到了3次握手的过程,那么攻击照样可以进行。另外,针对摘要算法的伪IP头参数,也有争议,因为它会影响NAT设备的实现,因此这个字段不要也罢,毕竟能猜出两个ISN的几率很小。

级别二:安全的cookie

如果说级别一的cookie是增加了攻击的难度的话,那么级别二的cookie就完全保证了安全,因为它是依靠自身加密机制保证的。诚然,需要协商一个密钥,还需要明确该密钥用来加密什么东西,剩下的问题就是实现问题了。如果说被攻击者嗅探到ISN是一件很可怕的事的话,那么引入一个字段,该字段从来不再网络上明文传输,然后基于该字段做摘要,那不就解决问题了?PPP的CHAP就是基于这种思想设计的。目的很明确,其中一个比较简单的实现还是认证一个value:

value=摘要算法(对端的random, 本端的random, 其它任何你想引入的...)

现在的问题是两个random如何从本端传给对端呢?答案当然是加密传输,密钥是什么?可选方案当然是自动协商,用DH?可以!那么DH协商过程如何和TCP兼容?答案就是将其藏在TCP的option中,和TCP的SYN握手一起发起的,还可以有一个DH过程,或者复杂的其它密钥协商过程,这些过程也可以不和SYN一起发起,甚至可以在任何时候重新发起,TCP标准的3次握手完成之后,上述的额外的协商可以继续,它不会对TCP标准的状态机转换过程有任何影响,只是额外叠加了一个状态机在option里面。事实上,以DH为例,完全可以在SYN和SYN/ACK中2次握手完成。

关于RFC 5925

我的实现当然没有RFC 5925那么完备。要知道设计一个协议或者仅仅一个协议元素是一件很难的事情,你要考虑到的事情不知一面。通常人们喜欢固化一些东西以免除自动化带来的困惑,然而这是不符合Internet设计原则的,固化的东西其适应性是非常有限的,充其量在你自己的环境中性能良好,可以不是一个普遍化的东西。我这里实现的这个FIN/RST cookie就是固化的东西。

关于TCP/IP与进化

传统TCP/IP-肉身必然是沉重的

神一样的存在必然希望摆脱沉重的肉身!然而恰恰是这个沉重的肉身却是一切的基础。崇尚SDN的同时也隐约感觉到网络的君主时代已经快要来临,SDN就像路易十四那样将众人玩弄于股掌。君主时代的专制阻碍着进化的发生,进化是逐步的修补,是带着沉重肉身的痛苦蹒跚。

控制平面必须和数据平面呆在一起,以人体为例,我们的内脏占据了大量的空间,其工作消耗着大量的能量,这个肉身是沉重的,但是就算是最伟大的人也不能奢望一个仅有的大脑存在,如果说思想是精化,那么所有的内脏,包括大脑本身都是沉重的,都是负担,我们要吃饭,要睡觉,要做太多的维护工作,全部都是为了所谓的思想,但是将内脏,大脑独立于思想之外,类似SDN那样却是万万不能的。

然而,控制平面和数据平面的一体化确实限制了单个个体的规模,导致必须铺开来很多个体,通过协作的方式扩大规模。以摩天大楼为例,真正的摩天大楼是不可能存在的,因为电梯会占据大量的空间。楼越高,电梯空间越大,因为每增加一层的高度,底层就要扩大一定的电梯空间,最终的摩天大楼会变成金字塔型的,低下无限大,顶上无限高。也许自然界的高山最能说明问题,群山总是能带来美感,瘦而不豺,高而不愣。

本文永久更新链接地址:

相关内容