CVE-2019-5163
An exploitable denial-of-service vulnerability exists in the UDPRelay functionality of Shadowsocks-libev 3.3.2. When utilizing a Stream Cipher and a local_address
, arbitrary UDP packets can cause a FATAL error code path and exit. An attacker can send arbitrary UDP packets to trigger this vulnerability.
Shadowsocks-libev 3.3.2
https://shadowsocks.org/en/index.html
5.9 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-306: Missing Authentication for Critical Function
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.
It is very important to note that this particular vulnerability is only exploitable if three conditions are met.
First, ss-server must be using a stream cipher. 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.
The second precondition needed is that the user is using the UDPRelay functionality.
The third precondition is either that the local_address
field is set in the shadowsocks configuration, or that ss-server is run with the -b <ip_address>
flag. This option is used to prevent shadowsocks from sending decrypted traffic out interfaces that it shouldn’t be.
Assuming that these three conditions (udprelay, local_address, stream cipher), an attacker can spam arbitrary UDP data to the ss-server and it will exit on its own:
boop@doop:~/shadowsocks/bin# cat config.server
{
"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"
}
Starting program: ~/shadowsocks/bin/ss-server -u -c config.server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
2019-10-16 11:38:47 INFO: binding to outbound IPv4 addr: 127.0.0.1
2019-10-16 11:38:47 INFO: UDP relay enabled
2019-10-16 11:38:47 INFO: initializing ciphers... aes-256-cfb
2019-10-16 11:38:47 INFO: tcp server listening at 127.0.0.1:9999
2019-10-16 11:38:47 INFO: udp server listening at 127.0.0.1:9999
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:50 ERROR: [udp] invalid header with addr type 171
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:50 ERROR: [udp] sendto_remote: Invalid argument
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:50 ERROR: [udp] unable to resolve
2019-10-16 11:38:51 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] invalid header with addr type 171
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] sendto_remote: Invalid argument
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:38:53 ERROR: [udp] unable to resolve
2019-10-16 11:39:05 ERROR: [udp] unable to resolve
2019-10-16 11:39:06 ERROR: [udp] invalid header with addr type 171
2019-10-16 11:39:06 ERROR: [udp] unable to resolve
2019-10-16 11:39:06 ERROR: [udp] sendto_remote: Invalid argument
2019-10-16 11:39:07 ERROR: [udp] unable to resolve
2019-10-16 11:39:07 ERROR: [udp] unable to resolve
2019-10-16 11:39:07 ERROR: [udp] unable to resolve
2019-10-16 11:39:07 ERROR: [udp] unable to resolve
2019-10-16 11:39:07 ERROR: [udp] unable to resolve
2019-10-16 11:39:07 ERROR: [udp] unable to resolve
2019-10-16 11:39:07 ERROR: [udp] invalid header with addr type 107
2019-10-16 11:39:07 ERROR: [udp] unable to resolve
2019-10-16 11:39:08 ERROR: [udp] invalid header with addr type 7
2019-10-16 11:39:08 ERROR: [udp] unable to resolve
2019-10-16 11:39:08 ERROR: bind_to_addr: Resource temporarily unavailable
2019-10-16 11:39:08 ERROR: [udp] cannot bind remote
[Inferior 1 (process 2531) exited with code 0377]
The code involved in this exit can be found around udprelay.c:380
:
379 if (is_bind_local_addr) {
// remote_sock=0x7
-> 380 if (bind_to_addr(&local_addr_v6, remote_sock) == -1) {
381 ERROR("bind_to_addr");
382 FATAL("[udp] cannot bind remote");
383 return -1;
384 }
385 } else {
If the address given by the udp back matches that of the configuration option (in this case “127.0.0.1”), then all is fine:
<(^_^)> print *storage
$2 = {
ss_family = 0x2,
__ss_padding = "\000\000\177\000\000\001", '\000' <repeats 111 times>,
__ss_align = 0x0
}
But if the socket parameters passed are all 0, the error occurs:
<(^_^)> print *storage
$2 = {
ss_family = 0x0,
__ss_padding = '\000' <repeats 117 times>,
__ss_align = 0x0
}
2019-11-08 - Vendor Disclosure
2019-11-12 - Vendor patched
2019-12-03 - Public Release
Discovered by a member of Cisco Talos.