Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shadowsocks-rust 服务端实现缺陷可能导致主动探测 #292

Closed
ghost opened this issue Aug 25, 2020 · 8 comments
Closed

shadowsocks-rust 服务端实现缺陷可能导致主动探测 #292

ghost opened this issue Aug 25, 2020 · 8 comments

Comments

@ghost
Copy link

ghost commented Aug 25, 2020

shadowsocks-rust 服务端实现缺陷可能导致主动探测

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

方法

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

分析

Shadowsocks AEAD 加密协议结构如下

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

其中 chunk 有以下结构

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

Shadowsocks (以及同类协议)对数据完整性的验证完全依靠 AEAD 解密过程保证——只有在第一次 AEAD 解密后,我们才能知道数据流是否伪造。如果一个服务端在发现解密失败后直接关闭连接,例如 shadowsocks-rust ,则可以使用 v2ray/v2ray-core#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 端口。

防御

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

@Mygod
Copy link
Contributor

Mygod commented Aug 25, 2020

Sounds fair. I think I proposed something similar for shadowsocks-libev or shadowsocks-org sometime back.

@zonyitoo
Copy link
Collaborator

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

But if we don't close invalid connections actively, file descriptors will be exhaused.

@zonyitoo
Copy link
Collaborator

zonyitoo commented Aug 26, 2020

It seems that shadowsocks-libev closes socket only when recv() returns EOF:

https://github.com/shadowsocks/shadowsocks-libev/blob/a727596faaa91a4459daad4cc5a6618c20ce6475/src/server.c#L715-L749

It is quite tricky to avoid TcpStream to be closed by RAII...

@p4gefau1t
Copy link

@zonyitoo 也许可以 spawn 一个新的任务来接管这个 TcpStream

@zonyitoo
Copy link
Collaborator

@zonyitoo 也许可以 spawn 一个新的任务来接管这个 TcpStream

This is one of the ways I was thinking. But that will become very nasty: you have to "catch" this stream everywhere that would throwing Err.

@zonyitoo
Copy link
Collaborator

zonyitoo commented Sep 9, 2020

Made a simple solution for this issue, which will throw away data if server failed to decrypt the first handshake packet. Althrough it is not a perfect solution, but it should help.

I am going to make a release v1.8.17 for this.

@IceCodeNew
Copy link

throw away data if server failed to decrypt the first handshake packet.

Is this what you mean?
https://cbonte.github.io/haproxy-dconv/2.2/configuration.html#4.2-http-request%20silent-drop

@zonyitoo
Copy link
Collaborator

zonyitoo commented Sep 9, 2020

throw away data if server failed to decrypt the first handshake packet.

Is this what you mean?
https://cbonte.github.io/haproxy-dconv/2.2/configuration.html#4.2-http-request%20silent-drop

Sort of.

@ghost ghost closed this as completed Oct 4, 2020
zonyitoo added a commit that referenced this issue Dec 15, 2020
- add #292 reply attack protection
zonyitoo added a commit that referenced this issue Dec 20, 2020
* Refactored and separate library into crates

- shadowsocks: the core feature of shadowsocks
- shadowsocks-service: library for building shadowsocks services
    - dns, http, redir, socks, tunnel
    - load balancer
- shadowsocks-rust: release binaries

fix #347

* unified DnsResolver implementation

* unified local service common parameters into ServiceContext

- ServiceContext is common parameters shared between all local
implementations
- Completely removed https local support

* add #292 reply attack protection

* migrated redir local server

* support customizing outbound socket bind address

* manager outbound socket should accepts connect_opts

* republic local implementations

* socks5 udp server should always listen to client address

* socks4 controlled by local-socks4 feature

* socks4 also obey mode configuration

* socks server tcp cannot be disable. add support of udp-bind-addr parameter

* add udp-bind-addr for customizing udp-relay bind-addr

* local-dns infra, support customizing resolver

* fully implements DNS relay server

* support binding to specific interface on Linux-like platform

* tcp cannot be disabled in socks

* enable local-flow-stat

* fixed windows build

* fixed android specific warnings and compile errors

* allow udp_only mode in socks5

* dns relay listens to both TCP and UDP, mode controls outbound upstreams

* dns relay retries twice if request failed

* doc

* fix DnsClient typo

* fix stream EncryptWriter bug

* allow disable logging output

updated dependencies

* add readme

* refine doc

* remove depending on trust-dns-client

* socks4/4a client

* allow socks5 udp_only mode, fixes compile warning

* create standalone socks5 UDP relay server

- socks5 UDP association full cone (NAT2)

* server udp relay supports full cone (NAT2)

* acl moved to crate root

* redir udp relay support full cone (NAT2)

* standard socks5 udp test must use tcp_and_udp mode

* set server context fields with pub APIs

* udp_max_associations and udp_timeout default value set in Config

* local dns resolver retry with fixed attempts

* max_udp_association keeps unlimited by default

* fixed logging binary name

* pops first exited future result for local and server

* update reverse target index cache

* fix ProxyClientStreamWriteHalf that allows sending empty buffers

ref #232

* remove unused import when socks4 is disabled

* make balancer become a globally shared object

* print plugin exit status

* control local, server, manager services in features
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants