前言
Shadowsocks(简称SS)是一款科学上网工具,基于Socks5代理方式的加密传输协议,但是近年来随着GFW墙的日益增高,一些 Shadowsocks 流量已经可以被很好的识别出来。
虽然“协议可以被识别”已经众所周知,但我们依旧认为,Shadowsocks 的加密做的不错,中间人应该破解不出明文信息。
然而,近期一份来自于奇虎 360 核心安全团队于披露的论文显示:Shadowsocks 的 steam 加密存在漏洞,导致数据包头部可被修改。攻击者可以利用修改过后的数据包进行「重定向」,从而进行 MITM 攻击。
目前受影响的包括:shadowsocks-py, shadowsocoks-go, shadowsocoks-nodejs.
2socks5 协议基础
根据官方文档所说,客户端向服务器发送的数据,一开始是流密码的 IV(也就是说,IV 由客户端生成,并直接扔进数据包中),之后就是一段加密数据,它的明文格式是这样的:[目标地址][数据]
其中,类型是 1 字节的枚举值:
0x01:主机名是 IPv4 地址;
0x03:主机名是变长字符串,首字节表示长度(最大 255),后面是数据;
0x04:主机名是 IPv6 地址。
一次代理的过程如下:
客户端将这些数据加密后发到服务器; 服务器收到后将其解密,会得到 [1 字节类型][主机名][2 字节端口][数据];
服务器会将数据部分直接发送给 主机名:端口;
服务器将主机返回的数据直接使用同样的算法加密(如果加密算法用了流密码,则会生成并使用一个新的 IV,并将其放在包的最前面),发送给客户端;
客户端解密后即可得到主机返回的数据。
作者的思路是:
为了知道明文内容,攻击者要么暴力破解密码(几乎不可行),要么想办法利用这台 Shadowsocks 服务器帮忙解密。
作者选择了后者,即想办法把这个包变成客户端发的包,让服务器解密后代理到自己指定的服务器,这被称为重定向攻击。
3CFB 模式
先考虑如何篡改数据。假设这台 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 了。
4漏洞利用过程
随机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 特性,那么数据就无法被篡改,本文的攻击方式也是无效的。
5参考
https://github.com/edwardz246003/shadowsocks
https://wonderkun.cc/2020/02/18/shadowsocks的通信原理以及攻击方法分析
https://github.com/LeadroyaL/ss-redirect-vuln-exp
https://jiajunhuang.com/articles/2019_06_06-socks5.md.html