Thursday, February 20, 2014

Breaking down the Asus router bug

Tuesday I wrote about an apparent bug in the ASUS RT-AC66R and RT-AC66U routers that prevents them from recognizing a new firmware version is available. After investigating further, the root cause turned out to be a simple matter of ASUS not updating the file on their live updates server that reports the latest firmware for each router model. Depending on the specific model, ASUS wireless routers download a text file (strangely labeled as a zip file) from http://dlcdnet.asus.com/pub/ASUS/LiveUpdate/Release/Wireless or /Wireless_SQ. This file lists all supported models of router, along with the latest firmware version for each. These files appear to have not been updated since October 9, 2013, the date of the 3.0.0.4.374.979 firmware release.

This report is one in a series I have written on ASUS wireless router features and vulnerabilities. Others of interest:

Now for a slightly technical explanation, which sheds some light on the inner workings of ASUS wireless routers.

To check for updates through the admin GUI, one would follow a few links on the admin GUI or simply browse to http://[router]/Advanced_FirmwareUpgrade_Content.asp and press the "Check" button. Some magic happens behind the scenes, then the router either reports that it has the latest version, or prompts you to upgrade.


That magic begins with some JavaScript in Advanced_FirmwareUpgrade_Content.asp. If you were to view the source of that page, you will see something like this:


var webs_state_update = '1';
var webs_state_upgrade = '';
var webs_state_error = '0';
var webs_state_info = '3004_374_979-gbc8961e';
.
.
.
function detect_firmware(){
.
.
.
  if(isNewFW(webs_state_info)){
    $('update_scan').style.display="none";
    $('update_states').style.display="none";
    if(confirm("There is a new firmware version available. Upgrade?")){
      document.start_update.action_mode.value="apply";
      document.start_update.action_script.value="start_webs_upgrade";
      document.start_update.submit();
      return;
    }
  }
  else{
    $('update_states').innerHTML="The router's firmware is current.";
    $('update_scan').style.display="none";
  }
 }


This is in turn dependent on the isNewFW() function, which is contained within the included file http://[router]/state.js:


var isNewFW = function(FWVer){
  var Latest_firmver = FWVer.split("_");
  if(typeof Latest_firmver[0] !== "undefined" && typeof Latest_firmver[1] !== "undefined" && typeof Latest_firmver[2] !== "undefined"){
    var Latest_firm = parseInt(Latest_firmver[0]);
    var Latest_buildno = parseInt(Latest_firmver[1]);
    var Latest_extendno = parseInt(Latest_firmver[2].split("-g")[0]);
    current_firm = parseInt('3.0.0.4'.replace(/[.]/gi,""));
    current_buildno = parseInt('374');
    current_extendno = parseInt('4422-gc83c78f'.split("-g")[0]);
    if((current_buildno < Latest_buildno) || (current_firm < Latest_firm && current_buildno == Latest_buildno) || (current_extendno < Latest_extendno && current_buildno == Latest_buildno && current_firm == Latest_firm)) {
      return true;
    }
  }
  return false;
}


But we are missing something. Where do the values for webs_state_update, webs_state_upgrade, webs_state_error, and webs_state_info come from? And where do the values isNewFW() compares them to come from? Something is happening on the backend before the web service serves up these pages to the browser.

So we dig deeper. Telnet into the router (which requires enabling the telnet daemon) and poke around. It turns out the asp and js files reside at /www, and they look a bit different from the client-side appearance.

Specifically, instead of hard values for state and version information, they look a bit like this:


var webs_state_update = '<% nvram_get("webs_state_update"); %>';
var webs_state_upgrade = '<% nvram_get("webs_state_upgrade"); %>'
var webs_state_error = '<% nvram_get("webs_state_error"); %>';
var webs_state_info = '<% nvram_get("webs_state_info"); %>';


nvram_get is not found on the filesystem, but nvram is, and it accepts a parameter of get. Each of the variables above is a valid argument for nvram get. So that explains where the web configuration files get the values to evaluate. It also explains why the web config script believes the firmware is up to date: 


admin@RT-AC66R:/bin# nvram get webs_state_info
3004_374_979-gbc8961e


isNewFW() is comparing the existing firmware (currently 3004_374_2050) against the value returned by nvram, and since 2050 is newer than 979, the web script says we're good. That would be fine, except that ASUS released 3004_374_4422 last week to fix a serious flaw in the AiCloud service. So, is 3004_374_979-gbc8961e erroneously hard-coded into this version of the firmware?

That was my first though, but no. I deleted the webs_state_info value from nvram and ran the check again, and the value was set back to the erroneous value. It was getting that value from somewhere. Back to the drawing board.

I did not have a non-switching hub handy, so could not easily insert a sniffer between the router and my ISP. Netstat on the router never showed any connection on the WAN side - it only showed LAN-side connections. But the router log give me a clue: at the moment that I ran the update check, the following showed up in the log:


Feb 19 11:46:04 rc_service: httpd 323:notify_rc start_webs_update


Back to telnet. I found a script /usr/sbin/webs_update.sh. This script uses wget (a handy command-line "web browser") to retrieve the file http://dlcdnet.asus.com/pub/ASUS/LiveUpdate/Release/Wireless/wlan_update_v2.zip
(note that webs_update.sh has some logic to select from among 4 files, depending on the state of the router). The contents of this file are:

RT-AC68U#FW3004100#EXT0-g0f3bacc#URL#UT4208
RT-AC66U#FW3004374#EXT979-gbc8961e#URL#UT4208
RT-AC56U#FW3004374#EXT339-gfa80738#URL#UT4208
RT-AC53U#FW3004100#EXT0-g0f3bacc#URL#UT4208
RT-AC52U#FW3004100#EXT0-g0f3bacc#URL#UT4208
RT-N66U#FW3004374#EXT979-gbc8961e#URL#UT4208
RT-N65U#FW3004374#EXT1317-g17e5f52#URL#UT4208
RT-N56U#FW3004374#EXT979-gbc8961e#URL#UT4208
RT-N53#FW3004374#EXT311-g6d4f56e#URL#UT4208
RT-N18U#FW3004100#EXT168-g50fb114#URL#UT4208
RT-N16#FW3004374#EXT979-gbc8961e#URL#UT4208
RT-N15U#FW3004374#EXT168-g50fb114#URL#UT4208
RT-N14U#FW3004374#EXT1667-g0005ce2#URL#UT4208
RT-N14UHP#FW3004374#EXT1631-gf2dd1d9#URL#UT4208
RT-N10PV2#FW3004100#EXT168-g50fb114#URL#UT4208
DSL-N55U#FW3004374#EXT1397-g3827650#URL#UT4208
DSL-N55U-B#FW3004374#EXT1397-g3827650#URL#UT4208
WL-330NUL#FW3-0-0-30#EXT0-46e615099#URL#UT1-0-3-2
DSL-N66U#FW1063#EXT#URL#UT4208
DSL-N55U-C1#FW1049#EXT#URL#UT4208
DSL-N16U#FW1059#EXT#URL#UT4208
DSL-N14U#FW1058#EXT#URL#UT4208
DSL-N12E-C1#FW1058#EXT#URL#UT4208
DSL-N12U-C1#FW1058#EXT#URL#UT4208
DSL-N10-C1#FW1049#EXT#URL#UT4208
AiCam#FW10109_194#EXT#URL#UT

This explains the failure to detect the new firmware: ASUS never updated the server files, so the server was reporting that a firmware 3 revs back was the latest available.

More significantly though, I discovered that the router update scripts (/usr/sbin/webs_update.sh and /usr/sbin/webs_upgrade.sh) use a cleartext, non SSL query to http://dlcdnet.asus.com/pub/ASUS/wireless/ASUSWRT/ for updated firmware, and that there is no validity check after downloading. The only check the router performs is to confirm the downloaded firmware is appropriate to the router model. There is no check to verify the firmware in fact came from ASUS and not a malicious third party. In this scenario, if an attacker inserted themselves between the router and the ISP, they could easily conduct a MITM attack to provide a malicious firmware upgrade to consumers.

At a minimum, ASUS needs to provide sha1 or md5 hashes for all updates, and needs to keep the wlan_update files current with the latest firmware revision. It would be preferable to also sign the updates with a digital certificate, and to have the router verify the certificate before installing updates.

So for now, my recommendation if you use an ASUS wireless router with the stock ASUS firmware, is to manually install the newest firmware available from support.asus.com/download. For the more technically inclined, DD-WRT or Tomato are open-source alternatives - just keep in mind that if you don't follow the instructions correctly, loading an alternative firmware is a great way to brick a device.

02/24/2014 update - It looks like Asus has updated their server table, and released a newer firmware update that gives a notification when a newer firmware is available. Definitely a step in the right direction. I'll be curious to see how quickly (or if) they address the lack of code validation. It is still possible to serve a malicious firmware to an ASUS RT- series router.

Version 3.0.0.4.374.4561
DescriptionASUS RT-AC66R Firmware version 3.0.0.4.374.4561
Security related issues:
- Force changing FTP from anonymous to account mode after firmware upgraded.
- Patched IPv6 user interface from Merlin's build.

UI
- Fixed IE and Firefox compatibility issues.
- Network map will show notification when newer firmware available.
File Size
25,53 (MBytes)2014.02.21 update

Do you have something to add? A question you'd like answered? Think I'm out of my mind? Join the conversation below, reach out by email at david (at) securityforrealpeople.com, or hit me up on Twitter at @dnlongen