## A buffer overflow vulnerability in Tenda Ac15 Router ### Affected product <!-- Tested on Tenda AC15 WiFi Router (firmware version: V15.13.07.13). It may also work on other models with similar firmware versions. --> - Product Name: Tenda Ac15 Router - Vendor: Tenda - Affected Version: V15.13.07.13 - Fixed Version: None (at the time of reporting) ### Overview A buffer overflow vulnerability has been discovered in the firmware (V15.13.07.13) of the Tenda Ac15 router. The vulnerability occurs when the `webCgiGetUploadFile` function calls the `socketRead` function to process HTTP request packet, resulting in the overwriting of a buffer on the stack. This vulnerability could lead to unauthorized access, arbitrary code execution, or a denial-of-service (DoS) condition on the router. <!-- Since both the `upgrade` and `UploadCfg` functions call the `webCgiGetUploadFile` function, an attacker can set the URL of HTTP request to either `/upgrade` or `/UploadCfg`. Once the URL is correctly set, triggering the vulnerability only requires the length of the request body in the HTTP request to exceed a certain threshold. --> ### Attack vector **Prerequisites** The attacker must have network access to the Tenda Ac15 router, either through a local network (e.g., being connected to the same Wi-Fi network) or remotely if the router is exposed to the Internet. **Attack Steps** Create an HTTP request with the URL set to either `/upgrade` or `/UploadCfg` and set the request body extremely long. Then use tools to send the crafted HTTP request to the router's IP address. ### Details #### Vulnerable Function Analysis The `webCgiGetUploadFile` function, which is involved in handling HTTP requests related to file uploads and other operations, calls the `socketRead` function to read data from the HTTP request. The `socketRead` function fails to perform proper bounds checking when writing data from the HTTP request body into a stack-based buffer. The following is a simplified pseudo-code representation of the vulnerable behavior: Function `webCgiGetUploadFile`: ```clike= int __cdecl webCgiGetUploadFile(webs_t wp, char_t **pBufStart) { signed int clen; // $v0 int copyflag; // [sp+18h] [+18h] char_t *pCurMem; // [sp+1Ch] [+1Ch] int memory_is_enough; // [sp+20h] [+20h] int i; // [sp+24h] [+24h] int j; // [sp+28h] [+28h] int action; // [sp+2Ch] [+2Ch] int count; // [sp+30h] [+30h] int rdBytes; // [sp+34h] [+34h] unsigned int lenFile; // [sp+38h] [+38h] unsigned int len; // [sp+3Ch] [+3Ch] char buf[2052]; // [sp+40h] [+40h] BYREF char bufBoundry[68]; // [sp+844h] [+844h] BYREF action = 0; j = 0; memory_is_enough = 1; count = wp->clen; printf("webCgiGetUploadFile wp->clen[%d]\n", count); doSystemCmd("echo 1 >/var/upgrading"); memory_is_enough = have_enough_mem(wp->clen); pCurMem = *pBufStart; while ( count > 0 ) { clen = wp->clen; if ( clen >= 2049 ) clen = 2048; len = clen; cyg_thread_delay(2); if ( sslenable == 1 ) { if ( count < len ) len = count; rdBytes = websSSLRead(wp->wsp, buf, len); } else { // buffer overflow occur here rdBytes = socketRead(wp->sid, buf, len); } ... } ... } ``` function `socketRead`: ```clike int __cdecl socketRead(int sid, char *buf, int bufsize) { int v4; // $v0 int v5; // $v0 int bytesRead; // [sp+18h] [+18h] int room; // [sp+1Ch] [+1Ch] int len; // [sp+20h] [+20h] int lena; // [sp+20h] [+20h] ringq_t *rq; // [sp+24h] [+24h] socket_t *sp_0; // [sp+28h] [+28h] int errCode; // [sp+2Ch] [+2Ch] BYREF sp_0 = socketPtr(sid); if ( !sp_0 ) return -1; if ( (sp_0->flags & 1) != 0 ) return 0; rq = &sp_0->inBuf; bytesRead = 0; while ( 1 ) { while ( 1 ) { if ( bufsize <= 0 ) return bytesRead; v4 = ringqLen(rq) >= bufsize ? bufsize : ringqLen(rq); len = v4; if ( v4 <= 0 ) break; LABEL_26: memcpy(&buf[bytesRead], sp_0->inBuf.servp, len); ringqGetBlkAdj(rq, len); bufsize -= len; bytesRead += len; } if ( (sp_0->flags & 0x80) != 0 && bytesRead > 0 ) return bytesRead; ringqFlush(rq); room = ringqPutBlkMax(rq); lena = socketGetInput(sid, (char *)sp_0->inBuf.endp, room, &errCode); if ( lena >= 0 ) break; if ( errCode != 11 ) return -1; if ( (sp_0->flags & 0x80) == 0 || bytesRead ) { if ( bytesRead >= 0 ) return bytesRead; return -1; } } if ( lena ) { ringqPutBlkAdj(rq, lena); v5 = lena; if ( lena >= bufsize ) v5 = bufsize; len = v5; goto LABEL_26; } if ( !bytesRead ) sp_0->flags |= 1u; return bytesRead; } ``` #### Proof-of-Concept (PoC) Use `qemu-user` to run the HTTP server in the firmware of Tenda AC15 router: ```shell= $ sudo qemu-mipsel -L . ./bin/httpd Yes: ****** WeLoveLinux****** Welcome to ... connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. [httpd][debug]----------------------------webs.c,157 httpd listen ip = 172.21.0.1 port = 80 webs: Listening for HTTP requests at address 172.21.0.1 ``` Send a constructed HTTP request using Python. And the `httpd` process will be terminated for detecting a memory access violaion. ```shell= connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. connect: No such file or directory func:cfms_mib_proc_handle, line:214 connect cfmd is error. webCgiGetUploadFile wp->clen[777] smbd: no process found nmbd: no process found stupid-ftpd: no process found minidlna: no process found have_enough_mem 507: current remain memory is -999632896 [webCgiGetUploadFile,410] receive file successs... no mem return fish: Job 1, 'sudo qemu-mipsel -L . ./bin/ht…' terminated by signal SIGSEGV (Address boundary error) ```