CVE-2023-48724
A memory corruption vulnerability exists in the web interface functionality of Tp-Link AC1350 Wireless MU-MIMO Gigabit Access Point (EAP225 V3) v5.1.0 Build 20220926. A specially crafted HTTP POST request can lead to denial of service of the device’s web interface. An attacker can send an unauthenticated HTTP POST request 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.
Tp-Link AC1350 Wireless MU-MIMO Gigabit Access Point (EAP225 V3) v5.1.0 Build 20220926
AC1350 Wireless MU-MIMO Gigabit Access Point (EAP225 V3) - https://www.tp-link.com/us/business-networking/omada-sdn-access-point/eap225/
7.5 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-121 - Stack-based Buffer Overflow
The EAP225(US) AC1350 Access Point is a wireless access point from TP-Link offering native integration with tp-link Omada Cloud SDN for centralized cloud management and zero-touch provisioning.
The EAP225 Wireless Access Point runs various services to manage the access point. One such service is httpd_portal
which listens, by default, on ports 80, 443, 22080, 22443, 33443, 44443 and 33080.
A stack-based buffer overflow exists in httpd_portal
within the functionality responsible for parsing x-www-form-urlencoded
POST parameters. When an inbound POST request is received, it will eventually have the POST parameters extracted within a function at offset 0x40e940
named _http_parser_formArg
. An annotated decompilation of this function is included below, for reference.
int _http_parser_formArg(http_request_t* request) {
char content[0x801];
char key[0x801];
char value[0x801];
int result;
memset(&content, 0, 0x801);
memset(&key, 0, 0x801);
memset(&value, 0, 0x801);
if (request == NULL) {
result = -1;
} else {
int content_length = request->content_length;
if (strncmp(&request->content_type, "application/x-www-form-urlencoded", 0x21) != 0) {
result = -1;
} else {
// [1] Receive `content_length` bytes into &content, where `content_length` is
// derived from the Content-Length Header
int rxd_bytes = _http_partial_recv(request->server, &content, content_length);
request->content_length -= rxd_bytes;
char* current_position = &content;
int num_bytes = rxd_bytes;
A portion of the HTTP request is extracted into the content
stack buffer via a function we refer to as _http_partial_recv
. The function will try to read content_length
(or 0x1000, whichever is less) bytes of the request into content
. Notably, this call can be manipulated into overflowing the content
buffer, but the limitation on the max size of 0x1000 stops this overflow from extending beyond the end of the key
stack buffer.
while (num_bytes > 0) {
if (request->num_params >= 0x28) {
break;
}
// [2] Create a pointer to the start of the key
char* key_start = current_position;
int key_len = 0;
// [3] Iterate along the string until '=', is found,
// incrementing `key_len` for each character in the key
while (*current_position != '=') {
if (num_bytes <= 0) {
break;
}
key_len++;
current_position++;
num_bytes--;
}
if (i == 0) {
break;
}
This is the portion of the function responsible for recovering POST parameter key
names. It does so by keeping a copy of a pointer to the start of the key string in key_start
(at [2]
) and then iterating over each character until it finds ‘=’ or runs out of data (at [3]
), incrementing key_len
for each acceptable character.
i--;
current_position++;
// [4] Similarly, iterate along the remainder of the string until
// the string has ended or '&' is found, denoting the next key/value pair
char* value_start = current_position;
int value_len = 0;
while (*current_position != '&') {
if (i <= 0) {
break;
}
value_len++;
current_position++;
i--;
}
if (i > 0) {
current_position++;
i--;
}
Similarly, immediately following a found ‘=’ character, the code will take the same set of steps to recover the value
of the POST parameter. Notably, this will calculate a length for the value
without regard for whether that length is larger than the allocated 0x801 bytes.
memset(&key, 0, 0x801);
memset(&value, 0, 0x801);
// [6] Finally, copy the key and value into their respective stack buffers, utilizing the
// calculated length of the attacker controlled strings, instead of the max size of the buffer
strncpy(&key, key_start, key_len);
strncpy(&value, value_start, value_len);
...
}
}
}
}
Finally, the key
and value
stack buffers are prepared to receive a copy of the key=value
pair by setting their content to \0
(as these buffers are reused between key=value
pairs). These memset
calls introduce some difficulty in further exploiting this vulnerability, as the layout of the stack means that when key
is memset
, every byte after the 0x804th byte of content
will be set back to \0
. This is where the data we intend to use to overflow the strncpy
call for value
is supposed to live. Working around this to craft an example exploit that fully corrupts the stack appears to be impossible due to these memset
calls. However, we can leverage the null-byte padding functionality of strncpy
to corrupt the stack frame with zeros and crash the process by attempting to return to 0x00000000
. So long as an unauthenticated attacker submits a key=value
pair where the value
is 0x815 bytes or longer, they can crash the httpd_portal
service, which can only be restarted by restarting the entire access point. We believe that for devices being managed by Omada, this would make them unmanagable without a restart, as Omada manages these devices via their web interfaces.
Program received signal SIGSEGV, Segmentation fault.
0x00000000 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────── registers ────
$zero: 0x0
$at : 0x7fff1dc0 → 0x00000000
$v0 : 0x0
$v1 : 0x0
$a0 : 0x77cc71f8
$a1 : 0x1
$a2 : 0x2
$a3 : 0x00474f7a → 0x00002f64
$t0 : 0x77cc71f8
$t1 : 0x0
$t2 : 0x1
$t3 : 0x20697320 (" is "?)
$t4 : 0xfffffffe
$t5 : 0x1
$t6 : 0x0
$t7 : 0x7420696e ("t in"?)
$s0 : 0x0
$s1 : 0x77c8d720
$s2 : 0x7fff6e68 → 0x00000000
$s3 : 0x7fff6f24 → 0x7fff6fd3 → "httpd_portal"
$s4 : 0x1
$s5 : 0x00403b28 → 0x3c1c0009
$s6 : 0x0040d7d0 → <main+0> addiu sp, sp, -32
$s7 : 0x0044e634 → nop
$t8 : 0x8
$t9 : 0x77c108fc
$k0 : 0x400
$k1 : 0x0
$s8 : 0x0
$pc : 0x0
$sp : 0x7ffc8c58
$hi : 0x8
$lo : 0x0
$fir : 0x0
$ra : 0x0
$gp : 0x0049a7d0 → 0x00000000
──────────────────────────────────────────────────────────────────────────────── code:mips:MIPS32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x0
───────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "httpd_portal", stopped 0x0 in ?? (), reason: SIGSEGV
The vendor released new firmware at: https://www.tp-link.com/us/support/download/eap115/v4/#Firmware https://www.tp-link.com/us/support/download/eap225/v3/#Firmware
2023-12-11 - Vendor Disclosure
2024-04-03 - Vendor Patch Release
2024-04-09 - Public Release
Discovered by the Vulnerability Discovery and Research team of Cisco Talos.