Tuesday, October 22, 2013

Complete, Persistent Compromise of Netgear Wireless Routers

UPDATE: Turns out, Jacob Holocomb (@rootHak42 on Twitter) of Independent Security Evaluators found this bug back in April on a different device, the WNDR4700. Thanks for letting me know, Jacob. Nice find. Here's a link to that report.

UPDATE 2: Because there are almost certainly fools who would go hack somebody's router and say I told them to do it, I added a warning to not do this. DON'T DO IT.

UPDATE 3: I have to confess I tested this on an older firmware, 1.0.1.32, and neglected to test on the latest, 1.0.1.42. I did some cursory static analysis on .42, and satisfied myself that the vulnerabilities discussed still existed. Since Netgear has patched this on other devices, I became concerned that I should have tested more thoroughly, so I did that this morning. I can now say, with confidence, that these vulnerabilities apply equally to the latest wndr3700v4 firmware, 1.0.1.42.

UPDATE 4: I want to give Craig Young of Tripwire VERT credit for finding all these bugs and more. I found the ones below in June, and I believe Craig found them before me. Craig also is responsible for the Netgear ReadyNAS finding which has gotten a lot of coverage lately.

One of my favorite embedded vendors' products to find bugs in is Netgear. Naturally, I was excited to take a look at the firmware for version 4 of Netgear's venerable WNDR3700 wireless router (I talked about version 3 at Black Hat in 2012). Of course, I updated my DLNA SQL injection + buffer overflow exploit code for the new version, but I found something else that's even better.

Don't have time for a bunch of IDA Pro nonsense?  Don't worry; just skip to the TL;DR.

Still here?  Excellent. Let's find out how deep the rabbit hole goes.

An All-purpose CGI Request Handler


On the WNDR3700v4, as with many embedded web servers, a single binary executable, /usr/sbin/net-cgi, gets executed by the web server to handle most, if not all, HTTP requests.


$ file usr/sbin/net-cgi
usr/sbin/net-cgi: ELF 32-bit MSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked (uses shared libs), corrupted section header size

It's important to understand how net-cgi performs authentication. It's a little messy, but don't panic. I brought pictures.

In the executable's data section, there's a table of mime handler structures that describe, among other things, names or partial names of paths that can be requested via HTTP.

A C declaration for the mime handler structure might look approximately like:

struct mime_handler {
        char *pattern;
        char *mime_type;
        char *extra_header;
        void (*input)(char *path, FILE *stream, int len, char *boundary);
        void (*output)(char *path, FILE *stream);
        void (*auth)(char *userid, char *passwd, char *realm);
};

The main function for handling requests is handle_http_request(). Its function signature looks like:


handle_http_request(char *script_file, //requested object or cgi script
                      char *query_string,
                     char *request_method,
                     FILE * stdout,
                     FILE *stdin);


In http_handle_request(), there is logic to loop over each of the mime handlers to check if the requested object matches the handler's pattern string. It actually just does a strstr() to see if the pattern is a substring of the requested object.



If there's a match, then it checks to see if there's an authentication handler. If there is none for that mime handler, then no authentication is performed. Execution skips down the the block I call "pass go, collect $200."



When we look at the mime handler table, we see an entry for the pattern "BRS_". This entry's auth function pointer is NULL, meaning requested objects matching this pattern don't require authentication.


This is interesting because there are several BRS_ files available for the web server to serve up:


root/www (0) $ ls -1 BRS_*
BRS_01_checkNet.html*
BRS_01_checkNet_ping.html
BRS_02_genieHelp.html*
BRS_03A_A_noWan_check_net.html*
BRS_03A_A_noWan.html*
BRS_03A_B_pppoe.html*
BRS_03A_B_pppoe_reenter.html*
BRS_03A_C_pptp.html*
BRS_03A_C_pptp_reenter.html*
BRS_03A_D_bigpond.html*
BRS_03A_detcInetType.html*
BRS_03A_E_IP_problem.html*
BRS_03A_E_IP_problem_staticIP_A_inputIP.html*
BRS_03A_E_IP_problem_staticIP_B_macClone.html*
BRS_03A_E_IP_problem_staticIP_B_macClone_plzWait.html*
BRS_03A_E_IP_problem_staticIP.html*
BRS_03A_F_l2tp.html*
BRS_03A_F_l2tp_reenter.html*
BRS_03B_haveBackupFile_change_domain.htm
BRS_03B_haveBackupFile_fileRestore.html*
BRS_03B_haveBackupFile.html*
BRS_03B_haveBackupFile_ping.html
BRS_03B_haveBackupFile_wait_ping.html*
BRS_03B_haveBackupFile_waitReboot.html*
BRS_04_applySettings.html*
BRS_04_applySettings_ping.html*
BRS_04_applySettings_wget.html*
BRS_04_applySettings_wgetResult.html*
BRS_04_B_checkNet.html*
BRS_04_B_checkNet_ping.html
BRS_05_networkIssue.html*
BRS_check_manulConfig.html
BRS_index.htm*
BRS_netgear_success.html
BRS_ping.html*
BRS_ping_result.html
BRS_plzWait.html*
BRS_retry.htm
BRS_success.html*
BRS_top.html*
BRS_wanlan_conflict.html*

