Plug and Play -- Part 1
Oct 7, 2017
9 minute read

tl;dr

Unauthenticated code execution on the D-Link DSP-W215 smart plug.

  • authentication bypass
  • getting the firmware and having a quick glance at it

Note: the following four-part blog post has greatly benefited from the help and involvement of Robin Verton, with whom all aspects of this work have been discussed. He gave me invaluable advices and support. He also could not resist and wrote 95% of the first exploit script. Thanks Robin!

Summary of disclosure timeline

  • I contacted D-Link on Oct 23, 2017, via this web form, but it seems nobody got my message.

  • I then contacted them via email on Nov 17, 2017 and got an answer straight away.

  • I sent my findings on Nov 19, 2017 and got an acknowledgment on Nov 28, 2017.

  • After sending an email on Jan 16, 2018 that remained without an answer, I sent another email on Feb 22, 2018, stating that I would publish my findings a week later if I got no answer to this email.

  • I got an answer the next day, and after a few exchanges, D-Link sent me a new firmware version (a beta patch from their R&D).

  • After taking the time to audit the new firmware they sent me (version 02.23), I noticed that some vulnerabilities were fixed, though not in the binary web_cgi.cgi itself, but in some other places (that I did not try to identify). The vulnerability that is discussed in Part 3 has not been fixed, although exploiting it is a little bit more difficult. I notified D-Link of all these matters on Mar 26, 2018.

  • Having no answer, I contacted them again on Apr 25, 2018.

  • Having still no answer on May 7, 2018, to my email stating that I would like to publish my findings soon, I’ve decided to publish them without waiting for an answer that may never come. And anyway, any “standard” user of the D-Link DSP-W215 smart plug will first do an upgrade, so that the exploits given here do not work (at least without some effort for the vulnerability that is discussed in Part 3). This series of four blog posts was originally written in Oct, 2017.

Choice of an IoT target

I find it fun to play ctfs and wargames, but at some point I wanted to do some real life pwning. So I decided to pwn IoT devices since, as some people put it, “The S in IoT stands for Security”. One of my aims was to learn hardware pwning (which has not been fulfilled, because there were more than enough software bugs to exploit).

I first had to chose a target, and my criteria were the following:

  • simplicity: embedded device with not too many features
  • price: not too high
  • pwnability:
    ~ enough features to have a reasonably large attack surface
    ~ pretty high probability of being vulnerable, so if possible find a device that has already been pwned, but not too recently
    ~ easy to open physically (for hardware pwning) – that may seem trivial, but I looked at pictures to see if I could spot screws!
    ~ availability of firmware

After some searching and thinking, I was set on trying to pwn the D-Link DSP-W215 smart plug, even if it is now “end of life”. This IoT device had already been pwned four times in 2014 by Craig Heffner, and the release notes of its latest firmware (1.25B03 for hardware A2, date 2017/07/20, at the time of writing) mentionned Fix the buffer overflow vulnerability for “splite_cookie” function web_cgi.cgi as one of the resolved problems. All other criteria seemed to be satisfied. As an added bonus, the cpu has a MIPS architecture, about which I knew nothing before starting this project. So I took the plunge and bought one of these plugs for 38€.

Opening the plug

First thing I did when receiving the plug was to open it and have a look at the various chips inside. I also tried to find JTAG and UART. No sign of JTAG, but there is something looking like UART. This is all so tiny and badly placed that it is not nice for a first hardware pwning experiment. So let’s postpone the learning of hardware pwning for now!

Plugging the plug, and play

So let’s do what an electric plug is for… plug it! After a few seconds, a red led starts blinking and the plug is now, among other things, a Wi-Fi access point. For all that follows (in this post and the next ones), I’ll pretend that the MAC address of the plug is 4d:43:41:44:44:52, that the corresponding SSID is DSP-4452 and that the PIN number found on a card in the plug’s box (or on the plug itself) is 123456.

Enumeration of services

The normal step after this is to use the Android or iPhone app to interact with the plug. So let’s not do this! I connected to the plug’s Wi-Fi directly from my computer, and got the IP address from ifconfig:

inet 192.168.0.80  netmask 255.255.255.0  broadcast 192.168.0.255

A nmap ping scan nmap -sn 192.168.0.0/24 reveals the IP of the plug

Nmap scan report for 192.168.0.60
Host is up (-0.092s latency).
MAC Address: 4d:43:41:44:44:52 (Unknown)

