## 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)
```