Monday, August 31, 2015

Examining Router Firmware

My last post got me thinking about whether the CSRF bug that sonar relies on in its example has been fixed. For this post, I decided to see if I could figure out the changes that Asus made to the firmware to determine if the sonar fingerprint might be affected by the bug.
So I went to Asus' site and pulled up the firmware download page for the RT-N66U (the router in the example):

Looks like there was a firmware update in January 2015 that fixed some CSRF issue. I wonder what changed between version 3657 and 3754 of the firmware. We will not look at version 3715 since that firmware was region-specific. This is probably because the frequencies used for WiFi in Japan differ from other regions where this device is sold. I do not have one of these routers to test, but maybe we can peek inside to see what makes these firmware images tick.
Let's take a moment to talk about the firmware we commonly see on consumer routers. Router technology has improved greatly over the last few years to keep up the demands of the modern internet user (high bandwidth, multiple simultaneous users and devices, and new technologies, like media sharing). The processing power of a modern consumer grade router eclipses that of routers just a few years ago. As an example, the RT-N66U features a Broadcom 4706 CPU running at 600 MHz, 32MB of flash memory and 256MB of RAM. It is like a small computer. This is true even on the software side. Many consumer routers are built on top of Linux. That will hopefully make our analysis a bit easier.
600MHz does not sound like a lot when newer computer CPUs run at 4.0GHz out of the box. However, remember that a computer CPU is general purpose, whereas the CPUs used in networking gear are purpose built for the task, so they do not need as much horsepower. Newer 802.11ac routers have CPUs with multiple cores and even more RAM to deal with the number of connections they have to track and packets they have to route. Given everything they have to do and the hardware they have, routers are sometimes designed with security as an afterthought. In addition, routers are expected to just work which means that usability and number of features sometimes trump security.
Today, we are going to see if we can find the improvements made between the two firmware versions.
I am going to use Craig Heffner's binwalk. I have prepared a Linux VM with binwalk and the two firmware images. You could do this in Windows as well, but binwalk has experimental support. OS X support seems to be good though. Since spinning up a Linux VM is not a big effort, we will go that route. The binwalk install document walks through the dependencies you need to have installed before running the software.
Before we get into running the software, let's take an educated guess at what we are looking at. Since Asus has GPLed code on their site for the router, I am willing to bet that they make use of some open source software. While the router could be using something like vxWorks, it features "Samba and FTP server with account management." Samba is a Linux / UNIX tool for Windows interoperability, so our hypothesis that the device is based on some sort of Linux could hold water. The firmware has a .trx extension which does not immediately tell us anything about the file. Let's see what binwalk has to say:

So we see the linux kernel offset (0x12E470), so we are likely dealing with a Linux-based firmware image. The second line tells us that we are dealing with an LZMA compressed file. LZMA stands for the Lempel–Ziv–Markov chain Algorithm and is a way to compress files, similar to ZIP or RAR. After that, we see the SquahFS filesystem. SquahFS is a file system typically used in embedded devices like consumer routers because of its low overhead. It is also read-only which protects the integrity of the running system.
Let's extract the contents of the firmware and see what we can find. To do this, we run binwalk -e against the trx file:
In the folder that binwalk made, we see the squashfs-root folder which sounds like it could have the files we want to examine.
Let's take a look at what it contains:
That looks like the root of a *nix filesystem. We could spend a long time looking through everything, but we want to see the CSRF mitigations, so we will focus on that. We will extract the other firmware image (FW_RT_N66U_30043763754.trx) and see if we can spot any changes.
Since we are looking for a CSRF vulnerability, the www folders would be a good place to start. We can use the diff command to see what files differ.
Let's run:
diff -rq --no-dereference _FW_RT_N66U_30043763754.trx.extracted/squashfs-root/www _FW_RT_N66U_30043763657.trx.extracted/squashfs-root/www

-rq tells diff to work recursively and only show differences. You may have noticed the symlinks in the root of the squashfs directory. There are others throughout the squashfs filesystem. Those are populated when the router is on. Right now, they will not go anywhere, so we do not want diff to try to follow those symlinks. We use --no-dereference to tell diff not to follow symlinks.
So it seems like three files have changed:
  • www/Advanced_WAN_Content.asp
  • www/Main_AdmStatus_Content.asp
  • www/state.js
