This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of LINKSYS EA7500 routers. Authentication is not required to exploit this vulnerability.
The specific flaw exists within the handling of HTTP request data to the IGD UPnP service. When parsing the contents of a user-supplied variable of a given SOAP UPnP Action Request, the process does not properly validate the length of user-supplied data prior to copying it to a fixed-length stack buffer. An attacker can leverage this vulnerability to execute code in the context of root.
Product/Firmware: Linksys EA7500 Firmware ALL VERSIONS including Ver.3.0.1.207964 - Other router models and firmware versions using this binary are also affected
The UPnP IGD service binary is located at /usr/sbin/IGD
and is running by default on port 49152. This service provides the IGDdevicedesc.xml
UPnP XML description file that details several functions that can be invoked using HTTP requests with an XML body.
One of these functions is SetDefaultConnectionService. This function requires one variable of type string. When the SetDefaultConnectionService function is invoked and executed, the program does not validate the length of the user-supplied variable prior to
performing a strncpy call involving this buffer. Both the source address and copy size variables of the strncpy call are user-controlled, with the copy size being the length of the user-supplied variable.
The data is then copied to a fixed buffer of 184 bytes, leading to a stack buffer overflow vulnerability.
The function SetDefaultConnectionService is labeled as _set_connection_type. The function begins by initializing a 184-byte buffer and then obtains a pointer to the buffer holding the user-supplied string variable included in the request if it is not NULL. This is done by calling PAL_xml_node_GetFirstbyName and then PAL_xml_node_get_value. See code below:
int _set_connection_type(int **param_1)
{
int iVar1;
char *var_value;
size_t var_value_length;
undefined uVar2;
undefined1 *puVar3;
char **ppcVar4;
undefined4 *puVar5;
char *pcVar6;
int *piVar7;
char acStack_d4 [184]; -----> /* Initializing 184-byte buffer */
memset(acStack_d4,0,0xb4);
iVar1 = PAL_xml_node_GetFirstbyName((*param_1)[0xf0],"NewConnectionType",0); -----> /* iVar1 now points to the user-supplied value */
if ((iVar1 != 0) && (var_value = (char *)PAL_xml_node_get_value(), var_value != (char *)0x0)) { -----> /* Ensures the user-supplied value is not empty and obtains a pointer to it */
...
Later in the same function, a strlen call is performed to obtain the size of the user-supplied string plus a static offset of 0x174. The vulnerable condition is triggered in the subsequent strncpy call where the destination argument is the address of the newly initialized 184-byte buffer, the source is the pointer to the user-supplied string, and the size of the copy operation is the size of the user-supplied string returned by the call to strlen plus a static offset of 0x174. This leads to a buffer overflow vulnerability as both the source address and size variables are controlled by the user and no size validation checks are performed. See code below:
int _set_connection_type(int **param_1)
{
...
var_value_length = strlen((char *)(iVar1 + 0x174)); ----> /* iVar1 is a pointer to the user supplied string */
strncpy(acStack_d4,(char *)(iVar1 + 0x174),var_value_length + 1); ----> /* Vulnerable strncpy call */
...
The offset to overwriting a function return address on the stack is 276 bytes. The next 4 bytes can be utilized to redirect execution to an arbitrary address and thus hijack the control flow of the program.
-
Use a safer string copy function: Instead of using strncpy, which does not guarantee null-termination of the destination buffer, a safer string copy function can be used like strncpy_s or memcpy_s. These functions ensure that the destination buffer is always null-terminated and do not allow the copying of more data than the buffer can hold.
-
Input validation: Validate user input to ensure that it does not exceed the size of the destination buffer. If the input is longer than the buffer size, either reject it or truncate it to fit within the buffer.
-
Use a dynamically allocated buffer: Allocate memory for the destination buffer dynamically instead of using a fixed-size stack buffer. This approach allows the buffer size to be adjusted according to the input data size. (Not recommended for a system with limited resources)
PoC Execution: python poc.py 192.168.1.1 49152
Software Download Link: https://www.linksys.com/support-article?articleNum=49798 (Firmware version Ver. 3.0.1.207964)
- The router has a UART interface that can be used to obtain a root shell. Credentials for root access are root:admin.
- The testing device that was used during this engagement is EA7500 R75. This device has a UART interface with the Rx pin disabled.
- To connect the Rx line, a bent paperclip was used to connect the Rx pin to a line on the PCB making it possible to login to the device.
- With a root shell on the box, drop a statically-compiled gdb-server (I used the prebuilt binary
gdbserver-7.7.1-armel-eabi5-v1-sysv
available here: https://github.com/stayliv3/gdb-static-cross/tree/master/prebuilt). - Use
pkill IGD
to terminate the binary and then relaunch it using./gdbserver :1337 /usr/sbin/IGD
. The following .gdbinit file can be used to initiate a stable debugging session:
> set follow-fork-mode child
> set detach-on-fork off
> target remote 192.168.1.1:1337
> c