Hacking the D-Link DIR-890L

The past 6 months have been incredibly busy, and I haven’t been keeping up with D-Link’s latest shenanigans. In need of some entertainment, I went to their web page today and was greeted by this atrocity:

All of the HTTP/UPnP/HNAP stuff is located under the htdocs directory. The most interesting file here is htdocs/cgibin, an ARM ELF binary which is executed by the web server for, well, just about everything: all CGI, UPnP, and HNAP related URLs are symlinked to this one binary:

It’s been stripped of course, but there are plenty of strings to help us out. The first thing that main does is compare argv[0] against all known symlink names (captcha.cgi, conntrack.cgi, etc) to decide which action it is supposed to take:

“Staircase” code graph, typical of if-else statements

Each of these comparisons are strcmp‘s against the expected symlink names:

Function handlers for various symlinks

This makes it easy to correlate each function handler to its respective symlink name and re-name the functions appropriately:

Renamed symlink function handlers

Now that we’ve got some of the high-level functions identified, let’s start bug hunting. Other D-Link devices running essentially the same firmware have previously been exploited through both their HTTP and UPnP interfaces. However, the HNAP interface, which is handled by the hnap_main function in cgibin, seems to have been mostly overlooked.

HNAP (Home Network Administration Protocol) is a SOAP-based protocol, similar to UPnP, that is commonly used by D-Link’s “EZ” setup utilities to initially configure the router. Unlike UPnP however, all HNAP actions, with the exception of GetDeviceInfo (which is basically useless), require HTTP Basic authentication:

Clearly, hnap_main is using data from the SOAPAction header as part of the system command! This is a promising command injection bug, if the contents of the SOAPAction header aren’t being sanitized, and if we can get into this code block without authentication.

Going back to the beginning of hnap_main, one of the first checks it does is to see if the SOAPAction header is equal to the string http://purenetworks.com/HNAP1/GetDeviceSettings; if so, then it skips the authentication check. This is expected, as we’ve already established that the GetDeviceSettings action does not require authentication:

However, note that strstr is used for this check, which only indicates that the SOAPAction header contains the http://purenetworks.com/HNAP1/GetDeviceSettings string, not that the header equals that string.

So, if the SOAPAction header contains the string http://purenetworks.com/HNAP1/GetDeviceSettings, the code then proceeds to parse the action name (e.g., GetDeviceSettings) out of the header and remove any trailing double-quotes:

SOAPAction = strrchr(SOAPAction, ‘/’);

It is the action name (e.g., GetDeviceSettings), parsed out of the header by the above code, that is sprintf‘d into the command string executed by system.

If remote administration is enabled, HNAP requests are honored from the WAN, making remote exploitation possible. Of course, the router’s firewall will block any incoming telnet connections from the WAN; a simple solution is to kill off the HTTP server and spawn your telnet server on whatever port the HTTP server was bound to:

I’ve tested both the v1.00 and v1.03 firmware (v1.03 being the latest at the time of this writing), and both are vulnerable. But, as is true with most embedded vulnerabilities, this code has snuck its way into other devices as well.

Analyzing “all the firmwares” is tedious, so I handed this bug over to our Centrifuge team at work, who have a great automated analysis system for this sort of thing. Centrifuge found that at least the following devices are also vulnerable:

DAP-1522 revB

DAP-1650 revB

DIR-880L

DIR-865L

DIR-860L revA

DIR-860L revB

DIR-815 revB

DIR-300 revB

DIR-600 revB

DIR-645

TEW-751DR

TEW-733GR

AFAIK, there is no way to disable HNAP on any of these devices.

UPDATE:

Looks like this same bug was found earlier this year by Samuel Huntly, but only reported and patched for the DIR-645. The patch looks pretty shitty though, so expect a follow-up post soon.

39 Responses to Hacking the D-Link DIR-890L

Good find!
Just adding to the D-Link story here –
Of course, D-Link also have several models found to be vulnerable to Misfortune Cookie (CVE-2014-9222) in our research (http://mis.fortunecook.ie) due to the inclusion of the Ralink/MTK chipset.
All vendors we contacted responded eventually, except for D-Link :\

I have a DIR-645 myself and I am not surprised. I found the first security problem with my router before I managed to successfully log in. I set a 16 character admin password which was not accepted when I tried to log in. I could log in as user (without any password) which allowed me to read the configuration including the admin password (which was cropped to 15 characters). Okay I had to right click on the password field and choose “Inspect Element” to see the password, so this probably makes me an advanced hacker in their eyes.

These shell escape problems seem to be everywhere in their code (a handful of them have already been fixed). I gave up updating the firmware and I hope that not making the device externally visible and putting it at a non-standard ip, so that malicious javascript code running on my local computer does not find it, is good enough.

this reminds me of an old privilege escalation exploit from 2010 published by soursec.
i quote the pdf from their PoC:

“When the router sees that the requested SOAPAction is GetDeviceSettings it forgoes the normal
authentication requirements, however, it will execute whatever SOAP action is specified in the body of
the SOAP request, even if that action does not match the action specified in the SOAPAction header.”

Yeah, I saw that too. It could be that “Zhang Wei” reported it to D-Link before I published it, or that he reported my blog post to D-Link and they attributed it to him. I suspect the former, but it’s funny that D-Link put out a security advisory on the same day that I published the bug. 🙂

thanks for your great explainations.
I was trying to run cgibin in a qemu emulator to see if I could send a request. I am not sure whether that is even possible without emulating larger parts of the system.

When runningsudo chroot . ./qemu-arm-static -strace htdocs/cgibin
(with the libraries copied in place) I get some library includes, the request for the current time, but finallywrite(1,0x40881648,32)CGI.BIN, unknown command cgibin
= 32
10889 exit(1)

Did you actually have access to this router model or emulated it, too?

Hey Craig,
Great work! here is CMFan again. I download one ARM simulate system from the link you gave me before for MIPS, and copy the rootfs of the firmware to the system. Then I tried to run httpd on the target using /sbin/httpd. However it didn’t give my any error and doesn’t listen on 80 either. Did you simulate the box whole? if so how did you do that? or if I got the httpd command line wrong?