I'm learning more about the Shadowsocks protocol. I read about why
certain protocol design decisions were made, in response to the
possibility of active probing. This isn't my original research; it's
mostly a restatement of this article by Blessing Studio (it's in Chinese
so I had to Google Translate it):
and the discussion of SIP004, which added AEAD encryption:
The summary is:
* the Shadowsocks protocol, despite having a per-server password, was
vulnerable to an active-probing attack using ciphertext malleability.
* developers deployed an incomplete fix called OTA (One-Time Auth), that
still permitted a more subtle form of active probing.
* now the protocol uses authenticated encryption (AEAD) that prevents
the active-probing attacks.
Originally, the Shadowsocks handshake was encrypted (using a pre-shared
key and random initialization vector), but not authenticated. The first
packet sent by the client contains a specification of the destination,
similar to SOCKS:
1 byte address type (0x01=IPv4, 0x03=hostname, 0x04=IPv6)
2 bytes destport
When the server receives a connection, it reads the first byte (the
address type). If the address type is 0x01, 0x03, or 0x04, then the
server resolves the destaddr and destport and tries to make a
connection. Otherwise, it disconnects the client immediately.
Because the ciphertext is not authenticated, it's possible to do active
probing via a chosen ciphertext attack. Make 256 connections to the
server, each time sending a single byte, cycling through all 256
possible values. The use of malleable stream ciphers means that after
decryption, the first plaintext byte of the plaintext also cycles
through all 256 possible values. If you've found a Shadowsocks server,
then in 253 out of 256 cases, you will get disconnected immediately, and
in 3 out of 256 cases, the server will pause, waiting to read more from
the client. I understand that this attack was discovered by @breakwa11.
A partial mitigation was to heuristically detect attack-like behavior,
and disconnect after a random delay.
A further mitigation was the introduction of OTA (One-Time Auth). OTA
adds a MAC to the handshake message, and breaks the formerly
unstructured client data stream into chunks, each with a 2-byte DATA.LEN
field and a MAC per-chunk. The MACs are computed before encryption
(MAC-then-encrypt). One problem with OTA was that it was optional:
clients could still connect without it, so the old probing attack still
worked. But OTA also has an active-probing vulnerability of its own,
because the per-chunk MACs protect the data but not DATA.LEN--you need
to decrypt DATA.LEN and read that many bytes before you can verify the
MAC. The censor can record a legitimate Shadowsocks session, then replay
the client→server packets. When replaying, the censor tweaks the byte
corresponding to the most significant byte of DATA.LEN, which will tend
to make the decrypted DATA.LEN large, which will cause the server to
continue trying to read from the client, until timeout. (I'm not sure I
have this attack exactly, but that's the gist of it.) I think this flaw
was also discovered by @breakwa11.
This further vulnerability was the motivation to begin using
authenticated encryption, instead of a separate MAC. Information about
Shadowsocks's use of AEAD ciphers is here:
As far as I know, this design prevents active probing, unless the censor
can guess the shared key.
Finally, all these protocol changes were preemptive on the part of the
Shadowsocks developers. I don't know of any evidence that any real
firewall actually did these active-probing attacks. This is in contrast
to Tor, obfs2, obfs3, and some others, for which there is abundant
evidence of active probing.