作者: BG7YWL
公众号: 数智安全研究院
前言
Shadowsocks(简称SS)是一款科学上网工具,基于Socks5代理方式的加密传输协议,但是近年来随着GFW墙的日益增高,一些 Shadowsocks 流量已经可以被很好的识别出来。
虽然“协议可以被识别”已经众所周知,但我们依旧认为,Shadowsocks 的加密做的不错,中间人应该破解不出明文信息。
然而,近期一份来自于奇虎 360 核心安全团队于披露的论文显示:Shadowsocks 的 steam 加密存在漏洞,导致数据包头部可被修改。攻击者可以利用修改过后的数据包进行「重定向」,从而进行 MITM 攻击。
目前受影响的包括:shadowsocks-py, shadowsocoks-go, shadowsocoks-nodejs.
socks5 协议基础
虽然 Shadowsocks 使用的底层协议是 SOCKS5,但对于本文而言,底层的 SOCKS5 并不是重点,我们只需要关注 Shadowsocks 的客户端与服务器之间是如何传输数据的。
根据官方文档所说,客户端向服务器发送的数据,一开始是流密码的 IV(也就是说,IV 由客户端生成,并直接扔进数据包中),之后就是一段加密数据,它的明文格式是这样的:[目标地址][数据]
其中,类型是 1 字节的枚举值:
-
0x01:主机名是 IPv4 地址;
-
0x03:主机名是变长字符串,首字节表示长度(最大 255),后面是数据;
-
0x04:主机名是 IPv6 地址。
一次代理的过程如下:
- 客户端将这些数据加密后发到服务器;
- 服务器收到后将其解密,会得到 [1 字节类型][主机名][2 字节端口][数据];
- 服务器会将数据部分直接发送给 主机名:端口;
- 服务器将主机返回的数据直接使用同样的算法加密(如果加密算法用了流密码,则会生成并使用一个新的 IV,并将其放在包的最前面),发送给客户端;
- 客户端解密后即可得到主机返回的数据。
论文中提到,如果攻击者抓到了一个 Shadowsocks 服务器返回的包,并且已知数据部分的开头七个字节,那么有可能在不知道密码的情况下,利用 Shadowsocks 服务器来解出包的绝大部分内容(最多损失 16 字节)。
作者的思路
假设有一台 Shadowsocks 服务器,攻击者通过嗅探或其它方式抓到了这个 Shadowsocks 服务器返回的一个包。
为了知道明文内容,攻击者要么暴力破解密码(几乎不可行),要么想办法利用这台 Shadowsocks 服务器帮忙解密。
作者选择了后者,即想办法把这个包变成客户端发的包,让服务器解密后代理到自己指定的服务器,这被称为重定向攻击。
CFB 模式
前面说了,Shadowsocks 客户端的发包格式是 :[1 字节类型][主机名][2 字节端口][数据]。如果攻击者可以利用加密算法的缺陷来篡改明文数据,就可以把主机名改成攻击者的服务器地址,Shadowsocks 服务器就会以为客户端想访问攻击者的服务器,于是就把解密后的包中的数据部分发了过去。
先考虑如何篡改数据。假设这台 Shadowsocks 服务器的加密算法使用的是 AES-256-CFB。
CFB模式的全称是 Cipher FeedBack模式(密文反馈模式),在CFB模式中前一密文分组会被送到密码算法的输入端,进行下一分组的加密。
加密的流程如下图所示:
相反的解密流程如下所示:
只看解密流程,如果我们知道了 明文分组1 和 密文分组1,接下来就可以通过构造一个假的密文分组1,让 Shadowsocks 服务器解密来伪造一个任意的 明文分组1。
'明文分组1' xor '密文分组1' = 'enc_iv''假密文分组1' xor 'enc_iv' = '假明文分组1''enc_iv' xor' 假明文分组1' = '假密文分组1'
通过这样的方式控制 假密文分组1 就可以构造任意的 假明文分组1 了。
漏洞利用过程
通过上面协议的分析,可以得出 sslocal 发送给 ssserver 的数据格式为:
随机IV + encrypt([ 1-byte type][variable-length host][2-byte port][payload])
ssserver 发送给 sslocal 的数据格式为:
随机IV + encrypt([payload])
如果拿到 ssserver 发送给 sslocal 的数据,使用常规的非暴力手段是无法解密的,但是如果我们知道此数据的前7个字节,那么就可以利用CFB明文伪造攻击将 假明文分组1 的前7个字节伪造为 [ 1-byte type][variable-length host][2-byte port],然后把此数据包做为 sslocal 发送给 ssserver 的数据,发给 ssserver。
因为数据 [ 1-byte type][variable-length host][2-byte port]的内容被我们控制,所以将目标地址修改为我们自己的服务器,然后 ssserver 就会把解密完的数据发送到我们自己的服务器上,工作过程如下所示:
ss-local(fake one) <--[encrypted]--> ss-remote <---> target(controlled)
那关键问题是怎么知道加密数据的前7个字节的明文呢?论文中提供了一种方法,如果用户使用 Shadowsocks 进行 http 通信,那么响应的前7个字节是HTTP/1,我们可以利用这7个字节来解密整个数据包。
直接用 LeadroyaL 写好的 exp 进行测试 https://github.com/LeadroyaL/ss-redirect-vuln-exp。由于攻击者修改了 密文分组1 ,而 密文分组1 在 CFB 模式中又用来解密 明文分组2 ,因此收到的 明文分组2 这 16 个字节是乱码。
攻击者最终可以还原出 明文分组2 以外的所有数据。论文中的命令行截图也说明了这点,获取到的数据的第一个字节是之前包的明文的第 8 个字节(前 7 个是 HTTP/1.),然后有 9 个字节是正确的,之后 16 个字节是乱码,再之后是完全正确的。
防御措施
-
禁用 shadowsocks-py、shadowsocks-go、go-shadowsocks2、shadowsocks-nodejs
-
只用 shadowsocks-libev,并且只使用 AEAD 加密
原因如下:shadowsocks-libev 的实现很久之前就已经禁止了 IV 重用,可以在一定程度上防止这种攻击;只要加密算法带有 AEAD 特性,那么数据就无法被篡改,本文的攻击方式也是无效的。
参考链接
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1134/