Let's see how they changed. We will take them in order.
diff _FW_RT_N66U_30043763657.trx.extracted/squashfs-root/www/Advanced_WAN_Content.asp

_FW_RT_N66U_30043763754.trx.extracted/squashfs-root/www/Advanced_WAN_Content.asp

163,165d162
< if(document.form.ttl_inc_enable.value != '<% nvram_get("ttl_inc_enable"); %>'){
< document.form.action_script.value += ";restart_firewall";
< }


The lines around the file:

function applyRule(){
if(ctf.level2_supprot && (based_modelid == "RT-AC68U" || based_modelid == "RT-AC56U") && ctf.changeType()){ //To notify
 if using Level 2 CTF and change wan type to PPPoE、PPTP、L2TP
if((wan_proto_orig == "dhcp" || wan_proto_orig == "static") && ctf.getLevel() == 2){
if(confirm("Level 2 CTF can not be supported under PPPoE、PPTP or L2TP. If you want to switch to Level 1 CTF, please click confirm ")){
document.form.ctf_disable_force.value = 0;
document.form.ctf_fa_mode.value = 0;
}
else{
return false;
}
}
}
if(validForm()){
showLoading();
inputCtrl(document.form.wan_dhcpenable_x[0], 1);
inputCtrl(document.form.wan_dhcpenable_x[1], 1);
if(!document.form.wan_dhcpenable_x[0].checked){
inputCtrl(document.form.wan_ipaddr_x, 1);
inputCtrl(document.form.wan_netmask_x, 1);
inputCtrl(document.form.wan_gateway_x, 1);
}
inputCtrl(document.form.wan_dnsenable_x[0], 1);
inputCtrl(document.form.wan_dnsenable_x[1], 1);
if(!document.form.wan_dnsenable_x[0].checked){
inputCtrl(document.form.wan_dns1_x, 1);
inputCtrl(document.form.wan_dns2_x, 1);
}
if(document.form.ttl_inc_enable.value != '<% nvram_get("ttl_inc_enable"); %>'){
document.form.action_script.value += ";restart_firewall";
}
if(ctf.changeType() && ctf.getLevel() == 2 && ctf.level2_supprot){
FormActions("start_apply.htm", "apply", "reboot", "<% get_default_reboot_time(); %>");
}
document.form.submit();
}
}

The brackets pointing towards the left indicates that the three lines were in the first file, which in our case is the older one (3657). So it looks like this piece of code compares the value of the TTL_Increment_Enable variable against what is in NVRAM (non-volatile RAM). This is where the router stores its settings. Remember that the file system is read-only, so it needs somewhere to store things that change, like settings. If there is a change (they are not equal), restart_firewall is added to the form action. Here is the relevant part of the code for that (from the same file):

<form method="post" name="form" id="ruleForm" action="/start_apply.htm" target="hidden_frame">
<input type="hidden" name="productid" value="<% nvram_get("productid"); %>">
<input type="hidden" name="support_cdma" value="<% nvram_get("support_cdma"); %>">
<input type="hidden" name="current_page" value="Advanced_WAN_Content.asp">
<input type="hidden" name="next_page" value="Advanced_WAN_Content.asp">
<input type="hidden" name="group_id" value="">
<input type="hidden" name="modified" value="0">
<input type="hidden" name="action_mode" value="apply">
<input type="hidden" name="action_script" value="restart_wan_if">
<input type="hidden" name="action_wait" value="5">
<input type="hidden" name="first_time" value="">
<input type="hidden" name="preferred_lang" id="preferred_lang" value="<% nvram_get("preferred_lang"); %>">
<input type="hidden" name="firmver" value="<% nvram_get("firmver"); %>">
<input type="hidden" name="lan_ipaddr" value="<% nvram_get("lan_ipaddr"); %>" />
<input type="hidden" name="lan_netmask" value="<% nvram_get("lan_netmask"); %>" />
<input type="hidden" name="wan_pppoe_username_org" value="<% nvram_char_to_ascii("", "wan_pppoe_username"); %>" />
<input type="hidden" name="wan_pppoe_passwd_org" value="<% nvram_char_to_ascii("", "wan_pppoe_passwd"); %>" />
<input type="hidden" name="ctf_fa_mode" value="<% nvram_get("ctf_fa_mode"); %>">
<input type="hidden" name="ctf_disable_force" value="<% nvram_get("ctf_disable_force"); %>">

