Talos Vulnerability Report

TALOS-2019-0942

Shadowsocks-libev ss-server Stream Cipher Information Disclosure Vulnerability

December 3, 2019
CVE Number

CVE-2019-5152

Summary

An exploitable information disclosure vulnerability exists in the network packet handling functionality of Shadowsocks-libev 3.3.2. When utilizing a Stream Cipher, a specially crafted set of network packets can cause an outbound connection from the server, resulting in information disclosure. An attacker can send arbitrary packets to trigger this vulnerability.

Tested Versions

Shadowsocks-libev 3.3.2

Product URLs

https://shadowsocks.org/en/index.html

CVSSv3 Score

7.4 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N

CWE

CWE-306: Missing Authentication for Critical Function

Details

Shadowsocks is a multi-platform and easy to use socks proxy with a focus on censorship evasion, thus highly popular in countries with restrictive internet policies. For the purposes of this advisory, we will be focusing on Shadowsocks-libev, a pure C implementation for lower end and embedded devices.

For a basic usecase and overview of ShadowSocks-libev, a setup like the following is required:

 ______________________               nnnnnnnnnnnnnnnnnn             __________________
|              \ ss-   |             c                  3           |                  |
|  laptop or   \ local |             c  Untrusted       3           | Remote Server    |
|  home network\       | ------------c  Internet        3-----------| Running          |
|              \       |             c                  3     [-_-]^| ss-server        |
|______________\_______|             c__________________3           |__________________|

A given laptop or home network will have an ss-local instance which listens on a given port and then forwards all traffic out via a specific encryption method specified in a configuration file or command-line argument. Both the ss-local instance and ss-server must have the same parameters in order for the setup to work, and an example configuration file might look like:

{
    "server":"192.168.149.144",
    "server_port":9999,
    "local_address": "127.0.0.1",
    "local_port":1080,
    "password":"sample_password",
    "user":"sample_user",
    "timeout":600,
    "method":"aes-256-cfb"
}

To get more specific into what attack surface is being examined (since there’s 2 ports for both ss-local and ss-remote), the [-_-]^ above designates the attack surface, the ss-server port that is accessible from the internet. Ideally, when a user has configured their browser of choice to use the Shadowsocks proxy, ss-local will read in the http or https request, encrypt it, and then send it off to the ss-server instance. The ss-server instance will decrypt the packet and then send it off to wherever it needs to go, which is specified in the message as either an ipv4, ipv6, or hostname. As per the generous comments in src/server.c, the first tcp request is as such:

    /*
     * Shadowsocks TCP Relay Header:
     *
     *    +------+----------+----------+
     *    | ATYP | DST.ADDR | DST.PORT |
     *    +------+----------+----------+
     *    |  1   | Variable |    2     |
     *    +------+----------+----------+
     *
     */

The ATYP & 0xF determines how the DST.ADDR field is parsed, as mentioned before, as ipv4 (ATYP & 0xF == 1), ipv6 (ATYP & 0xF == 4), or hostname (ATYP & 0xF == 3). Assuming that the request is long enough for the given ipv4 or ipv6 address, the server will connect out to the destination and will proceed to decrypt and directly forward all subsequent traffic.
In the case of the DST.ADDR being a hostname, a few more steps are taken: first, the hostname must pass validation tests. Assuming the hostname is considered valid, a DNS request will then be made by the ss-server and will then connect/forward as normal. We will come back to this after a short overview of the encryption and decryption schema.

Depending on the cipher mode chosen, encryption and decryption can be done many ways, but the most important decision is whether to use a stream cipher or an AEAD cipher. Normal stream ciphers only provide confidentiality and no sort of authentication or integrity checks, unlike the AEAD ciphers which provide all three. As mentioned in the documentation, it is recommended that users use AEAD ciphers whenever possible: https://shadowsocks.org/en/spec/AEAD-Ciphers.html, and this advisory will hopefully demonstrate another reason why.

As part of the required configuration, either a password or a base64-encoded key is provided by the user. For base64, the decoded key must be at least the needed key length, however this is not the case for the password. Regardless, both methods then do key_length iterations of md5_hmac(Prev_hash, password) which generates the set of bytes used during the encryption and decryption process. Once this has all been initialized, the server will start listening for connections and parsing the input. Normally, a ShadowSocks client will prepend its encrypted message with the 16-byte I.V. used to encrypt, which allows the ss-server to decrypt, and as mentioned before, since we are talking about Stream Ciphers and not AEAD Ciphers, there’s no authentication or integrity checks, only confidentiality. As such, if any packet of sufficient length is sent from an unknown source to ss-server, it will attempt to decrypt it and parse whatever is inside (with the exception being packets with duplicate I.V.s, of which the server can hold 100000 entries).

As mentioned earlier in the advisory, (assuming hostname validation passes) ss-server will either connect to an ipv4 address, a hostname, or an ipv6 address after receiving the first packet (as a socks proxy should). However, even if an attacker does something as simple as sending an increasing I.V. from zero and then completely random data after, ss-server will parse and act upon it, connecting to or looking up a given host. Thus, if an attacker with the ability to view network traffic coming from the ss-server, via a mitm or access to network infrastructure, sends random data to ss-server, the server effectively reveals itself as a Shadowsocks server with both random connections and malformed DNS lookups.
While this fingerprinting is undesirable in the first place, even better for the attacker, if attackers manage to accept the outbound connection from the ss-server (e.g. via mitm) they now both send encrypted data and view the ‘decrypted’ version. Since they also know the first packet sent, along with the I.V., the encrypted and decrypted versions of the traffic, they can then attempt to offline-bruteforce the configured password or base64 key of the server.
Since it’s assumed that attackers can perform a mitm attack, and they also possess the encryption key, they can subsequently alter any encrypted traffic to ss_server from any given socks client (assuming the whole conversation is seen).

Mitigation

  • Use an AEAD Cipher.

Timeline

2019-10-31 - Vendor Disclosure
2019-11-22 - Vendor advised issue is “won’t solve”
2019-12-03 - Public Release

Credit

Discovered by Lilith [._.] of Cisco Talos.