Talos Vulnerability Report

TALOS-2023-1903

Realtek rtl819x Jungle SDK boa getInfo stack-based buffer overflow vulnerability

July 8, 2024
CVE Number

CVE-2023-50330

SUMMARY

A stack-based buffer overflow vulnerability exists in the boa getInfo functionality of Realtek rtl819x Jungle SDK v3.4.11. A specially crafted series of HTTP requests can lead to remote code execution. An attacker can send a series of HTTP requests 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.

LevelOne WBR-6013 RER4_A_v3411b_2T2R_LEV_09_170623
Realtek rtl819x Jungle SDK v3.4.11

PRODUCT URLS

rtl819x Jungle SDK - https://www.realtek.com/en/ WBR-6013 - https://www.level1.com/level1_en/wbr-6013-n300-wireless-router-54069103

CVSSv3 SCORE

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

CWE

CWE-121 - Stack-based Buffer Overflow

DETAILS

The rtl819x Jungle SDK is an SDK for routers. This SDK uses as web server boa.

This Realtek rtl819x Jungle SDK vulnerability was found while researching the Levelone WBR-6013 router. We are going to explain this vulnerability from the perspective of the WBR-6013 router.

The boa’s getInfo function is a template function, used to generate dynamic data in the HTML page. This function is called when the boa server is serving an HTML page that contains <% getInfo(<parameter>); %>. This template call can be found in multiple HTML pages. For instance in wizard.htm the following portion of HTML can be found:

[...]
var wps_disabled=<% getIndex("wscDisable");%>;
var wps_ssid_old='<% getInfo("ssid"); %>';
var wps_mode_old=<% getIndex("wlanMode"); %>;
var wps_config_by_registrar=<% getIndex("wps_by_reg"); %>;
var wps_encrypt_old=<% getIndex("encrypt"); %>;
var wps_wpaCipher_old=<% getIndex("wpaCipher");%>;
var wps_wpa2Cipher_old=<% getIndex("wpa2Cipher");%>;
var wps_psk_old='<% getInfo("pskValue");%>';
var wps_psk_unmask_old='<% getInfo("pskValueUnmask");%>';
var wps_type_old=<% getIndex("networkType");%>;
var wps_enable1x=<% getIndex("enable1X");%>;
var wps_wpa_auth=<% getIndex("wpaAuth");%>;
[...]

In this HTML file, multiple calls for getInfo are performed. This advisory focuses in particular on the one with pskValueUnmask as argument. Following the relevant portion of getInfo:

int getInfo(request *wp, int argc, char **argv)
{
    [...]
   
    memset(tmpStr ,'\0',20);
    name = argv[0];
    if (name == NULL) {
        fprintf(stderr, "Insufficient args\n");
        return -1;
    }

    if ( !strcmp(name, "device_name") ) {
        buffer[0]='\0';
        if ( !apmib_get(MIB_DEVICE_NAME,  (void *)buffer) )
            return -1;
        return req_format_write(wp, "%s", buffer);
    }
    [...]
[1] else if ( !strcmp(name, "pskValueUnmask")) {
        buffer[0]='\0';
        if ( !apmib_get(MIB_WLAN_WPA_PSK,  (void *)buffer) )
            return -1;
[2]     translate_control_code(buffer);
        return req_format_write(wp, buffer);
    }
    [...]
}

This function has a series of if and else if to match the provided parameter with the correct functionality required. <% getInfo("pskValueUnmask"); %>) is used to get the HTML page into the URL-encoded version of the current WPA-PSK value, where WPA-PSK is a string with at most 64 characters. At [1] the parameter of getInfo is compared with pskValueUnmask. If the template function was called with that argument, the WPA-PSK value is fetched from flash. The fetched value then goes through the translate_control_code function, called at [2], that will URL-encode some of the characters in the WPA-PSK string. Following the translate_control_code function:

void translate_control_code(char *buffer)
{
    char tmpBuf[200], *p1 = buffer, *p2 = tmpBuf;


    while (*p1) {
        if (*p1 == '"') {
            memcpy(p2, "&quot;", 6);
            p2 += 6;
        }
        else if (*p1 == '\'') {
            memcpy(p2, "&#39;", 5);
            p2 += 5;
        }
        else if (*p1 == '\\') {
            memcpy(p2, "&#92;", 5);
            p2 += 5;
        }
        else if (*p1 == '<') {
            memcpy(p2, "&#60;", 5);
            p2 += 5;
        }		
        else if (*p1 == '>') {
            memcpy(p2, "&#62;", 5);
            p2 += 5;
        }
        else if (*p1 == '%') {
            memcpy(p2, "&#37;", 5);
            p2 += 5;
        }
        else if (*p1 == '&') {
            memcpy(p2, "&#38;", 5);
            p2 += 5;
        }
        else
            *p2++ = *p1;
        p1++;
    }
    *p2 = '\0';

[3] strcpy(buffer, tmpBuf);
}

