D-Link登录信息泄露漏洞复现

漏洞编号CVE-2018-7034,影响D-Link和TrendNet的一些老设备。

固件包(见附件):TEW751DR_FW103B03.bin

这里分析的是TrendNet TEW751的固件,此外D-Link的一些老设备,如DIR645,DIR815等也都受该漏洞影响。

固件分析

首先解包一下固件:

根据相关报告描述,存在漏洞的文件是 /htdocs/web/getcfg.php

这里通过 $AUTHORIZED_GROUP 变量判断用户权限
$GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ","); 中,$GETCFG_SVC 是通过 POST 传入的 $_POST["SERVICES"] 所以这里文件的加载是我们可控的,也就是我们可以控制 /htdocs/webinc/getcfg/ 这个路径下的文件加载,这显然存在一个我们可以利用的地方,所以我们需要进入到 else分支 中,也就是说我们的 $AUTHORIZED_GROUP 变量应该大于等于0。

我们进入这个路径 /htdocs/webinc/getcfg/ 看看有什么文件是我们可以利用的

关注到 /htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml.php 这个文件:

这段代码的作用是遍历系统路径 /device/account/entry 下的所有账户条目,并且使用echo输出,并且echo输出的条目包括用户名和密码,所以我们可通过这个加载文件泄露账号

但是,我们首先还是得先知道如何绕过全局变量 $AUTHORIZED_GROUP >= 0 的检查
我们在解包的固件目录里中使用命令 grep -ra 'AUTHORIZED_GROUP' 来寻找都有哪些文件含有控制 $AUTHORIZED_GROUP 变量的功能

主角登场!

漏洞分析

我们去逆向分析一下 cgibin 文件

由于这里的 webserver 运行的是php脚本,那么这个二进制文件中重点的就是处理 php 语言的部分,也就是 phpcgi,我们进入 phpcgi_main

进入 phpcgi_main 之后我们分析一下下面这几个函数

sobj_new() 的作用是创建一个结构体,用于存放之后程序解析出来的各个字段:

sobj_add_string(v7, *(a2 + 4)); 的作用就是将 *(a2 + 4) 存储的内容放入到 v7 这个结构体中,其实就是分配一个空间存放解析的URL字符串,然后,下面的for循环就是在读取我们的环境变量并拼接起来

下面编写shell脚本来使用qemu进行动态调试

编写start.sh脚本

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
#!/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

echo "run ok"
rm -f ./qemu

编写gdb启动脚本

1
2
3
4
5
6
7
# 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

然后执行如下命令进入gdb调试:

1
2
sudo ./start.sh
gdb-multiarch -x mygdb.sh

我们在 sobj_add_string 函数处打上一个断点随后跟进到此处,这样我们就可以看到这里的几个参数,而它的第二个参数也就是 *(a2 + 4) 显示为 phpcgi 这个字符串,也就是说在开头的 sobj_add_string(v7, *(a2 + 4)); 它将 phpcgi 这个字符串写入了 v7 这个结构体中:

然后我们在 phpcgi_main 打个断点,继续往下走,往下走就对应进入ida中的这个for循环:

我们进入for循环,看看变量 v6 的变化(下面是进入for循环但是还没有开始执行for循环的任何指令)

根据图中信息 v6 的地址应该为 0x438008 (此时v6是这个函数中的第一个参数,对应mips汇编就是a0这个寄存器)

下面我们直接跳过 for ( i = a3; *i; ++i ) 来看结果

从上面这张图中我们可以看到参数是以键值对的形式存储并且以换行符 \n 分割

而这个结构体是用于存储我们传入的数据包中的内容,如果我们构造并传入一个 AUTHORIZED_GROUP=1,这样就可以在给用我们的值去顶替掉原来的 AUTHORIZED_GROUP 赋值程序所赋的值。

我们再继续往下看:

其中 v9 获取了 REQUEST_METHOD 的值,随后判断是否使用 HEADGET 类型传参,如果是,则将 sub_405CF8 函数地址赋值给 v11,如果使用的是 POST 传参那么就会将 sub_405AC0 函数地址赋值给 v11,而显然我们使用的是 POST 传参方式,也就是说我们的 v11sub_405AC0 函数地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  v9 = getenv("REQUEST_METHOD");
v10 = v9;
if ( !v9 )
goto LABEL_20;
if ( !strcasecmp(v9, "HEAD") || !strcasecmp(v10, "GET") )
{
v11 = sub_405CF8;
goto LABEL_13;
}
if ( strcasecmp(v10, "POST") )
{
LABEL_20:
v5 = -1;
goto LABEL_21;
}
v11 = sub_405AC0;

继续往下走会进入到 cgibin_parse_request 函数中,这里获取了 CONTENT_TYPECONTENT_LENGTH 两个环境变量,所以我们的 start.sh 脚本中也需要设置对应的 -E CONTENT_TYPE-E CONTENT_LENGTH 这里需要注意 CONTENT_LENGTH 得和我们输入的长度一致,否则可能发生错误

再进入到 parse_uri ,可以看到开头获取了 REQUEST_URI 如果我们的 start.sh 脚本中没有设置 -E REQUEST_URL
那么后续就会导致获取失败,从而导致程序异常退出

回到 cgibin_parse_request 函数中,我们继续往下看,可以看到下面有一个 strncasecmp 要求让 content_typev14 的相同才可以进入其中,否则则会执行 return -1

我们在 strncasecmp 处下断点看到这里的 v14application/

呃呃呃,这里没有搞明白QAQ,下次再补了


D-Link登录信息泄露漏洞复现
https://zer0ptr.github.io/2026/06/07/iot-dlink-cve-2018-7034/
作者
zer0ptr
发布于
2026年6月7日
更新于
2026年6月8日
许可协议