There is nothing here (or elsewhere in the file) that would prevent CSRF. Typically, there is some sort of token (a randomly generated string, hopefully generated in a cryptographically strong way). The token is usually embedded in the page as a hidden form field. Essentially, any way of maintaining state should work as long as the user cannot fake it. It looks like the fix was to remove the functionality from the page. That sounds a like security through obscurity. The underlying vulnerability has not really been addressed. The solution would be to implement some sort of robust state tracking.
Let's take a look at the next file: Main_AdmStatus_Content.asp

[user@localhost firmware]$ diff _FW_RT_N66U_30043763657.trx.extracted/squashfs-root/www/Main_AdmStatus_Content.asp _FW_RT_N66U_30043763754.trx.extracted/squashfs-root/www/Main_AdmStatus_Content.asp
98c98
< 
---
> 

So the removed part is
<% nvram_dump("syscmd.log","syscmd.sh"); %>
. Here is the code surrounding the change:

<iframe name="hidden_frame" id="hidden_frame" src="" width="0" height="0" frameborder="0"></iframe>
<form method="GET" name="form" action="/apply.cgi" target="hidden_frame">
<input type="hidden" name="current_page" value="Main_AdmStatus_Content.asp">
<input type="hidden" name="next_page" value="Main_AdmStatus_Content.asp">
<input type="hidden" name="group_id" value="">
<input type="hidden" name="modified" value="0">
<input type="hidden" name="action_mode" value="">
<input type="hidden" name="action_script" value="">
<input type="hidden" name="action_wait" value="">
<input type="hidden" name="first_time" value="">
<input type="hidden" name="preferred_lang" value="<% nvram_get("preferred_lang"); %>">
<table class="formTable" width="60%" border="1" align="center" cellpadding="4" cellspacing="0" bordercolor="#6b8fa3">
<thead>
<tr>
<td colspan="2" height="30">System Command</td>
</tr>
</thead>
<tbody>
<tr>
<td id="cmdTd">
<input type="text" width="90%" name="SystemCmd" value="" class="input_option" style="padding-left:3px">
<input class="button_gen" id="cmdBtn" onClick="onSubmitCtrl(this, ' Refresh ')" type="submit" value="<#797#>" name="act
ion">
<img id="loadingIcon" style="display:none;" src="/images/InternetScan.gif"></span>
</td>
</tr>
<tr>
<td>
<textarea cols="80" rows="27" wrap="off" readonly="readonly" id="textarea" style="width:99%;font-family:Courier New, Courier, mono; font-size:11px;background:#475A5F;color:#FFFFFF;"></textarea>
</td>
</tr>

This might be an effort to eliminate information leakage. So, instead of a text area that presumably shows a log of system commands run on the router, there is now nothing.
The final file is state.js:

 diff _FW_RT_N66U_30043763657.trx.extracted/squashfs-root/www/state.js _FW_RT_N66U_30043763754.trx.extracted/squashfs-root/www/state.js

The changes in this file seem to affect the pages that are displayed on the router webpages. TR-069 is a protocol for managing end-user devices remotely. It does not appear that this would protect against CSRF.
All in all, the CSRF vulnerabilities do not seem to be that terrible. The first allows a malicious person to restart the firewall, and the second allows someone to see a certain system log. I am not giving Asus a pass, but these vulnerabilities could be much worse. It appears that some of these vulnerabilities fall under CVE-2014-7270, so at least Asus is making an attempt to fix them. Unfortunately, there are likely other vulnerabilities in these routers since the fixes have not addressed the root vulnerability as discussed above. That brings me back to sonar. Here is the request that sonar takes advantage of:

