为何 shadowsocks 要弃用一次性验证(OTA)

不学无术 暂无评论

不久前,shadowsocks 爆出安全漏洞,然后 OTA 特性匆匆上马,结果在一次性验证特性推出不久,就推出了 SIP004 草案,彻底弃用了 OTA 。

简单地说就是新的 shadowsocks 使用 AEAD 替换了 OTA 流加密方案。

何为 OTA

OTA(One Time Auth,一次性验证),是之前 shadowsocks 为了增强安全性,抵抗 CCA(Chosen-ciphertext Attack,选择密文攻击)而加入的实验性功能。

OTA 由来

为什么当初要加入 OTA 这个现在看来并不安全的特性,这就不得不提到原版协议所存在的问题。

原版协议的这个漏洞其实早在 2015 年就被 @breakwa11 提出了。当时正值 @clowwindy 被喝茶之际,在 SSR 项目的 issue(已删除) 下闹得沸沸扬扬撕逼不断,过了好一段时间后才开始有正经的技术讨论。

因原问题已经删除,因此参考其内容将其问题整理如下:

原版协议剖析

原版协议的 TCP 握手包(加密后)的格式如下:

+-------+----------+
|  IV   | Payload  |
+-------+----------+
| Fixed | Variable |
+-------+----------+
其中 IV (Initialization Vector, 初始化向量)是使用随机数生成器生成的一个固定长度的输入值。通过引入 IV 能够使相同的铭文和相同的密钥产生不同的密文,让攻击者难以对同一密钥的密文进行破解。

shadowsocks 服务端会用这个 IVPSK(Pre-Shared Key,预共享密钥)(通常是用户设置的密码)来解密 TCP 数据包中的 Payload

解密后的内容格式如下:

+--------------+---------------------+------------------+----------+
| Address Type | Destination Address | Destination Port |   Data   |
+--------------+---------------------+------------------+----------+
|      1       |       Variable      |         2        | Variable |
+--------------+---------------------+------------------+----------+
其中 Address Type 是地址类型,占一个字节,有三个可能的取值:01, 03, 04,分别对应 IPv4, hostname, IPv6 类型的地址。这些都是 SOCKS5 协议中定义的标准,详见 RFC1928

握手完成后 shadowsocks 中继就会工作在 stream 流模式下,后续的所有 TCP 数据包不会再带上 IV,而是使用握手时协商的那个 IV

存在的缺陷

从上述可知,原始 shadowsocks 协议 TCP 握手包中的 IV 字段是 Fixed(定长)的。不同的加密算法 IV 长度不同,对于 rc4-md5aes 系列等常用算法,这个长度是 16 字节。各加密算法的详细信息可以在 Stream-Ciphers - 官方文档 中查看。

而服务端为了判断数据是否有效,会检查数据包中表示地址信息的那个字节,看它是不是上面提到的三个可能取值。如果是,就尝试解析后面的地址和端口进行连接;如果不是,立即断开连接。 正是 shadowsocks 服务器的这个行为使得主动探测成为可能。

主动探测原理

此方法由 @breakwa11 提出,并经测试后证实。

一般来讲,Address Type「表示地址类型的那个字节」是被加密后发送的,所以第三方无法精确的修改它。但是不巧的是,shadowsocks 所有的加密方式都是 stream cipher(流加密),而这种加密方式的特点就是「明文数据流与密钥数据流一一对应」。

通俗地讲,即对应修改了某个位置的密文(根据加密模式的不同,可能影响到后面其他密文块的解密,也可能影响不到,但在这里这个性质并不重要),如果预先知道了明文的模式,虽然无法解密还原出内容,但可以修改密文中的特定字节,起到修改解密后的明文的效果。

根据流加密的这个特性,攻击者就可以通过伪造 TCP 数据包来主动探测 shadowsocks 服务器了。攻击者只要暴力尝试修改加密后的数据包中 IV 之后紧接着的那个字节(如果使用的加密算法 IV 长度为 16 字节,那么就修改第 17 个字节),穷举 2^8 = 256 种可能性,如果被测试的服务器有一种到三种情况下没有立即关闭连接,就可以判断出这台机子的这个端口开放的是 shadowsocks 服务。

