Talos Vulnerability Report

TALOS-2023-1743

SoftEther VPN vpnserver ConnectionAccept() denial-of-service vulnerability

October 12, 2023
CVE Number

CVE-2023-25774

SUMMARY

A denial-of-service vulnerability exists in the vpnserver ConnectionAccept() functionality of SoftEther VPN 5.02. A set of specially crafted network connections can lead to denial of service. An attacker can send a sequence of malicious packets to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

SoftEther VPN 5.01.9674
SoftEther VPN 5.02

PRODUCT URLS

SoftEther VPN - https://www.softether.org/

CVSSv3 SCORE

7.5 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CWE

CWE-400 - Uncontrolled Resource Consumption

DETAILS

SoftEther is a multi-platform VPN project that provides both server and client code to connect over a variety of VPN protocols, including Wireguard, PPTP, SSTP, L2TP, etc. SoftEther has a variety of features for both enterprise and personal use, and enables Nat Traversal out-of-the-box for remote-access setups behind firewalls.

To various ends, SoftEther VPN server accepts and parses HTTP connections by default on all of its configured TCP ports. Out of the box, this means TCP ports 443, 992, 1194 and 5555. Each port runs the ListenerTCPMainLoop function, which quickly hits the following code:

// TCP listener main loop
void ListenerTCPMainLoop(LISTENER *r)
{
    SOCK *new_sock;
    SOCK *s;
    // Validate arguments
    if (r == NULL)
    {
        return;
    }

    Debug("ListenerTCPMainLoop Starts.\n");
    r->Status = LISTENER_STATUS_TRYING;

    while (true)
    {
        bool first_failed = true;
        Debug("Status = LISTENER_STATUS_TRYING\n");
        r->Status = LISTENER_STATUS_TRYING;

        // Try to Listen
        while (true)
        {
            UINT interval;
            // Stop flag inspection
    // [...]

            // Accept loop
        while (true)
        {
            // Accept
            Debug("Accept()\n");
            new_sock = Accept(s);           // [1]
            if (new_sock != NULL)
            {
                // Accept success
                Debug("Accepted.\n");
                TCPAccepted(r, new_sock);   // [2]
                ReleaseSock(new_sock);
            }
            else
            {
    STOP:
                Debug("Accept Canceled.\n");
                // Failed to accept (socket is destroyed)
                // Close the listening socket
                Disconnect(s);
                ReleaseSock(s);
                s = NULL;

   // [...] 

It suffices to say that each of these listeners continuously listens for new TCP connections, accepts the connections [1] and then spawns a new thread inside of [2] that eventually leads to the ConnectionAccept function:

// Function that accepts a new connection
void ConnectionAccept(CONNECTION *c)
{
    SOCK *s;
    X *x;
    K *k;
    LIST *chain;
    char tmp[128];
    UINT initial_timeout = CONNECTING_TIMEOUT;  // (15 * 1000)
    UCHAR ctoken_hash[SHA1_SIZE];

    // Validate arguments
    if (c == NULL)
    {
        return;
    }

    Zero(ctoken_hash, sizeof(ctoken_hash));

    // Get a socket
    s = c->FirstSock;
    AddRef(s->ref);

    Dec(c->Cedar->AcceptingSockets);

    IPToStr(tmp, sizeof(tmp), &s->RemoteIP);

    SLog(c->Cedar, "LS_CONNECTION_START_1", tmp, s->RemoteHostname, (IS_SPECIAL_PORT(s->RemotePort) ? 0 : s->RemotePort), c->Name);

    // Timeout setting
    initial_timeout += GetMachineRand() % (CONNECTING_TIMEOUT / 2);  // [3] 
    SetTimeout(s, initial_timeout);

    // Handle third-party protocols
    if (s->IsReverseAcceptedSocket == false && s->Type == SOCK_TCP)
    {
        if (c->Cedar != NULL && c->Cedar->Server != NULL)
        {
            PROTO *proto = c->Cedar->Server->Proto;
            if (proto && ProtoHandleConnection(proto, s, NULL) == true) // [4] // => wireguard or OpenVPN
            {
                c->Type = CONNECTION_TYPE_OTHER;
                goto FINAL;
            }
        }
    }

    // Specify the encryption algorithm
    Lock(c->Cedar->lock);                 // [5]
    {
        if (c->Cedar->CipherList != NULL)
        {
            SetWantToUseCipher(s, c->Cedar->CipherList);
        }

        x = CloneX(c->Cedar->ServerX);
        k = CloneK(c->Cedar->ServerK);
        chain = CloneXList(c->Cedar->ServerChain);
    }
    Unlock(c->Cedar->lock);

Assuming that our socket is not sending Wireguard or OpenVPN traffic [4] (which is checked by peeking at bytes), the connection is treated as an HTTPS connection. The c->Cedar->lock is set at [5], and then some non-trivial functions are called within the code block. Important to note, the Cedar object is shared between all threads within the SoftEtherVPN server, and as such, the c->Cedar->lock is also shared between all threads. Thus, if we have a program repeatedly making SSL connections to any of the TCP ports that the vpnserver is configured for, more and more threads start contesting for the c->Cedar->lock because the code starting at [5] runs much slower than the ListenerTCPMainLoop can spawn new threads. This results in an ever-increasing number of threads waiting for the lock, the Server thread dying and being respawned and a killing of all existing VPN connections.

While it is worth noting that each of our TCP sockets have a timeout that is set at [3], in practice, the timeout ends up being anywhere from 15 to 22 seconds, depending on the hostname of the machine the server is running on. If we hit all of the four default configured TCP ports from a single VM, we can kill the server within a couple minutes. Once the server reaches around 32657 threads, memory gets sparse and an alloc causes an Abort() to be called. While the SoftEther vpnserver network service will restart (as it runs as a service), due to the speed at which we can continuously kill it, we believe this can be used for performing a denial of service.

Crash Information

<(^.^)>#bt
#0  futex_wait (private=0, expected=2, futex_word=0x5616139cd770) at ../sysdeps/nptl/futex-internal.h:146
#1  __GI___lll_lock_wait (futex=futex@entry=0x5616139cd770, private=0) at ./nptl/lowlevellock.c:49
#2  0x00007fda0bc98082 in lll_mutex_lock_optimized (mutex=0x5616139cd770) at ./nptl/pthread_mutex_lock.c:48
#3  ___pthread_mutex_lock (mutex=0x5616139cd770) at ./nptl/pthread_mutex_lock.c:93
#4  0x00007fda0bed55cb in UnixLock (lock=0x5616139cd750) at /softether/SoftEtherVPN_orig/src/Mayaqua/Unix.c:1858
#5  0x00007fda0bfeb016 in ConnectionAccept (c=c@entry=0x7fd9f4166b30) at /softether/SoftEtherVPN_orig/src/Cedar/Connection.c:3036
#6  0x00007fda0c008142 in TCPAcceptedThread (param=<optimized out>, t=<optimized out>) at /softether/SoftEtherVPN_orig/src/Cedar/Listener.c:181
#7  TCPAcceptedThread (t=<optimized out>, param=<optimized out>) at /softether/SoftEtherVPN_orig/src/Cedar/Listener.c:140
#8  0x00007fda0be9943d in ThreadPoolProc (param=0x7fd9ba976270, t=0x7fd9ba9807c0) at /softether/SoftEtherVPN_orig/src/Mayaqua/Kernel.c:872
#9  ThreadPoolProc (t=0x7fd9ba9807c0, param=0x7fd9ba976270) at /softether/SoftEtherVPN_orig/src/Mayaqua/Kernel.c:827
#10 0x00007fda0bed57d1 in UnixDefaultThreadProc (param=0x7fd9ba980650) at /softether/SoftEtherVPN_orig/src/Mayaqua/Unix.c:1594
#11 0x00007fda0bc94b43 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#12 0x00007fda0bd26a00 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

   
<(^.^)>#bt
#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=140184485450624) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=140184485450624) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=140184485450624, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3  0x00007f81e5c42476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007f81e5c287f3 in __GI_abort () at ./stdlib/abort.c:79
#5  0x00007f81e5fc043e in AbortExit () at /softether/SoftEtherVPN_orig/src/Mayaqua/Kernel.c:2086
#6  0x00007f81e5fd7a42 in NewThreadInternal (thread_proc=thread_proc@entry=0x7f81e5fd53b0 <ThreadPoolProc>, param=param@entry=0x7f81344f4c00) at /softether/SoftEtherVPN_orig/src/Mayaqua/Kernel.c:1198
#7  0x00007f81e5fd7bb2 in NewThreadNamed (thread_proc=thread_proc@entry=0x7f81e5fc51d0 <DnsResolverReverse>, param=param@entry=0x7f81344f7a10, name=name@entry=0x7f81e601758a "DnsResolverReverse") at /softether/SoftEtherVPN_orig/src/Mayaqua/Kernel.c:997
#8  0x00007f81e5fc4eae in DnsResolveReverse (dst=dst@entry=0x7f7f3e7545e0 "", size=size@entry=512, ip=ip@entry=0x7f7f51850294, timeout=500, timeout@entry=0, cancel_flag=cancel_flag@entry=0x0) at /softether/SoftEtherVPN_orig/src/Mayaqua/DNS.c:586
#9  0x00007f81e5fee5a9 in GetHostName (hostname=0x7f7f3e7545e0 "", size=512, ip=0x7f7f51850294) at /softether/SoftEtherVPN_orig/src/Mayaqua/Network.c:15412
#10 0x00007f81e5fee6a5 in AcceptInitEx (s=0x7f7f51850140, no_lookup_hostname=<optimized out>) at /softether/SoftEtherVPN_orig/src/Mayaqua/Network.c:12755
#11 0x00007f81e61440fe in TCPAcceptedThread (param=<optimized out>, t=<optimized out>) at /softether/SoftEtherVPN_orig/src/Cedar/Listener.c:172
#12 TCPAcceptedThread (t=<optimized out>, param=<optimized out>) at /softether/SoftEtherVPN_orig/src/Cedar/Listener.c:140
#13 0x00007f81e5fd543d in ThreadPoolProc (param=0x7f81444ee2d0, t=0x7f812d624840) at /softether/SoftEtherVPN_orig/src/Mayaqua/Kernel.c:872
#14 ThreadPoolProc (t=0x7f812d624840, param=0x7f81444ee2d0) at /softether/SoftEtherVPN_orig/src/Mayaqua/Kernel.c:827
#15 0x00007f81e60117d1 in UnixDefaultThreadProc (param=0x7f812d6264e0) at /softether/SoftEtherVPN_orig/src/Mayaqua/Unix.c:1594
#16 0x00007f81e5c94b43 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#17 0x00007f81e5d26a00 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
VENDOR RESPONSE

Vendor pull request on Github: https://github.com/SoftEtherVPN/SoftEtherVPN/pull/1911

TIMELINE

2023-04-26 - Vendor Disclosure
2023-09-28 - Vendor Patch Release
2023-10-12 - Public Release

Credit

Discovered by Lilith >_> of Cisco Talos.