Quick note: this project was a joint effort between Andrew Orr and Alex Weber
I recently subscribed to a VoIP service to use as my home phone. As part of this service I had to purchase an ATA or Analog Telephone Adapter; this connects a landline phone to the VoIP network. They’re neat devices! After fiddling with the settings, you plug a phone into the device, plug the device into the Internet, and away you go.
But I got curious about the security of these ATAs. These tiny boxes have a gigantic attack surface. They speak a busload of network protocols, and voice calling is an important business IT function. Plus, they’re inexpensive, which never helps anything. What could we find if we dug into them?
First things first, we had to buy some of these. We settled on two Cisco devices: the SPA112 and SPA122. They’re practically identical, except that the SPA122 has an extra port, labelled “Ethernet”, that lets you daisy-chain your computer through the device, saving you a switch port.
Next, we grabbed the latest firmware (1.4.1 SR3, released in April 2019) to unpack it. We found a great tool on Github, courtesy of BigNerd95, to both unpack and edit the firmware. We re-rolled the firmware with some extra tools like gdbserver for remote debugging, and a Telnet server to access a shell on the device. That was a challenge in itself. The device is built around an ARMv4 CPU, so we found and used an old, unsupported Debian armel emulator image, under QEMU, to compile the tools we needed.
Lastly, we fired up Ghidra and started looking at the httpd binary. Not to be confused with Apache HTTPD, this was the completely custom HTTP server that provided the device’s administrative Web interface. Everything from handling form submissions to parsing cookies was done in unsafe C code inside this httpd program.
Warming up with XSS
The first bugs we found were stored XSS, both related to DHCP. When these devices connect to a network, they request an IP address, and one of the settings pushed to them over DHCP, the “domain-name” option, is reflected directly in the status page.
In the other direction, the SPA122 has a DHCP server and will give IP addresses out on its second Ethernet port. In this case, the client connected to the ATA can send its hostname. This hostname is reflected on the status page, which leads to a second XSS.
Admin password hash leak
We explored the Web UI and found that there are two users, “cisco” and “admin”. The “cisco” user has lower privileges and generally can only read settings, whereas the “admin” user can do anything, including uploading new firmware.
We explored the login system and found a number of minor issues, none of which lead to a full authentication bypass, so we moved on to the post-authentication features of the Web UI.
Our first surprising finding is that user password hashes are leaked through the Web UI, but the page that this happens on is not visible to the unprivileged “cisco” user; only to the superuser, “admin”:
This issue is insignificant on its own, as this page is only readable by the “admin” user. Entering the page’s URL manually as “cisco”, we get a lovely “401 Unauthorized”:
So it does appear that GET requests do check the user’s access level. However, this will make an appearance later!
Path traversal and privilege escalation
Next, we looked at how Web form submissions are handled. We noticed that many form submissions followed a pattern: they were submitted to the URL “apply.cgi” with “change_action=gozila_cgi”. In addition, they had a “submit_button” value indicating what the target function of the submission was. For example, when we changed a user’s password on the “User_Level.asp” page, the “submit_button” was “User_Level”.
In Ghidra, we found the responsible function. It works by searching a lookup table containing “submit_button” values paired with function pointers, and, upon finding a match, the found function pointer is called and that function becomes responsible for handling the form submission. What was more interesting, though, was what comes after the form handler:
The “submit_button” (or “next_page”) form parameter is used directly as a filename to read from disk, render as ASP, and return as a response, and there is no user privilege check!
We tried making a POST request as the “cisco” user with “submit_button=User_Level” (the page that reflected the password hashes), and lo and behold: the device returned the User_Level.asp page, complete with the “admin” user’s password hash.
POST /apply.cgi;session_id=33ebccfdcdde6f9d588295ce83ac4bc6 HTTP/1.1
HTTP/1.1 200 Ok
< … >
< … rest of User_Level.asp page follows … >
How about the “next_page” parameter? We tried the full name of the ASP file on disk: “User_Level.asp”. This rendered the page the same way. Then, on a whim, we tried a “next_page” of “/etc/passwd” (the full path to the Linux user database file), and amazingly that worked as well:
We also tried reading “/nvram/nvram.data”, which allows reading the contents of the device’s flash storage. However, the results were truncated; we assume this is because the files are passed through an ASP interpreter, and that this interpreter doesn’t like non-ASCII characters and bails early.
By the way, in case you were curious: the password hash is computed by taking the original password, appending the password’s length as two digits, repeating this to fill 64 characters, and taking the MD5 hash of that. In other words, if the password is “admin”, the hash is computed as follows:
After further exploration of the form submission handlers in Ghidra, we noticed a lot of unsafe C functions were being used, like strcat and strcpy. Although these functions were reachable only post-auth, we were curious: could we exploit these?
This is one example, which is not actually used by the Web UI that we could see, but nevertheless was reachable by a POST with “submit_type=fav_add”:
This function apparently allows setting a “favorite help page” in the UI. The “help_page” form value is copied with strcat to a 1028-byte array on the stack. If the submitted “help_page” is any longer than this, it overflows that array and overwrites other control information on the stack such as the return address.
In total, we found and reported 13 stack and heap overflows similar to the above. Thanks to the gdbserver we bundled in our custom firmware, we verified that the majority lead to trivial and direct control of the program counter, the first step of arbitrary code execution.
Putting everything together
We wanted to legitimize our memory corruption findings by fully exploiting at least one of them.
We discovered that the stack was executable, through no fault of the developers: the ARM CPU in use did not have the “XN” feature available. This would make exploitation easier. On the flip side, we found that ASLR was enabled. To work around this, we used the arbitrary file read vulnerability, detailed above, to read /proc/<pid>/maps for every possible PID until httpd’s memory map was found. This gave us the starting address of the stack, however, we still did not know the address of anything within the stack. We determined that this was not a problem: httpd is a single-threaded loop that accepts requests sequentially; each vulnerable handler function had a consistent stack “depth.” Each handler’s stack “depth” could be worked out ahead of time, by hand. When running the exploit, we would simply add this known stack “depth” to the stack base address determined from /proc/<pid>/maps. This was all that was needed to work around the ASLR on httpd.
From there, we found and adapted some existing ARM bindshell shellcode, wrapped it up into a Python script, and were able to go from a user login to a root shell:
We added a few other features to this Python script, including the dumping of admin hashes from a user login, and an arbitrary file read utility:
As part of this blog we are publishing this script and it can be found here: https://github.com/tenable/poc/blob/master/cisco/spagett.py
In summary, we were able to take the lower-privilege “cisco” user, leak the “admin” user’s password hash and then “pass-the-hash” to elevate our privileges. Separately, we were able to use an arbitrary file read to defeat ASLR, and then exploit a stack overflow to achieve code execution as root.
We were unable to find any serious pre-auth vulnerabilities in the Web UI, which is very encouraging, but could be the subject of further research.
In total, 19 CVEs were issued across 7 Cisco security advisories, and all are expected to be fixed in the upcoming SPA 100 Series 1.4.1 SR5 firmware release:
Tenable also issued a security advisory and that is available at https://www.tenable.com/security/research/tra-2019-44. Nessus plugin 129982 will find any affected devices.
Thank you to Cisco PSIRT for working with us on the disclosure.