防范主动探测

为了解决上述问题,大部分分支都加入了针对此种探测方法的对抗措施,即「随机超时抵抗」(eg. shadowsocks-libev v2.5.5+),不再立即关闭连接,从而降低被探测的风险。

OTA 诞生

OTA 剖析

虽然上述措施可以降低风险,但是并不是解决问题的方式。因此 OTA 闪亮登场。

上述情况下主动探测能够得逞的原因是服务器没有对收到的数据包进行校验,随便一个假冒客户端发来的数据包,不管有没有被恶意篡改过,服务端都会做出同样的反应。

此时 @madeye (目前影梭项目的维护者)提出了 OTA 方法的提案。主要目的就是给原版协议增加了数据包验证特性,用于抵抗主动探测。

开启 OTA 的握手包就会变成下面的格式:

+------+---------------------+------------------+-----------+
| ATYP | Destination Address | Destination Port | HMAC-SHA1 |
+------+---------------------+------------------+-----------+
|  1   |       Variable      |         2        |    10     |
+------+---------------------+------------------+-----------+
新增的 HMAC-SHA1 字段是将出了 DATA 通过 HMAC-SHA1 算法(用 IVPSK 生成)进行校验,并且在数据包的头部 ATYP 中添加了一个标志位勇于指示是否开启了 OTA 验证(ATYP & 0x10 == 0x10)。

握手完成后,接下来的数据包会携带上 DATA.LEN(包长度)和 HMAC-SHA1(校验位)字段,这样服务端即可对数据包进行完整性校验,即可判断数据包是否被篡改。

+----------+-----------+----------+----
| DATA.LEN | HMAC-SHA1 |   DATA   | ...
+----------+-----------+----------+----
|     2    |     10    | Variable | ...
+----------+-----------+----------+----

OTA 缺陷

虽然上述的提案可以防范 CCA 攻击,并且增加了数据校验,解决了数据包被篡改的问题。然而却带来了一个更严重的问题。

还记得上面提到的流加密的特点吗?攻击者可以使用同样的套路修改数据包中的 DATA.LEN 字段,然后通过观察服务器的反应来判断这是否是一个 shadowsocks 服务器。

举个例子:攻击者可以恶意构造一个异常巨大的高位字节密文 DATA.LEN,使得解密后的包长度异常巨大,服务端在解密后就会等待那个实际上不存在的数据传输完毕直至超时。基于此例子只要在发送被篡改的数据包后观察服务器的行为是不是「不会断开连接且至少等待 1 分钟无任何数据包」即可确定该服务器是否开启了 shadowsocks 服务。

可以发现此种主动探测的方案相比于原版的主动探测方法更加简单和隐匿,这也是 OTA 上线后没过多久就匆匆废弃的原因。

何为 AEAD

那么原版协议的缺陷该如何解决呢?这时一个新的方案即可解决此问题。

在通常的密码学应用中,Confidentiality(保密)用加密实现,消息认证用 MAC(Message Authentication Code,消息验证码)实现。

这两种算法的配合方式,引发了很多安全漏洞,过去曾经有 3 种方法:

后来人们发现,E&MMtE 都存在安全问题,所以 2008 年起,逐渐提出了「用一个算法在内部同时实现加密和认证」的 idea,称为 AEAD (Authenticated Encryption with Associated Data,关联数据身份验证的加密协议)。在 AEAD 这种概念里,Cipher & MAC 的模式被 AEAD 算法取代。

AEAD 算法的本质上就是更完善的 stream cipher + authentication,虽然它依然使用的是流加密,但是通过更完善的数据包完整性验证机制杜绝了(目前时间点上的安全)上面所述的可被篡改密文的可能性。

附录

参考链接

本文撰写于一年前,如出现图片失效或有任何问题,请在下方留言。博主看到后将及时修正,谢谢!
回复
回答32+34=