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.
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