CVE-2023-50330
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.
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
rtl819x Jungle SDK - https://www.realtek.com/en/ WBR-6013 - https://www.level1.com/level1_en/wbr-6013-n300-wireless-router-54069103
7.2 - CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
CWE-121 - Stack-based Buffer Overflow
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, """, 6);
p2 += 6;
}
else if (*p1 == '\'') {
memcpy(p2, "'", 5);
p2 += 5;
}
else if (*p1 == '\\') {
memcpy(p2, "\", 5);
p2 += 5;
}
else if (*p1 == '<') {
memcpy(p2, "<", 5);
p2 += 5;
}
else if (*p1 == '>') {
memcpy(p2, ">", 5);
p2 += 5;
}
else if (*p1 == '%') {
memcpy(p2, "%", 5);
p2 += 5;
}
else if (*p1 == '&') {
memcpy(p2, "&", 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 <
, 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.
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
Realtek has provided updates software to their customers. LevelOne has declined to patch the issues in their software.
2023-12-14 - Initial Vendor Contact
2023-12-22 - Vendor Disclosure
2024-05-20 - Vendor Patch Release
2024-07-08 - Public Release
Discovered by Francesco Benvenuto of Cisco Talos.