Practice Hacking on Your Home Router
Although it's true that I tend to focus mostly on Linux in systems administration (after all, that is my day job), I've always had a secondary interest in security, whether it's hardening systems, performing forensics on a hacked system, getting root on a pico projector or even trying my hand at finding and exploiting vulnerabilities. Even though it's fun to set up your own Web services and attempt to exploit them, there's something more satisfying about finding vulnerabilities in someone else's code. The downside, of course, is that most Webmasters don't appreciate it when you break into their sites. However fun hacking is, at least for me, it isn't worth the risk of jail time, so I need to have my fun in more legal ways. This is where my wireless router comes in.
Wireless routers have a long history of being hackable. If you take any group of Linux geeks, you are bound to find a number of them who have, or have had, a member of the classic Linksys WRT series. If you look on-line, there are all sorts of custom firmware you can install to extend its functionality. Although it's true that on some versions of the router you have to jump through some crazy hoops to install custom firmware, it's still not the same kind of challenge as discovering and exploiting a vulnerability on a server. Although I have a stack of WRT54G routers, this article isn't about them; instead, it's about the D-Link DIR-685.
The D-Link DIR-685I first became aware of the D-Link DIR-685 during a Woot-Off on woot.com. If you are familiar with Woot-Offs, you understand that when a new product shows up on the site, you have a limited time to decide whether you want to buy it before it disappears and a new product shows up. The moment I read the specs, I knew this router looked promising. First, it was an 802.11n router, and I was in the market to upgrade from my 802.11g network. Second, it had five different gigabit ports in the back along with two USB ports. Finally, as icing on the cake, it not only had this interesting-looking color LCD on the front that could show statistics, photos or other data, but you also could slot a 2.5" SATA drive up to 1Tb and turn the thing into a small NAS. Based on the fact that it required an ext3 filesystem on the 2.5" drive, I reasonably could assume it even already ran Linux. I didn't have much time to see if anyone already had hacked into the router or created custom firmware, so I made up my mind and clicked the order button.
While I was waiting for the router to ship to my house, I did some extra research. Although unfortunately it looked like there wasn't any custom firmware I could find (this originally was quite an expensive router, so I imagine it didn't have a large install base), I did find a site from someone who documented how to open up the router and wire up and connect a serial port to it, so you could access the local serial console. I decided that in the worst case, if I couldn't find a simpler method, I always could just go that route.
When I got the router, I did the initial setup on my network via the Web interface and then looked one last time for any custom firmware or other method apart from a serial console to get root on the router. I wasn't able to find anything, but before I went to the trouble of taking it apart, I decided to poke around on the Web interface and see if I saw anything obvious. The first dead end came when I enabled the FTP service via the Web interface, yet was not able to find any known vulnerabilities with that FTP server that I could exploit. Unlike when I got root on my pico projector, when I ran an nmap against the machine, I wasn't lucky enough to have telnet waiting for me:
PORT STATE SERVICE
21/tcp open ftp
80/tcp open http
139/tcp open netbios-ssn
445/tcp open microsoft-ds
One Ping Only
As I continued searching though, I got my first clue: the ping test. The
Web interface provides a complete set of diagnostic tools that, among other
things, allows you to ping remote machines to test for connectivity on
http://<router ip>/tools_vct.php (Figure 1). I figured there was a good
chance that the PHP script just forwarded the hostname or IP address you
typed in to a system call that ran ping, so I started by adding a semicolon
and an ls
command to my input. Unfortunately, there was a JavaScript
routine that sanitized the input, but what I noticed was that after I
submitted a valid input, the variable also showed up in the URL:
http://<router ip>/tools_vct.php?uptime=175036&pingIP=127.0.0.1.
Figure 1. The Ping Test
I discovered that although the page used JavaScript to sanitize the input, it did not sanitize the POST data. In fact, I could put just about anything I wanted as the value of pingIP, and it would not only accept it, but because the PHP page displayed the value of pingIP in the output, I also would see my variable output in the resulting Web page, which opened up all sorts of possibilities for JavaScript injection and XSS attacks. Of course, none of that would help me get root on the machine, so I started trying to figure out what kind of payload I could send that would allow me to execute system calls.
No EscapeIt was at this point that I searched on-line for a nice complete table of all of the URL escape codes. You may have noticed that whenever you type a space in a URL, for instance, browsers these days tend to convert it into %20. That is just one of many different escape codes for symbols that are valid to have in a URL in their escaped form. Table 1 shows some of the more useful ones for what I was trying to achieve.
Escape Code | Character |
%3B | ; |
%3F | ? |
%26 | & |
%22 | " |
%3C | < |
%3E | > |
%7C | | |
%60 | ` |
So, for instance, to perform a simple test of command injection, you might attempt to add a sleep command. If the page seems to pause for that amount of time before it reloads, that's a good sign your command injection worked. So, to attempt a sleep command with that page, my encoded URL to set pingIP to "127.0.0.1; sleep 30" looked like http://<router ip>/tools_vct.php?uptime=175036&pingIP=127.0.0.1%3B%20sleep%2030.
"If it's PHP, there will be a hole."I iterated through all sorts of different symbols and options to pass for pingIP, and nothing I tried seemed to have any effect. I was talking to a friend of mine about what I was trying and how I wasn't turning up any usable hole yet, and I got the encouraging reply, "If it's PHP, there will be a hole." I figured that if I already managed to find a JavaScript injection XSS vulnerability, if I kept looking, I had to find some way in. I decided to forget about the ping page for a while and try to find some other vulnerability.
My next clue came when I looked into the system tools page at http://<router ip>/tools_system.php (Figure 2). A glaring item on that page was the reboot button. I figured there was at least a chance that it made some sort of system call, so I looked into the source for that Web page in my browser and noticed that when you clicked on the reboot button, the JavaScript called this URL: http://<router ip>/sys_config_valid.xgi?exeshell=submit%20REBOOT. There's nothing more reassuring than a CGI variable named exeshell. Because I had all sorts of example encoded URLs from my ping test, I decided to try enclosing a sleep command in backticks to see if it would exit to a shell—low and behold, it worked!
Figure 2. System Tools Page
The PayloadOkay, so now I had a viable way to execute shell commands on the system. The next question was how I was going to take advantage of this to log in remotely. My first approach was to try to execute netcat, have it listen on a high port, and use the -e argument so that it would execute a shell once I connected to that port—a poor man's telnetd. After all, many consumer devices that run Linux use BusyBox for their shell, and BusyBox often includes a version of netcat that supports this option. Unfortunately, no combination of netcat arguments I tried seemed to do anything. I was starting to think that I didn't get a shell after all—that is, until I enclosed reboot in backticks, and it rebooted the router.
After the machine came back up, I decided it was possible netcat just wasn't installed, so it was then that I tried the fateful URL: http://<router ip>/sys_config_valid.xgi?exeshell=%60telnetd%20%26%60.
In case you don't want to look it up, that converts into
`telnetd &`
as input. Sure enough, after I ran that command, my nmap output looked a
bit different:
PORT STATE SERVICE
21/tcp open ftp
23/tcp open telnet
80/tcp open http
139/tcp open netbios-ssn
445/tcp open microsoft-ds
Then, I fired up telnet from that same machine:
$ telnet <router ip>
Trying <router ip>...
Connected to <router ip>.
Escape character is '^]'.
BusyBox v1.00 (2009.07.27-14:12+0000) Built-in shell (msh)
Enter 'help' for a list of built-in commands.
#
I not only got a shell, but also a root shell! When I ran a
ps
command, I
noticed my telnetd process on the command line:
sh -c `telnetd &` > /dev/console
It turns out any command you pass to exeshell just gets passed to
sh -c
, so
I didn't need any fancy escaped backticks or ampersands,
exeshell=telnetd
would work just fine.
So, what's the moral to this story? Well, for one, don't open up hardware and void its warranty to get to a serial console when you can get a shell from the Web interface. Two, JavaScript isn't sufficient to sanitize inputs—if you accept POST data, you need to sanitize it as well. Three, passing an input variable directly to sh is probably a bad idea. Finally, next time you want to try your hand at a bit of penetration testing, you don't have to go any further than your own network. Hacking your own hardware can be just as fun (and safer) than hacking someone else.