ASUS router's ping function

ASUS routers have diagnostic tools built into their dashboard which allows authenticated web users to execute ping and netstat commands and have the output be displayed on the webpage. Naturally, this seems like a really good entry point to perform command injection, have the router execute cat /tmp/etc/shadow and display the output. Therefore, I decided to look into how the ping function is implemented.

image

The first step involves downloading the router firmware from ASUS's website and using binwalk to unpack the firmware. binwalk detected a squashfs file system, which is what we are looking for. sqaushfs is a read only file system, perfect for embedded devices since we do not store files on routers. After searching through the filesystem, I found the page responsible for the ping function located at /www/Main_Analysis_Content.asp. Much of the page is plain HTML/JS so there might have been no need to get into the firmware at all. The pertinent code in the file is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function onSubmitCtrl(o, s) {
    document.form.action_mode.value = s;
    updateOptions();
}
function updateOptions(){
    if(document.form.destIP.value == ""){
        document.form.destIP.value = AppListArray[0][1];
    }
    if(document.form.cmdMethod.value == "ping"){
        if(document.form.pingCNT.value == ""){
            document.form.pingCNT.value = 5;
        }
    document.form.SystemCmd.value = "ping -c " + document.form.pingCNT.value + " " + document.form.destIP.value;
    }
    else
        document.form.SystemCmd.value = document.form.cmdMethod.value + " " + document.form.destIP.value;
    if(validForm()){
        document.form.submit();
        ...
        setTimeout("checkCmdRet();", 500);
    }
}
function checkCmdRet(){
    $.ajax({
        url: '/cmdRet_check.htm',
        dataType: 'html',
        error: function(xhr){
        setTimeout("checkCmdRet();", 1000);
    },
    success: function(response){
        var retArea = document.getElementById("textarea");
        var _cmdBtn = document.getElementById("cmdBtn");
        if(response.search("XU6J03M6") != -1){
            ...
            retArea.value = response.replace("XU6J03M6", " ");
            return false;
        }
    ...
    }
}
...
<span><input class="button_gen_long" id="cmdBtn" onClick="onSubmitCtrl(this, ' Refresh ')" type="button" value="<#1672#>"></span>
<textarea cols="63" rows="27" wrap="off" readonly="readonly" id="textarea" ...>
    <% nvram_dump("syscmd.log","syscmd.sh"); %>
</textarea>


# Contents of cmdRet_check.htm
<% nvram_dump("syscmd.log",""); %>

The first impression looks good, input checking is done client side. My aim is to get to Line 16 so I just need to change cmdMethod variable to cat and destIp variable to /tmp/etc/shadow. However, it didn't work. Upon closer inspection, I noticed something interesting on Line 35. I decided to grep all firmware files for the string XU6J03M6. The only other place where the string was found was in the binary file /usr/sbin/httpd{s}. Initially, I thought that the file was a generic apache binary, however it was not so. The following strings were found in the binary.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[0x004043a0]> pd 10 @0x44b23c
            ;-- str.tmp__s:
            0x0044b23c     .string "/tmp/%s" ; len=8
            ;-- str.syscmd.sh:
            0x0044b244     .string "syscmd.sh" ; len=10
            ;-- hit0_0:
            0x0044b245      797363         unaligned
            0x0044b246      7363           unaligned
            0x0044b247      63             unaligned
            0x0044b248      6d642e73       invalid
            0x0044b24c      68000000       invalid
            ;-- str.s____tmp_syscmd.log_2__1____echo__XU6J03M6______tmp_syscmd.log:
            0x0044b250     .string "%s > /tmp/syscmd.log 2>&1 && echo 'XU6J03M6' >> /tmp/syscmd.log &\n" ; len=67
            0x0044b293      00             unaligned
            ;-- str.tmp_syscmd.log:
            0x0044b294     .string "/tmp/syscmd.log" ; len=16


[0x004043a0]> pd 20 @0x44e118
            ;-- str.httpd__Invalid_SystemCmd:
            0x0044e118     .string "[httpd] Invalid SystemCmd!\n" ; len=28
            ;-- hit2_2:
            0x0044e128      53797374       jalx 0x1cde54c
            0x0044e12c      656d436d       invalid
            0x0044e130      64210a00       invalid
            ;-- str.Main_Netstat_Content.asp:
            0x0044e134     .string "Main_Netstat_Content.asp" ; len=25
            0x0044e14d      000000         unaligned
            0x0044e14e      0000           unaligned
            0x0044e14f      00             unaligned
            ;-- str.netstat:
            0x0044e150     .string "netstat" ; len=8
            ;-- str.Main_Analysis_Content.asp:
            0x0044e158     .string "Main_Analysis_Content.asp" ; len=26
            0x0044e172      0000           unaligned
            0x0044e173      00             unaligned
            ;-- str.ping:
            0x0044e174     .string "ping" ; len=5
            0x0044e179      000000         unaligned
            0x0044e17a      0000           unaligned
            0x0044e17b      00             unaligned
            ;-- str.traceroute:
            ;-- hit1_0:
            0x0044e17c     .string "traceroute" ; len=11
            0x0044e187      00             unaligned
            ;-- str.nslookup:
            0x0044e188     .string "nslookup" ; len=9
            0x0044e191      000000         unaligned

I telneted into the router and confirmed that /tmp/syscmd.log was being populated with the output of the command. XU6J03M6 was used to mark the end of the file. I was able to get output from other commands to display on the webpage by writing into /tmp/syscmd.log while the ping was ongoing. However, I could not find syscmd.sh anywhere on the file system even when ping was running. I believe the httpd binary does further whitelisting of SystemCmd as evidenced by the string [httpd] Invalid SystemCmd! which explains why cat fails to work. Without further reverse engineering, there is no way to find out, I was unable to find the function making the system call. The whitelisting seems relatively secure, I have tried the following without any success.

  • ping -c5 google.com;uname -a
  • ping -c5 google.com\nuname -a
  • ping -c5 google.com(null byte)uname -a