Usual tcp nmap -A -T4 192.168.0.60 and udp nmap -sU 192.168.0.60 port scans give us (among many other lines of output)

PORT   STATE SERVICE VERSION
80/tcp open  http    lighttpd 1.4.34
|_http-server-header: lighttpd/1.4.34
MAC Address: 4d:43:41:44:44:52 (Unknown)
Device type: general purpose
Running: Linux 2.6.X
OS CPE: cpe:/o:linux:linux_kernel:2.6
OS details: Linux 2.6.17 - 2.6.36

PORT     STATE         SERVICE
67/udp   open|filtered dhcps
5353/udp open          zeroconf

Investigating the HTTP service

Requesting http://192.168.0.60 in a browser yields a bare, ctf-like, login form (after redirection to /Login.html), with pre-filled user name

login_form

One can view the source code of Login.html and of the associated JavaScript files. In particular, js/soapclient.js tells us that the plug is using the SOAP protocol. This will be discussed later, in Part 2.

Trying the brute-forcible 6-digit PIN number given by the manufacturer leads to a successful authentication. It turns out that the authentication also works by replacing the user name Admin by admin or AdMiN. So the underlying code is not case-sensitive. This is already bad. But far worse: almost any user name and an empty password allow for authentication. The reason for this behaviour will be studied in Part 4. Not all user names work, in particular an empty user name, Admin or its case variants all fail. Furthermore, looking at the source of Login.html, one notices a line appearing twice:

+ "<Username>"+ document.getElementById("user_name").value + "</Username>"

Trying the user name </Username> leads to an ‘ERROR’ message, showing that the user name, not being sanitized, leads to SOAP injection. I have not found a way to exploit this, but I found another vulnerability concerning the user name, which will be discussed in Part 4 and leads to a root shell.

Getting the firmware

After authentication and redirection, one is presented with the content of http://192.168.0.60/version.txt

Firmware External Version: V2.02
Firmware Internal Version: V2.02b04
Date: Wed, 22 Oct 2014
Checksum: 0x06A16CC0
2.4GHz regulation domain: EU
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
Firmware Query:
http://wrpd.dlink.com/router/firmware/query.aspx?model=DSP-W215_Bx_Default_FW_0202_4d4341444452
LAN MAC: 4d:43:41:44:44:52
Kernel: 2.6.31, B0005, Date=Tue, 9 Sep, 2014
Apps: 1.1, B0016, Date=Wed, 22 Oct, 2014
WLAN Driver: AR9531, 10.2-00082-4, B0006, Date=Thu, 18 Sep, 2014
2.4GHz WLAN MAC 0: 4d:43:41:44:44:52
2.4GHz SSID: DSP-4452
Factory Default: 1

We thus have a Kernel version (compatible with nmap’s estimate), the firmware version, but also a link to what seems to be the firmware run by the plug. Requesting
http://wrpd.dlink.com/router/firmware/query.aspx?model=DSP-W215_Bx_Default_FW_0202_414141414141 gives the following XML content

<DSP-W215_Bx>
	<Default>
		<FW_Version>
			<Major>02</Major>
			<Minor>01</Minor>
			<Date>2014-10-13</Date>
			<Recommend/>
		</FW_Version>
		<Download_Site>
			<Global>
				<Firmware>
					http://dlinkpp-s3.s3.amazonaws.com/DSP-W215/Bx/Default/0201/DSP-W215B1_FW_201B02.bin
				</Firmware>
				<Release_Note>
					http://wrpd.dlink.com/router/firmware/GetReleaseNote.aspx?model=DSP-W215_Bx_Default_FW_0201
				</Release_Note>
			</Global>
		</Download_Site>
	</Default>
</DSP-W215_Bx>

The second link is dead, but the first one is not, and one can thus get the firmware. It took me longer than I’d like to admit to realize that the downloaded firmware is not the one running on the plug: it is version 2.01b02 as opposed to version 2.02b04. I realized this when an exploit, working on the downloaded firmware, failed on the plug. A little googling leads to the following link from which a few firmwares, including both versions mentionned previously can be downloaded.

Taking a very quick glance at the firmware

The firmware is easily extracted thanks to binwalk -e firmware.bin, and one can then browse the resulting squashfs-root directory. Of interest are mnt/passwd and mnt/shadow that, when recombined by running unshadow, give the two following entries:

root:$1$Wqz3mfqU$K7HGtD2jFqZlUs8SG7hP/0:0:0:root:/root:/bin/sh
Admin:$1$$zdlNHiCDxYDfeF4MZL.H3/:0:0:root:/root:/bin/sh

