TLDR; Journey of hunting for bugs in the WAG54G routers http daemon. The end goal of this research is to find a way to remotely flash C&C firmware (pre-auth), while learning a thing or two along the way… hey, we’d never actually touched MIPS assembly before this!
To avoid the typical intro preamble, let’s dive straight into the juicy bits of doing this research.
While we already had command line access to the WAG54G available through the serial console, we chose to find another way of gaining remote access to the device for some added flavor. Having console access is an important first step in understanding how the device works and beginning analysis. The WAG54G router comes with an option to enable remote management via Telnet, and can be enabled using its web interface:
Unfortunately the default shell is restricted and only exposes typical “admin” commands that are available through the routers web interface. This means we lose the ability to explore the underlying Linux OS and filesystem, and basically looks something like this:
Well that’s just not good enough. One option is to exploit a vulnerability in the shells command line parsing, which is a promising vector and quickly revealed trivial bugs like the following:
However it still feels a little dirty even having to see the custom shell prompt, surely there’s a better way of breaking out? The bug above is nice enough to show us the admin users shell is
/bin/configurator. What if we can update that entry to point to
Fortunately, Protip #1 of router hacking dictates: When thou needeth a shell, pop a command injection into the “diagnostic ping” page. Rumor has it that 80% of the time, it works every time.
Voilà! The command injection in
/etc/passwd with a new entry for admin, and successfully breaks out of the restricted shell. The next time we try authenticating over Telnet, we’re greeted with a friendly busybox prompt:
So far so good, but this hasn’t technically advanced any further than our previous blog post. The next steps are where things get better, we use the WAG54G devices
tftp client to upload binaries and library dependencies onto our laptop for offline analysis.
Now we’re finally ready to begin the real adventures. To get a lay of the land, we ran the
file command against the binaries to see how they’re linked:
mbp-wifi:Desktop daniel$ file ./httpd ./httpd: ELF 32-bit LSB executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked (uses shared libs), stripped
Learning that our target is a dynamically linked (and stripped) ELF executable compiled for the 32-bit MIPS little endian architecture. Having a copy of the MIPS instruction set documentation handy for reference, an iced-tea, and a copy of IDA Pro - we’ve got everything we need to begin hacking.
This vulnerability is not going to stop the press, but it was a personal moment of celebration. It’s significance shows that we understand enough about the MIPS instruction set to successfully find bugs!
The following screenshot shows a fragment of code which parses an HTTP request, attempting to break apart the request method from the URI by calling the
strsep() libc function:
The problem is
strsep() can set
NULL if the end of the source string is reached and no tokens are found. This condition is not tested for in the code, and as a result the dereference at
0x00407898 causes an access violation reading unmapped memory.
The following commands can be used to verify the issue (note the lack of whitespaces):
python -c 'print "GET" + "A" * 200 + "\n"' | nc 192.168.1.1 80
Which results in the following segmentation fault:
The next bug we came across was slightly more interesting. The code attempts to parse the
Host: header while copying its value into the
re_ip_des global char array. The problem is the use of
strcpy() which performs an unbounded copy using an attacker supplied string, and the destination is of a fixed-length (~64 byte) buffer. By sending a large value in the header, an attacker can exploit this to overwrite other global variables on the heap - a construct which might be useful for privesc:
The following commands can be used to verify the issue:
python -c 'print "GET /index.asp HTTP/1.0\nHost: " + "B" * 9000 + "\n\n"' | nc localhost 80
In which the
entry_config_buf variable is after
re_ip_des and is overwritten with “BBBB”:
The final bug we came across is potentially the most interesting, however it’s triggered post-auth so would need to be combined with another vulnerability to be useful.
The issue itself is a result of calling
fread() into a fixed-size global buffer, but using an unbounded
Content-Length size which has been provided by the remote user:
What is nice about this construct is
global_post_buffer is 10,000 bytes in size, and is directly followed by function pointers for handling mime types and other goodies. This makes exploitation relatively straight forward:
The following commands can be used to verify the issue:
python -c 'print "POST /apply.cgi HTTP/1.0\nAuthorization: Basic YWRtaW46YWRtaW4=\nContent-Length: 20000\n\n" + "A" * 20000' | nc localhost 80
NB: The auth token is required in this case, as POST requests are only handled after a connection has been authenticated.
To ensure that our understanding of MIPS assembly was correct, it was necessary to perform runtime testing on the binaries and ensure our triggers lead to a crash. While this activity could have been done against the WAG54G device itself, we chose to use an emulated environment with full control over tools and debuggers. We achieved this by using QEMU.
brew install qemu mkdir linksys54g cd linksys54g curl -O https://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-maltaurl -O https://people.debian.org/~aurel32/qemu/mips/debian_wheezy_mips_standard.qcow2 qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -had debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -m 512M
Once the machine has booted we can update it and install our binaries:
apt-get update apt-get install gcc gdb vim screen curl curl -O https://website.com/files.tar.gz # binaries/shared libraries etc tar -zxvf ./files.tar.gz cp *.so /lib
The following screenshot shows what it looks like running the httpd in our emulated environment:
Although we are successfully running the executables in our own environment, several errors related to
/dev/nvram access are causing “401 Unauthorized” errors. This may limit the amount of code we are able to exercise.
To resolve this issue, we decided to perform
LD_PRELOAD hooking to intercept requests to the nvram device and send custom responses back. The hooks used an export of the WAG54G routers configuration, which was decoded with the tool here. The following Vim macro was used to convert the tools output into a C like structure, representing the devices configuration as key-value pairs:
The final version of the nvram hooking code can be downloaded here.
Re-executing the daemon with the hook shows no further errors, indicating we have successfully emulated the runtime found on the physical device.
Using the above steps, we have successfully started the process of vulnerability discovery and proof of concept development against the router. In a future blog post, we hope to explore ways in which these vulnerabilities can be exploited to enable flashing C&C firmware onto the device pre-auth.
If you’re wondering about the disclosure timeline for these bugs, long story short the WAG54G has reached end-of-life support. If you’re an internet hero, you can always release your own updates! or choose to flash your router with the great OpenWRT firmware.