POST /start_apply.htm HTTP/1.1
Host: 192.168.1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:40.0) Gecko/20100101 Firefox/40.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.1/Advanced_DHCP_Content.asp
Cookie: apps_last=; dm_install=no; dm_enable=no
Authorization: [REDACTED]
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 519

productid=RT-N66U&current_page=Advanced_DHCP_Content.asp&next_page=Advanced_GWStaticRoute_Content.asp&next_host=192.168.1.1&modified=0&action_mode=apply&action_wait=30&action_script=restart_net_and_phy&first_time=&preferred_lang=EN&firmver=3.0.0.4&lan_ipaddr=192.168.1.1&lan_netmask=255.255.255.0&dhcp_staticlist=&dhcp_enable_x=1&lan_domain=&dhcp_start=192.168.1.2&dhcp_end=192.168.1.254&dhcp_lease=86400&dhcp_gateway_x=&dhcp_dns1_x=8.8.8.8&dhcp_wins_x=&dhcp_static_x=0&dhcp_staticmac_x_0=&dhcp_staticip_x_0=&FAQ_input=

Since Advanced_DHCP_Content.asp was not changed, it was either already fixed before version 3657 of the firmware, or it is still vulnerable as of version 3754. There have been one or two more new releases as of this writing, but we will not take a look at them in this post.

Next Steps

For tools like sonar

Since firmware updates happen, I think a good way to target vulnerable versions of firmware would be to target changed files. For example, if we wanted to take advantage of the vulnerability in Advanced_WAN_Content.asp, we could get a hash of the vulnerable version(s) and check for those hashes on the files that sonar grabs successfully from the server (maybe using something like node.js to do the hashing).

For Users Worried About CSRF

If you have one of these routers and you are worried about CSRF, there are a few options, each with their benefits and drawbacks:
  1. Keep your firmware up to date by periodically checking the manufacturer's website for updates: If the manufacturer is timely about fixing vulnerabilities, this might be a good option. However, not every vulnerability is fixed with each release, so the user has to stay on top of new releases. Also, flashing firmware is not something everyone is comfortable with.
  2. Use third-party firmware: Third-party firmware could be a good option because the author(s) may prioritize security and add other features. However, as with manufacturer firmware updates, flashing the firmware is not an option for everyone.
  3. When administering your router, clear your cookies or use Private Browsing: I talked about this in my last post, but I think it will really help mitigate this issue. It only works as long as the user does not visit any other site that might exploit the vulnerability. For the time the user is in that Private Browsing session, the user is authenticated, and therefore potentially vulnerable. The drawback is that the user has to pay attention to what they are doing so that they do not visit another site in the Private Browsing session or forget to close the Private Browsing session when they are done.

Conclusion

We had some fun examining router firmware images and looking around for vulnerabilities. Give it a shot, and share your findings. Thanks for reading!

2 comments:

  1. Excellent writeup! I've never done any of this before, so it was very cool seeing how you used binwalk and diff to locate the changes. A question on the private browsing thing -- if a user were administering the router via private browsing, would it still be vulnerable to vulnerable to CSRF from the non-private browser windows? I would hope that the private browsing cookies would be segregated from the normal cookie store, but I guess it probably depends on the browser.

    ReplyDelete
  2. Hi simulation95243! Thanks for reading and for your kind words. Some browsers store cookies in memory (like IE with InPrivate browsing, see here: http://windows.microsoft.com/en-us/windows/what-is-inprivate-browsing#1TC=windows-7). I imagine Firefox and Chrome do something similar. So, in order to access "private" cookies from "non-private" windows would likely take an exploit or something that can read other parts of the process' memory.

    This is something that I did not mention in my post, but I have not looked into what browsers isolate data created by plugins like Flash. According to Mozilla, they do not keep plugin data (https://support.mozilla.org/en-US/kb/private-browsing-use-firefox-without-history#w_what-does-private-browsing-not-save). It is not clear what IE does with plugin data, same with Chrome (https://www.google.com/chrome/browser/privacy/).

    ReplyDelete