Googling for the unsalted Admin password hash leads to the following link and reveals the Admin:5up credentials (trying to find these with john and the usual Kali Linux rockyou.txt file fails because this word list does not contain 5up). These credentials will be useful later on, see Part 3.

Other files of interest are contained in www-ro/, which contains, apart from Index.html, Login.html and a js/ folder, the binary web_cgi.cgi. This is the binary to which the requests are transfered to by lighttpd (it’s not called my_cgi.cgi as is the case for the firmware studied by Craig Heffner). This binary will be studied in Part 3. For now, let us just run strings www-ro/web_cgi.cgi | grep '/', which gives (among others) the following

/common/info.cgi
/HNAP1
/www/config.bin
/www/version.txt
/www/mplist.txt

Apart from config.bin, all these can be requested, as well as /web_cgi.cgi (dropping the /www for the last two). Most of the information gained from /common/info.cgi and /mplist.txt was already available in version.txt. One additional information gained from /mplist.txt is HW Ver (MP): B1, telling us the hardware version (which, as the MAC address and the firmware version, can also be found on the plug’s box of or on the plug itself). Another one is that there is a second MAC address, differing from the already mentionned one by its first byte

WLAN MAC 1: 6d:43:41:44:44:52
WLAN 1 Channel List: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13

where I obviously again redacted the MAC address. More interesting is /HNAP1 that serves the following XML content

