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

Talos Security Advisory for Shadowsocks-libev 3.3.2 (TALOS-2019-0956) #2536

Closed
CiscoTalos opened this issue Nov 8, 2019 · 5 comments
Closed

Talos Security Advisory for Shadowsocks-libev 3.3.2 (TALOS-2019-0956) #2536

CiscoTalos opened this issue Nov 8, 2019 · 5 comments

Comments

@CiscoTalos
Copy link

@CiscoTalos CiscoTalos commented Nov 8, 2019

TALOS-2019-0956
CVE-2019-5163

Shadowsocks-libev ss-server UdpRelay Denial-of-Service Vulnerability

Summary

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.

Tested Versions

Shadowsocks-libev 3.3.2

Product URLs

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

CVSSv3 Score

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

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.

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
}

Mitigation

  • Use an AEAD Cipher.

Timeline

2019-11-08 - Vendor Disclosure
YYYY-MM-DD - Public Release

@madeye madeye closed this in 1ba753a Nov 8, 2019
@madeye

This comment has been minimized.

Copy link

@madeye madeye commented Nov 8, 2019

This is a bug that IPv6 local address is not initialized when binding an IPv6 socket.

Thanks for catching it!

@CiscoTalos

This comment has been minimized.

Copy link
Author

@CiscoTalos CiscoTalos commented Nov 12, 2019

You're welcome. We will make not of the fix on our end via public disclosure.

@CiscoTalos

This comment has been minimized.

Copy link
Author

@CiscoTalos CiscoTalos commented Nov 20, 2019

Based on the closing of this ticket and comment acknowledging the fix, we will consider this issue resolved on your end and prepare for public disclosure.

@carnil

This comment has been minimized.

Copy link

@carnil carnil commented Dec 2, 2019

This appear to be CVE-2019-5163.

@PantherJohn

This comment has been minimized.

Copy link

@PantherJohn PantherJohn commented Jan 10, 2020

@madeye @CiscoTalos What if I don't want to use aead ciphers? Obviously a better solution is to close the file descriptor (and better report_addr) instead of exiting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.