This function takes a string buffer. Some of the characters of this string will be URL-encoded. This function uses a stack buffer, tmpBuf, to save the work-in-progress encoding until the process is finished, at which point the tmpBuf is copied back into the buffer variable pointing to the original buffer location. Because tmpBuf is 200 bytes long and the WPA-PSK is at most 64 bytes long, it would seem that no buffer overflow could occur.

Because the translate_control_code expands the provided input, it is possible to cause a buffer overflow with the WPA-PSK string. For example, if in the provided string, the first 40 characters are <, because < is expanded as the 5-character-long sequence &#60;, it would mean that any other character in the provided string is going to be written out of bounds. So, a buffer overflow can occur in the tmpBuf stack buffer in the translate_control_code function.

Crash Information

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$zero: 0x00000000  →  0x00000000
$at  : 0xfffffff8  →  0xfffffff8
$v0  : 0x407fd10c  →  0x2671756f  →  0x2671756f ("&quo"?)
$v1  : 0x407fd204  →  0x00000000  →  0x00000000
$a0  : 0x00000000  →  0x00000000
$a1  : 0x407fce00  →  0x00450000  →  0x66616c73  →  0x66616c73 ("fals"?)
$a2  : 0x00000006  →  0x00000006
$a3  : 0x0044ee1e  →  0x00002623  →  0x00002623
$t0  : 0x0000003b  →  0x0000003b
$t1  : 0x00000000  →  0x00000000
$t2  : 0x8c94ddd6  →  0x8c94ddd6
$t3  : 0x41414141  →  0x41414141 ("AAAA"?)
$t4  : 0x41414141  →  0x41414141 ("AAAA"?)
$t5  : 0x41414141  →  0x41414141 ("AAAA"?)
$t6  : 0x41414141  →  0x41414141 ("AAAA"?)
$t7  : 0x004b0000  →  0x004b0000
$s0  : 0x2671756f  →  0x2671756f ("&quo"?)
$s1  : 0x743b2671  →  0x743b2671 ("t;&q"?)
$s2  : 0x756f743b  →  0x756f743b ("uot;"?)
$s3  : 0x2671756f  →  0x2671756f ("&quo"?)
$s4  : 0x743b4141  →  0x743b4141 ("t;AA"?)
$s5  : 0x41414141  →  0x41414141 ("AAAA"?)
$s6  : 0x41414141  →  0x41414141 ("AAAA"?)
$s7  : 0x41414141  →  0x41414141 ("AAAA"?)
$t8  : 0x004b5650  →  0x3ff44ff0  →  0x00801021  →  0x00801021
$t9  : 0x3ff44ff0  →  0x00801021  →  0x00801021
$k0  : 0x00000000  →  0x00000000
$k1  : 0x00000000  →  0x00000000
$s8  : 0x004b0000  →  0x004b0000
$pc  : 0x41414141  →  0x41414141 ("AAAA"?)
$sp  : 0x407fcdf8  →  0x41414141  →  0x41414141 ("AAAA"?)
$hi  : 0x00000000  →  0x00000000
$lo  : 0x00000c6c  →  0x00000c6c
$fir : 0x00739300  →  0x00739300
$ra  : 0x41414141  →  0x41414141 ("AAAA"?)
$gp  : 0x3ffdaa40  →  0x3ffdaa40
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x407fcdf8│+0x0000: 0x41414141  →  0x41414141	 ← $sp
0x407fcdfc│+0x0004: 0x41414141  →  0x41414141
0x407fce00│+0x0008: 0x00450000  →  0x66616c73  →  0x66616c73	 ← $a1
0x407fce04│+0x000c: 0x00450000  →  0x66616c73  →  0x66616c73
0x407fce08│+0x0010: 0x00000000  →  0x00000000
0x407fce0c│+0x0014: 0x00000000  →  0x00000000
0x407fce10│+0x0018: 0x00000000  →  0x00000000
0x407fce14│+0x001c: 0x00000000  →  0x00000000
──────────────────────────────────────────────────────────────────────────────────────────────────────── code:mips:MIPS32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x41414140
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, stopped 0x41414141 in ?? (), reason: SIGSEGV
VENDOR RESPONSE

Realtek has provided updates software to their customers. LevelOne has declined to patch the issues in their software.

TIMELINE

2023-12-14 - Initial Vendor Contact
2023-12-22 - Vendor Disclosure
2024-05-20 - Vendor Patch Release
2024-07-08 - Public Release

Credit

Discovered by Francesco Benvenuto of Cisco Talos.