Blog: How-Tos

LAN surfing. How to use JavaScript to Execute Arbitrary Code on Routers

Dave Null 13 Mar 2017

First, here’s some pretty broad technical context.

Partly thanks to the widespread use of NAT (Network Address Translation) in routers, attackers can’t make unsolicited attempts to connect to your computer right now. When you use a NAT router, it gets assigned a WAN (Wide Area Network) IP addresses so it’s uniquely identifiable to others on the wider Internet, while also issuing LAN (Local Area Network) IP addresses to each of the devices which connect to it on your local network.

Subsequently, every time a device from inside the local network makes a request to the wider Internet, the router makes a note of which device has made a request to which server and from which local IP. Then, when it gets a response from that server, it forwards it back to the appropriate device on its local IP. This also means that, in most setups, unsolicited packets directed at the router get dropped.

There’s lots of ways to NAT:

In this way, a NAT router, while not really being an active firewall in a strict sense, is firewall-like by design. Computers within the local network don’t have a WAN IP, aren’t directly routable from the Internet, and are therefore harder to directly target. It’s also the closest thing to a firewall there is on most home networks, and many small business networks.

Reading this, it probably sounds like you’re quite safe from attackers behind your router. But, sadly, that’s not quite true.

Fun on the LAN-side with JavaScript

You’re probably using a browser to read this, a browser which has access to all sorts of web servers, all over the world. But it also has access to the mini web server running on your router on the LAN side. Attackers often won’t have access to these web interfaces from the WAN side but, since web pages can interact with pages on other sites (albeit in a limited manner, due to the Same Origin Policy), a web page which running in your browser can also make requests to web servers within your local network. If those web servers have security weaknesses, then code running in your browser can exploit these vulnerabilities.

These kinds of attacks are possible when a local web server lacks robust CSRF (Cross-Site Request Forgery) protection. Without CSRF protection, a router will accept and process a request originating from a context outside of its immediate control. This means that, if we can run code within your browser, perhaps by Cross-Site Scripting, or compelling you to visit a malicious page, we can write this code to send crafted requests directly targeting that router’s vulnerable web interface, from inside your browser. We can do all of this very easily in JavaScript, in the background, and you would probably never notice.

We could do this in any web language really. But JavaScript is the most friendly and efficient way for us to translate the attack context if we need to. It can be run either in a legitimate page owned and operated by the attacker, or in combination with XSS on some other site.

It’s also happening right now, in the wild. For quite a few years now, JavaScript-driven CSRF exploit kits have been observed “pharming” vulnerable routers to change DNS settings. In one recently-discovered case, which had previously gone undetected for an alarmingly long time, JavaScript exploit code was found steganographically and cryptographically obfuscated in the comment field of a malicious image file. It was then decrypted on-the-fly and executed, using known exploits to change your local router’s DNS servers.

When a router is made to use malicious DNS servers, websites of value to criminals can be resolved to IP addresses of servers under malicious control. These servers can be used to host doppelganger sites designed to trick users into entering their valid login credentials, presumably for high-value sites which don’t implement 2-factor authentication. Or, in another well-documented case, malicious DNS servers forced the download of a malware-laden Chrome installer.

This is pretty bad, but how much worse could it get? Since most routers are just low-powered Linux boxes which manage your traffic, the answer is probably arbitrary code execution. A router running malware can do pretty much anything it wants with your traffic.

In late 2016, some Netgear routers were found to be vulnerable to unauthenticated code execution through the web interface (CVE-2016-6277). Although many of the affected devices were higher-end models, the security of the web interfaces had been overlooked. Even though they implemented CSRF protection, this didn’t save Netgear – the vulnerability was simply in the way the server handled the cgi-bin URI. Simply appending a semicolon and a command to the routerlogin.net/cgi-bin/ URI would result in arbitrary code execution.

So, to get a better sense of how these exploits work, let’s go through one I found recently in the stock firmware of the GL Innovations 2.24 firmware.

Example Case – GL Innovations Firmware 2.24 – Exploit Anatomy

The GLi range of routers are small and very customisable routers, predominantly for those who fancy an extra level of control over their Wi-Fi-connected devices, but don’t want to pay much. Their marketing material also suggests they help you “Avoid Hackers”, which is something I like to do too.

So, I bought one to have a look at. After some light spare-time poking, I’d identified two separate issues: an authentication bypass and authenticated code execution.

So let’s run through in detail how a full exploit chain can be written for this router in JavaScript. The full exploit code can be found at https://github.com/tests00/gli-js-driveby/.

The GLi router IP default is 192.168.8.1, and assigns IPs in the standard /24 range. However, it is possible to adjust the router IP and the DHCP range very easily, so there’s no guarantee that we’ll find the router at 192.168.8.1. So, we should make at least a half-decent effort to find the router on the local network.

For this, the first step is to try to grab the local IP of the machine we’re running the code in. We can use a piece of hacky code which uses webRTC, which should give us the exact local IP. In some cases, this technique might not grab the right IP exactly, but it gets the first three octets right – which is good enough for us since we’re only interested in the /24 range. Once we know that the host machine is at, for example, 192.168.20.149, we then only need to attempt to hit 254 IPs. This helps us hone in on the router without having to fumble around the whole RFC1918 address space.

Here’s some lightly modified code using webRTC to find the local IP of the host:

Once we find our local IP, it’s most likely that the router will be sitting at 192.168.20.1, but that’s never guaranteed. So, we need a way to find the router with a degree of certainty.

Same Origin problems, different day

There are some problems at this point. Because of the restrictions the Same Origin Policy puts on us, we can’t just pop iframes and examine the contents of any page which loads. So, to work around this restriction, we can try to load an image file we know will be present at a particular location on the GLi web server. We’re only using one image for this example, but you could use a known unique set of image locations to give you a higher level of certainty.

This known image file location set is often called a “fingerprint”, and can be found in JavaScript router exploit kits. When you’ve got a lot of exploits, you don’t want to just be firing them out randomly all over the place. Attempting to match fingerprints to routers on the local network helps in the initial reconnaissance phase of the exploit: if there is a match it means we simultaneously find the IP address of the local router and have a very good idea of which manufacturer and model it is.

So, we try to load our known image from the 254 possible IP addresses on the local network. On the GLi, we are looking for an image at the location http://192.168.20.x/images/75e.png. When the image loads, it triggers the JavaScript “onload” event handler, which tells us exactly which IP address the GLi is sitting on. Now we know the exact location of the router, we can fire off the exploit chain.

Here we start looping through the class C, trying to find an image:

First of all, it’s worth mentioning that the GLi web server did not have CSRF protection mechanisms in place at all. If it did have, this exploit would have been much less trivial, and may have required finding XSS in the web application to attempt to bypass the CSRF protection. GLi have introduced CSRF tokens in their latest 2.25 firmware, a link for which you can find at the end of this post.

Avoiding passwords

So, the first exploit is the authentication bypass.

Some context: on initial setup, the router forces you to set a new root password. This is not only the password for the web interface, but also the Linux root user password. In general, forcing users to set unique passwords is good practice as it means that users can’t just leave the password as a stock value or blank, making it harder for opportunistic attackers to guess or brute-force the password.

But, unfortunately, this functionality was left exposed and functional after the password had been set the first time. By repeating the first-set password request, it was possible for anyone to arbitrarily set a new root password. Not only that, but simply setting a new password returned a valid session cookie, meaning we didn’t even have to send another log in request. Once the browser has a session cookie, it just keeps and reuses it in our future requests to the router.

This authentication bypass isn’t particularly stealthy, but it’s functional. The code I’ve written simply sets the password to the same as the default Wi-Fi password: “goodlife”.

It’s worth noting that even if such an authentication bypass isn’t present, a router without CSRF protection could also be “brute-forced” with a list of common usernames and passwords, to attempt to establish an authenticated session.

The console output of the full exploit chain running against a GLi router. Even though we are warned about breaking the SOP, the requests are still made successfully:

Injection

Now we’ve got a cookie, we can get to the code injection. This is often more difficult to identify, and most likely won’t get picked up by automated scanning tools. The GLi was, like many embedded devices, running its own “API” – a set of binaries in the /cgi-bin/ subdirectory to which settings were sent in JSON-formatted POST requests. Usually, the easiest method is to binwalk the firmware package out to its component parts and have a look at the contents. With the GLi, as it’s running a modified version of OpenWRT, you also have the option to SFTP in and dump the binaries off that way.

When there are binaries which make changes to system settings sitting exposed on the web server, running as root, with no CSRF protection, the possibility for exploitation can’t be ruled out. In this case, in a bit of free time, I did some analysis of the binaries and found a vulnerable field in the “openvpn_cgi” binary. In this case, when a file is uploaded, the binary looks for a .zip extension and, if it finds it, it attempts to unzip it by passing it directly to the command line.

So, if we simple attempt a fake file upload POST request, using the filename “x.zip\’;wget example.com;echo ‘”, and check the uploaded openvpn folder, we find the homepage to example.com nicely downloaded to the directory.

Here’s the example.com index page downloaded to the /tmp/openvpn_upload directory:

As sometimes happens with code injection, you got to do some workarounds. This injection point doesn’t like slashes, but it’s not too tough to bypass this if you’re determined.

This has been disclosed to GLi, and they’ve fixed the auth bypass, the code injection point, and implemented CSRF tokens in v2.25. So if you’re using stock firmware on a GLi router, get the most recent version for your model at http://www.gl-inet.com/firmware/.

Conclusions

If you’re making routers, make sure your web interfaces are tight. There’s no reason not to implement CSRF protection, at the very least. Robust CSRF protection will mitigate most drive-by JavaScript-based attacks. But, as CSRF tokens can often by bypassed if there’s any XSS in there, or if the implementation isn’t robust, it can’t be the only line of defence.

This doesn’t just affect routers, it affects anything with a web interface. Don’t assume that, just because your device is only on a local network, that the security can be lax, or that it can’t get compromised. Any device with a web interface, even hidden behind a router, can be attacked in this way.

So, whatever router you’re using, you might want to go check the DNS settings now, just to be sure.

 

Disclosure Timeline

15/12/2016 – Vendor notified of authentication bypass
16/12/2016 – Vendor response
21/12/2016 – Vendor fixes authentication bypass, beta firmware released
21/12/2016 – Vendor notified of code execution
21/12/2016 – Vendor response
30/12/2016 – Vendor fixes code execution, implements CSRF protection, beta firmware released
11/01/2017 – v2.25 firmware released