<soap:Envelope>
	<soap:Body>
		<GetDeviceSettingsResponse>
			<GetDeviceSettingsResult>OK</GetDeviceSettingsResult>
			<Type>ConnectedHomeClient</Type>
			<DeviceName>DSP-W215</DeviceName>
			<VendorName>D-Link</VendorName>
			<ModelDescription>D-Link Socket</ModelDescription>
			<ModelName>DSP-W215</ModelName>
			<DeviceMacId>4d:43:41:44:44:52</DeviceMacId>
			<FirmwareVersion>2.02</FirmwareVersion>
			<FirmwareRegion>Default</FirmwareRegion>
			<LatestFirmwareVersion/>
			<HardwareVersion>B1</HardwareVersion>
			<HNAPVersion>0114</HNAPVersion>
			<PresentationURL>http://dsp.local</PresentationURL>
			<CAPTCHA>false</CAPTCHA>
			<ModuleTypes>
				<string>Smart Plug</string>
				<string>Electrical Sensor</string>
				<string>Electrical Sensor</string>
			</ModuleTypes>
			<SOAPActions>
				<string>http://purenetworks.com/HNAP1/Reboot</string>
				<string>http://purenetworks.com/HNAP1/SetFactoryDefault</string>
				<string>http://purenetworks.com/HNAP1/IsDeviceReady</string>
				<string>http://purenetworks.com/HNAP1/Login</string>
				<string>http://purenetworks.com/HNAP1/GetMultipleHNAPs</string>
				<string>http://purenetworks.com/HNAP1/SetMultipleHNAPs</string>
				<string>http://purenetworks.com/HNAP1/GetDeviceSettings</string>
				<string>http://purenetworks.com/HNAP1/SetDeviceSettings</string>
				<string>http://purenetworks.com/HNAP1/GetDeviceSettings2</string>
				<string>http://purenetworks.com/HNAP1/SetDeviceSettings2</string>
				<string>http://purenetworks.com/HNAP1/GetGroupSettings</string>
				<string>http://purenetworks.com/HNAP1/SetGroupSettings</string>
				<string>http://purenetworks.com/HNAP1/GetSystemLogs</string>
				<string>http://purenetworks.com/HNAP1/CleanSystemLogs</string>
				<string>http://purenetworks.com/HNAP1/GetModuleOPStatus</string>
				<string>http://purenetworks.com/HNAP1/SetModuleOPStatus</string>
				<string>http://purenetworks.com/HNAP1/GetModuleProfile</string>
				<string>http://purenetworks.com/HNAP1/SetModuleProfile</string>
				<string>http://purenetworks.com/HNAP1/GetModuleSOAPActions</string>
				<string>http://purenetworks.com/HNAP1/GetTimeSettings</string>
				<string>http://purenetworks.com/HNAP1/SetTimeSettings</string>
				<string>http://purenetworks.com/HNAP1/GetScheduleSettings</string>
				<string>http://purenetworks.com/HNAP1/SetScheduleSettings</string>
				<string>http://purenetworks.com/HNAP1/GetRecursiveSchedule</string>
				<string>http://purenetworks.com/HNAP1/SetRecursiveSchedule</string>
				<string>http://purenetworks.com/HNAP1/GetDCHPolicy</string>
				<string>http://purenetworks.com/HNAP1/SetDCHPolicy</string>
				<string>http://purenetworks.com/HNAP1/PushDCHEvent</string>
				<string>http://purenetworks.com/HNAP1/GetEventSupportList</string>
				<string>http://purenetworks.com/HNAP1/GetActionSupportList</string>
				<string>http://purenetworks.com/HNAP1/GetFirmwareStatus</string>
				<string>http://purenetworks.com/HNAP1/GetFirmwareValidation</string>
				<string>http://purenetworks.com/HNAP1/StartFirmwareDownload</string>
				<string>http://purenetworks.com/HNAP1/PollingFirmwareDownload</string>
				<string>http://purenetworks.com/HNAP1/SettriggerADIC</string>
				<string>http://purenetworks.com/HNAP1/GetInternetSettings</string>
				<string>http://purenetworks.com/HNAP1/GetCurrentInternetStatus</string>
				<string>http://purenetworks.com/HNAP1/GetWLanRadios</string>
				<string>http://purenetworks.com/HNAP1/SetTriggerWirelessSiteSurvey</string>
				<string>http://purenetworks.com/HNAP1/GetSiteSurvey</string>
				<string>http://purenetworks.com/HNAP1/SetAPClientSettings</string>
				<string>http://purenetworks.com/HNAP1/GetAPClientSettings</string>
				<string>http://purenetworks.com/HNAP1/GetPowerMeterSettings</string>
				<string>http://purenetworks.com/HNAP1/SetPowerMeterSettings</string>
				<string>http://purenetworks.com/HNAP1/GetPowerMeterSettings2</string>
				<string>http://purenetworks.com/HNAP1/GetCurrentPowerConsumption</string>
				<string>http://purenetworks.com/HNAP1/GetPMWarningThreshold</string>
				<string>http://purenetworks.com/HNAP1/SetPMWarningThreshold</string>
				<string>http://purenetworks.com/HNAP1/GetPURSupportedTypes</string>
				<string>http://purenetworks.com/HNAP1/GetPURecords</string>
				<string>http://purenetworks.com/HNAP1/GetPowerMeterLogs</string>
				<string>http://purenetworks.com/HNAP1/SetPowerMeterLogs</string>
				<string>http://purenetworks.com/HNAP1/CleanPowerMeterLogs</string>
				<string>http://purenetworks.com/HNAP1/GetTempMonitorSettings</string>
				<string>http://purenetworks.com/HNAP1/SetTempMonitorSettings</string>
				<string>http://purenetworks.com/HNAP1/GetTempMonitorSettings2</string>
				<string>http://purenetworks.com/HNAP1/GetCurrentTemperature</string>
				<string>http://purenetworks.com/HNAP1/GetTempMonitorLogs</string>
				<string>http://purenetworks.com/HNAP1/SetTempMonitorLogs</string>
				<string>http://purenetworks.com/HNAP1/CleanTempMonitorLogs</string>
				<string>http://purenetworks.com/HNAP1/GetSocketSettings</string>
				<string>http://purenetworks.com/HNAP1/SetSocketSettings</string>
				<string>http://purenetworks.com/HNAP1/GetSocketSettings2</string>
				<string>http://purenetworks.com/HNAP1/GetSocketLogs</string>
				<string>http://purenetworks.com/HNAP1/CleanSocketLogs</string>
				<string>http://purenetworks.com/HNAP1/GetmydlinkSupportStatus</string>
				<string>http://purenetworks.com/HNAP1/GetmydlinkRegInfo</string>
				<string>http://purenetworks.com/HNAP1/SetmydlinkReg</string>
				<string>http://purenetworks.com/HNAP1/SetmydlinkUnregistration</string>
			</SOAPActions>
			<SubDeviceURLs/>
		</GetDeviceSettingsResponse>
	</soap:Body>
</soap:Envelope>

One new information gained from this is the existence of http://dsp.local. Requesting this address brings us, after redirection, to http://dsp.local/Login.html. This is compatible with the fact that there is a zeroconf service running on the plug.

Of course, one major information is that the communication with the plug is not only based on the SOAP protocol, but also on the HNAP protocol. These will be the subject of Part 2.