ginuerzh / gost

GO Simple Tunnel - a simple tunnel written in golang
MIT License
15.85k stars 2.48k forks source link

gost 的 Shadowsocks 服务端实现缺陷可能导致主动探测 #683

Closed DuckSoft closed 1 year ago

DuckSoft commented 3 years ago

注意:并非协议设计缺陷,仅影响 gost Shadowsocks,其他主要的 Shadowsocks 实现都可以抵御此探测,此缺陷可以通过软件更新比较简单地修复,请勿恐慌。

方法

在到 gost Shadowsocks 服务端的 TCP 连接上逐字节发送随机数。如服务端采用 AES-256-GCM 或 CHACHA20-IETF-POLY1305,则连接会在服务端接收到第 50 字节时断开,对于其他 加密,连接会在其他的固定位置断开。在使用此方法测试其他服务(HTTP,TLS,SOCKS5,SSH,RDP,Minecraft,RTMP)时均未见在数十字节左右的固定值中断的现象。

分析

AEAD 加密

Shadowsocks AEAD 加密协议结构如下

[salt][chunk][chunk]...

其中 chunk 有以下结构

[encrypted_length][encrypted_length_tag][encrypted_data][encrypted_data_tag]

Shadowsocks (以及同类协议)对数据完整性的验证完全依靠 AEAD 解密过程保证——只有在第一次 AEAD 解密后,我们才能知道数据流是否伪造。如果一个服务端在发现解密失败后直接关闭连接,例如 shadowsocks-rust ,则可以使用 https://github.com/v2ray/v2ray-core/issues/2523 中提到的逐字节发送数据包并观测连接何时关闭的手段测量验证过程至少需要多少字节。

对于 Shadowsocks AEAD,完成验证需要接收 salt ,第一个 chunkencrypted_lengthencrypted_length_tag 。其中 saltencrypted_length_tag 是与加密相关的常数, encrypted_length 长度为 2。则对现有 AEAD 加密,有下表,可见对于现有加密,有 34/42/50/82 这四种可能的验证所需长度,抛开不常用加密,实际上只有 50(和 34)。

加密 salt 长度 tag 长度 完成验证所需长度
XChacha20-Poly1305 32 16 32+16+2=50
Chacha20-Poly1305 32 16 32+16+2=50
AES-256-GCM 32 16 32+16+2=50
AES-192-GCM 24 16 24+16+2=42
AES-128-GCM 16 16 16+16+2=34
AES-256-PMAC-SIV 64 16 64+16+2=82
AES-128-PMAC-SIV 32 16 32+16+2=50

显然随机数不可能通过 AEAD 解密过程验证,服务端会在接收到完成验证所需长度的数据后关闭连接。因此若多次向某个开放的 TCP 端口逐字节输入随机数,连接始终在输入50字节后断开,则可怀疑此端口是 Shadowsocks 端口。

流加密

Shadowsocks 流加密协议结构如下

[iv][encrypted_data]

其中 encrypted_data 有以下结构

[atyp][addr][port][data]

使用错误的密码时,atyp 极有可能是不合理的数值,导致服务端无法解析地址。对于流加密,在解密 atyp 后,即可以较低的假阳性率(3/256)知道数据流是否伪造。按照相同的原理,连接会在 8/12/16 字节后断开。

无加密/置换密码/rc4加密

这三种加密不使用 iv ,可以视作流加密中 iv 长度为0的特例。按照相同的原理,连接在接收到第一字节时(有 (256-3)/256 的概率)断开。

防御

按照 https://censorbib.nymity.ch/#Frolov2020a 的结论,最稳妥的防御是从不主动关闭异常的连接。我们不可能改变完成验证所需长度,但从不主动关闭连接应足以阻止此类依赖服务端响应的主动探测。

Credits

https://github.com/Qv2ray https://github.com/studentmain

Related

https://github.com/txthinking/brook/issues/708 https://github.com/shadowsocks/shadowsocks-rust/issues/292 https://github.com/nadoo/glider/issues/180

proxy666-dev commented 3 years ago

@DuckSoft 由 https://docs.ginuerzh.xyz/gost/ss/ 可知gost的shadowsocks由 https://github.com/shadowsocks/shadowsocks-go 库和 https://github.com/shadowsocks/go-shadowsocks2 库实现。

proxy666-dev commented 3 years ago

@DuckSoft 由 https://docs.ginuerzh.xyz/gost/ss/ 可知gost的shadowsocks由 https://github.com/shadowsocks/shadowsocks-go 库和 https://github.com/shadowsocks/go-shadowsocks2 库实现。

@DuckSoft 上面这两个库有没有你所说的缺陷?

proxy666-dev commented 3 years ago

@ginuerzh 来看看。

DuckSoft commented 3 years ago

@DuckSoft 由 https://docs.ginuerzh.xyz/gost/ss/ 可知gost的shadowsocks由 https://github.com/shadowsocks/shadowsocks-go 库和 https://github.com/shadowsocks/go-shadowsocks2 库实现。

@DuckSoft 上面这两个库有没有你所说的缺陷?

在最新的 release 中(2.11.1),这个问题仍然存在。 复现方法非常简单,本地开一个 Shadowsocks 服务器,然后用 nc 连上去,按住你的回车键。 如果连接提前中断,说明受到影响。