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
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.