All of these files may be accessed without a password. Almost certainly there is something juicy in there, such as some diagnostic information, or maybe a page that will show you the WPA passphrases for the 2.4 GHz and 5GHz networks.

BRS_success.html. No authentication needed.

Excellent. Free wifi passwords!  Still though, full administrative pwnage would be cool. Good news....


Are You Really Sure We Need to Check Authentication?


When we look at how authentication is checked, there are a bunch of execution paths to the "pass go, collect $200" block. Lets take a look.

Lots of requested paths that result in authentication being skipped.

Above, you can see the first two strings, "unauth.cgi" and "securityquestions.cgi" are checked against the query string, not the requested file. If either of these strings are substrings of the query string, execution bypasses authentication. Since this substring check is against the entire query string, you can request something like http://router_address/protected_page.htm?foo=unauth.cgi. The "unauth.cgi" will match the substring check and execution will skip authentication.

I actually discovered this authentication bypass while double checking my research for the next vulnerability. It is the next one that is a much more powerful bypass and even more trivial to exploit. Plus, it's persistent.


So, Do We Really Super Double For Sure Need to Check Authentication?



After an authentication handler is located, but before the query string is checked for special unauthenticated strings, there is yet another interesting check. Let's have a look[1].

If hijack_process != "3", collect $200.

Above, we see a query of the NVRAM configuration for the hijack_process setting. If that setting is "3", then the authentication execution path is followed as normal. If it is something other than "3", execution skips down to "pass go, collect $200," authentication is bypassed. The purpose of this configuration setting is so that when first plugged in, the router will redirect all web requests to its own web-based administrative interface. In order to prevent users from having to know a default password, authentication is disabled until after the router is configured. The hijack_process setting is one among several that, together, determine whether the router is in an unconfigured state.

Where this gets interesting, however, is an unauthenticated page that will set the hijack_process setting to a value other than "3".

Grepping through the firmware's html files for "hijack_process" yields an interesting find.


$ grep -rn 'cfg_set("hijack_process"' *
BRS_02_genieHelp.html:12:<% cfg_set("hijack_process", "1") %>


The BRS_02_genieHelp.html file contains a command to set hijack_process to "1". The web interface uses a custom HTML templating language. Any text between '<%' and '%>'  is preprocessed by the web server and the entire directive replaced by the output of that processing. In the case above, the request handler processes the "cfg_set" directive to set the hijack_process configuration setting. As we discovered earlier, any web page beginning with "BRS_" does not require authentication. An unauthenticated request to BRS_02_genieHelp.html will have the effect of disabling authentication for the rest of the web interface.  Since the hijack_process setting is only one of several that mark the router as being unconfigured, this one setting alone has no noticeable effect for the user. No web requests are actually hijacked. Further, this setting is stored in NVRAM, which means it is persistent across reboots.

TL;DR


You skipped straight to the good stuff didn't you?  That's cool. Here's the deal. If you browse to http://<router address>/BRS_02_genieHelp.html, you are allowed to bypass authentication for all pages in the entire administrative interface. But not only that, authentication remains disabled across reboots. And, of course if remote administration is turned on, this works from the frickin' Internet.

Don't believe me?  Give it at try. Surf to your WNDR3700v4's web interface and request BRS_02_genieHelp.html. Don't have one of your own? No problem. Shodan's got you covered. (Just to be clear, don't go to Shodan and hack a router you don't own, okay? That's stupid, and it's not legal in most jurisdictions.)


With complete, persistent administrative access to the web interface, a huge attack surface is opened up. A malicious DNS server could be configured, exposing users to web browser exploits. Ports could be forwarded to devices on the LAN, exposing vulnerable services to attack. Or, a trojan horse firmware could be flashed onto the device that would give the attacker persistent root-level access to the router. Additionally, any command injection or buffer overflow vulnerabilities in the router's web interface become fair game once authentication is disabled.

In the next few posts, I will describe additional vulnerabilities that can be exploited once authentication is disabled on this device.

---------
[1] This is the the technical analysis of how this bug works. It's not how I found it. If you buy me a beer at a conference, I'll tell you how I actually found this vulnerability.