1. 程式人生 > 實用技巧 >XML 相關漏洞

XML 相關漏洞

XML基礎

基礎

https://www.cnblogs.com/l0nmar/p/13337996.html

一個XML示例:

<?xml version="1.0" encoding="utf-8"?>
<!--這裡是註釋-->
<books>
    <book id="b01">
        <name>Python黑客程式設計從入門到入獄</name>
        <author>張三</author>
        <price>$20.00</price>
    </book>
</books>

如上程式碼,第一行是XML文件的宣告,由“”結尾,其中的內容是對本xml文件所使用的版本 “version”和編碼“encoding”的宣告,version一般情況下都是1.0,因為目前為止,xml只有這一個版本。

第二行是註釋,不多做解釋。

從第三行開始,就是XML文件的主要內容了,如程式碼中所示的“”,是本文件的根元素,“”是“”的子元素,而“”也都是子元素,但是是“”的子元素。

那麼我們可以將這個XML文件,視為是一個描述圖書的文件,它所描述的內容,包括了圖書的名字、作者和價格,如果使用程式對這個文件進行解析後,那麼這些資訊就可以更好的顯示在web頁面或者是應用程式中,方便使用者檢視。

XML的格式

a. 宣告資訊,用於描述xml的版本及編碼格式。

b. xml有且僅有一個根元素(可以理解為頂級的元素、沒有被其他元素包起來的元素)。個人理解:像是資料庫的表名

c. xml中大小寫敏感

d. 標籤是成對出現的,所有元素都必須有一個關閉標籤,而且要正確巢狀。

e. 屬性值要使用雙引號

f. 註釋的寫法。

<!--這是註釋 -->
g. 一個格式良好的xml檔案

<?xml version="1.0" encoding="utf-8"?>
<!--這裡是註釋-->
<books>
    <book id="b01">
        <name>Python黑客程式設計從入門到入獄</name>
        <author>張三</author>
        <price>$20.00</price>
    </book>
</books>

h. XML並不是讓使用者直接開啟的,而是讓別的語言來從檔案中讀取資訊的。至於為什麼可以直接用瀏覽器瀏覽,只是瀏覽器可以識別而已。

XML的屬性

擁有正確語法的 XML 被稱為"形式良好"的 XML。而判斷XML的語法是否合法,叫做XML驗證,是通過 DTD進行驗證的。
DTD:Document TypeDefinition 文件型別定義。用於約束xml的文件格式,保證xml是一個有效的xml,DTD分為內部和外部兩種。DTD定義在xml檔案中視為內部DTD;DTD定義在外部的dtd檔案中,視為外部DTD。
說的簡單一點,DTD就是對當前的XML文件做一個約束,DTD中定義了這個文件中的根元素是什麼,有幾個子元素,每個子元素能出現幾次,哪些元素有屬性,屬性的型別是什麼,屬性的預設值是什麼等等,如果後面的XML內容中,與DTD中的定義不符,如元素個數不符、元素名稱大小寫不符等,那麼XML檔案解析時就會報錯。

1)內部DTD的使用

內部DTD的定義

<!DOCTYPE 根元素 [元素宣告]>

元素宣告語法

[
<!ELEMENT 根元素 (子元素)>
<!ELEMENT 根元素的子元素 (子元素的子元素,子元素的子元素)>
<!ELEMENT 子元素 (資料型別)>
<!ELEMENT 子元素 (資料型別)>
]

元素宣告中的數量詞

"+" 表示出現一次或者多次
"?"表示出現0次或多次
"*"表示出現任意次。

屬性宣告語法

<!ATTLIST 元素名稱 屬性名稱 屬性型別 預設值>

示例:

<?xml version="1.0" encoding="utf-8"?>
<!--這裡是註釋-->
<!DOCTYPE books [
    <!ELEMENT books (book+)>
    <!ELEMENT book (name,author,price)>
    <!ATTLIST book id CDATA #REQUIRED>
    <!ELEMENT name (#PCDATA)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT price (#PCDATA)>
    
]>
<books>
    <book id="b01">
        <name>Python黑客程式設計從入門到入獄</name>
        <author>張三</author>
        <price>$20.00</price>
    </book>
</books>

如上,就是一個內部DTD的引用示例,在DTD定義中,要求根元素books的子元素book出現一次及以上,子元素book又有三個子元素,分別為name,author和price,然後聲明瞭元素book的id屬性,其型別是CDATA,並且是必須的(#REQUIRED),最後定義了book的三個子元素的資料型別為#PCDATA,這表示這三個元素標籤中的內容必須是文字,並能再出現子標籤。

2)外部DTD的使用:

首先需要建立一個外部的dtd檔案。內容中不需要包括<!DOCTYPE...>,直接<!ELEMENT...>,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<!ELEMENT books (book+)>
<!ELEMENT book (name,author,price)>
<!ATTLIST book id CDATA #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>

然後在XML文件中引入外部的DTD:
<!DOCTYPEbooks SYSTEM "xxx.dtd">
注意外部實體引用時的關鍵字“SYSTEM”,同時也可以使用“PUBLIC”這個關鍵字,這兩者的區別在於,SYSTEM表示私有的DTD,PUBLIC表示共有的DTD。

DTD實體

實體就像是變數,可以用於儲存資料,以便後續的使用。但它的功能又不僅僅是儲存,比如外部實體,除了可以儲存資料,還可以從遠端檔案或遠端網路中讀取內容或呼叫資料。

從實體被定義的位置來看,實體可以分為內部實體和外部實體,就像內部DTD和外部DTD一樣,內部實體,就是在XML文件內部的DTD進行定義的實體,外部實體就是定義在外部DTD檔案中然後被引用到當前XML中的實體。

(1) 內部實體宣告

宣告語法:<!ENTITY 實體名稱 "實體的值">

內部實體引用示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE books [
	<!ENTITY test "Hello World">
]>

<books>&test;</books>

(2) 外部實體宣告

宣告語法:

<!ENTITY 
實體名稱 SYSTEM "URI/URL"
>

宣告一個外部實體的關鍵在於“SYSTEM”這個關鍵字。SYSTEM在此意圖讓xml解析器知道,現在宣告的是一個外部實體,需要從後面的外部資源中獲取內容並存儲在內部實體,如果後面的外部資源的語法,存在特殊符號,那麼xml解析器會報錯。

外部實體引用可支援http,file等協議,不同的語言支援的協議不同,但存在一些通用的協議,比如http、file、ftp等,具體內容如下所示:

另外,從實體的引用方式來區分,實體又可以分為:一般實體、引數實體、預定義實體。

一般實體:General Entities,就是我們上面的示例中的實體,使用&進行引用

預定義實體:PredefinedEntities,就是xml本身對一些特殊字元進行了預定義,方便使用者直接引用,比如小於號,如果直接在xml文件中使用小於號,會被xml解析器視為標籤,從而引起解析錯誤。那麼此時就需要呼叫小於號所對應的預定義實體來引用:&#x3C。

引數實體:Parameter Entities,這也是XXE學習中的重點,在XXE利用中經常被使用。

(3) 引數實體

引數實體宣告:

內部:<!ENTITY % 實體名稱 "實體值">
外部:<!ENTITY % 實體名稱 SYSTEM  "URI">

引數實體應注意以下幾點:

(1)使用 % 實體名(這裡面空格不能少) 在 DTD 中定義,並且只能在 DTD 中使用 “%實體名;” 引用

(2)只有在 DTD 檔案中,引數實體的宣告才能引用其他實體

(3)和通用實體一樣,引數實體也可以外部引用

就是引數實體不能像普通實體那樣在xml文件內容中進行引用,它的引用範圍只在當前xml檔案的DTD宣告中,或者是當前的DTD檔案中

引數實體引用示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE books [
	<!ENTITY % xxe "hello">
	%xxe;

]>

<books></books>

XML注入

原理

比如:一個 web 應用,在進行使用者註冊時,選擇以 xml 來儲存資料到 xmldb 資料庫中,當用戶填寫使用者名稱,密碼和郵箱時,後臺儲存的檔案格式及內容如下

那麼攻擊者就可以在註冊的時候構造惡意的資料,假設他在使用者名稱與密碼的輸入框中輸入正常的文字,在最後的郵箱輸入框中輸入如下內容:

那麼就會多註冊一個名為admin的使用者

防禦

能夠進行XML注入攻擊的前提是,使用者能夠控制資料的輸入,程式沒有對輸入的內容進行過濾且拼接了資料

那麼相應的,破壞掉其中一個前提就可以進行防禦了,既然我們無法限制使用者的輸入,那麼就可以對資料進行過濾,將XML語言本身的“保留字元”進行過濾或者轉義即可。

XXE注入漏洞

介紹

XXE注入也是XML注入的一部分,但相較於普通的XML注入,XXE注入的攻擊面更廣,危害更大。

XXE注入(XML External Entity Injection) 全稱為 XML 外部實體注入

注入的物件 : XML外部實體

當遇見能夠解析XML內容的頁面時,如果能注入外部實體並且成功解析的話,這就會大大拓寬XML 注入的攻擊面

XXE型別

XXE的攻擊形式主要分為:帶內資料實體注入、基於錯誤的實體注入和帶外資料實體注入

帶內資料實體注入:in-band ,XML解析後的資料會直接顯示在螢幕上

基於錯誤:error-based,解析結果只有一大堆的錯誤

帶外資料:out-of-band,也叫XXE盲注,注入的XML解析後無任何輸出響應,必須執行一些帶外請求把資料提取出來。

PHP的XXE注入產生的條件:

a. Libxml的版本儘可能的低,libxml是PHP的xml解析庫,因為從2.8.0版本開始,libxml預設是不載入外部實體的,如果要使用較高版本的libxml的話,需要在編寫程式碼的時候對引數做設定。
b. 目標主機沒有禁用外部實體的引用。
c. 使用者可以控制xml的輸入內容

有回顯的本地檔案讀取

讀取檔案

測試程式碼:

<?php

    libxml_disable_entity_loader (false);
    $xmlfile = file_get_contents('php://input');
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
    $creds = simplexml_import_dom($dom);
    echo $creds;

?>

payload:

<?xml version="1.0" encoding="UTF-8"?>
<!-- payload 1 -->
<!DOCTYPE root [
	<!ENTITY xxe SYSTEM "file:///c:/windows/system.ini">
]>

<root>&xxe;</root>

這樣我們就讀取到了windows系統的system.ini的檔案內容。

但是這樣也不代表這個payload的就適用於任何情況 比如我們更換一個讀取的檔案xmltest2.txt,內容是

就會報錯

主要是因為我們要讀取的檔案內容中存在很多的特殊字元,大於號、小於號等

當xml的標籤內還存在小於號、大於號等特殊字元時,尤其是小於號,會被XML解析器誤認為是另一個標籤的開始,這樣就會造成解析的錯誤

所以我們就要想辦法繞過。這個時候我們就需要了解一下XML CDATA了

CDATA

XML CDATA:

所有XML文件中的文字均會被解析器解析, 只有CDATA中的文字會被解析器忽略

它可以使得使用其中的資料內容不會被xml解析器解析。然後我們再看其使用方式

CDATA 部分由 "" 結束:

例子:

<script>
<![CDATA[
function matchwo(a,b)
{
if (a < b && a < 0) then
  {
  return 1;
  }
else
  {
  return 0;
  }
}
]]>
</script>

CDATA 部分不能包含字串 "]]>"。也不允許巢狀的 CDATA 部分。

標記 CDATA 部分結尾的 "]]>" 不能包含空格或折行。

那麼瞭解了這些,我們就可以嘗試使用CDATA繞過

我們嘗試直接使用實體來進行拼接,但是測試失敗:

這說明我們的拼接方式不可行,我們現在使用的是一般實體,我們在前面的xml基礎知識中介紹過了,一般實體的引用是在xml文件內容中,既然在xml文件內容中拼接不可行,那再dtd中拼接可行嗎?我們再次進行嘗試,既然再dtd中拼接,那就需要用到引數實體了。

我們再次嘗試構造payload:

理論上,我們完美地將這幾個引數實體拼接了起來,並將值賦給了一般實體all,但是遺憾的是,我們的payload還是報錯了

那麼這又是為什麼呢?根據XML規範所描述:“在DTD內部子集中的引數實體呼叫,不能混摻到標記語言中”,這是什麼意思呢?就是不能在實際的標記語言中來呼叫引數實體,像我們這樣,就是在標記語言中進行呼叫:

但可以在同級別中被當作標記語言呼叫,就像是引數實體的引用,就是將呼叫當成了一個標記語言,像這樣:

也就是我們所構造的payload這種使用方式,不能在內部DTD中被這樣使用,但是幸運的是,XML規範還聲明瞭一點:“外部引數實體不受此限制”,這就告訴我們可以使用外部的DTD來構造payload,將我們的CDATA內容拼接起來:

DTD檔案的內容:

我們再次進行攻擊嘗試,成功讀取到檔案內容:

由於環境資源的關係,我們在進行攻擊時,所使用的外部dtd檔案,是本地環境的。但是在實際的攻擊情況下,這個DTD檔案應該是我們自己所掌握的主機的DTD檔案,檔案的內容是受我們所控的。

無回顯本地檔案讀取

但是,在實際情況中,大多數情況下伺服器上的 XML 並不是輸出用的,所以就少了輸出這一環節,這樣的話,即使漏洞存在,我們的payload的也被解析了,但是由於沒有輸出,我們也不知道解析得到的內容是什麼,因此我們想要現實中利用這個漏洞就必須找到一個不依靠其回顯的方法——外帶資料

先看一下漏洞示例:

相較於前面有回顯的漏洞程式碼,我們去掉了內容輸出的一部分。這樣,用之前的payload就沒有作用了:

有了前面使用外部DTD檔案來拼接內部DTD的引數實體的經驗,我們可以知道,通過外部DTD的方式可以將內部引數實體的內容與外部DTD宣告的實體的內容拼接起來,那麼我們就可以有這樣的設想:

我們可以在本地做一個埠監聽,然後利用payload來從目標主機讀取到檔案內容後,將檔案內容作為url的一部分來請求我們本地監聽的埠,這樣,我們只需要檢視請求的url就可以知道讀取到的內容是什麼。

首先,我們使用ncat監聽一個埠:

然後,我們構造payload:

我們選擇使用外部DTD,在我們自己所能掌控(或是自己搭建)的主機上編寫一個dtd檔案:

我們注意到,第一個引數實體的宣告中使用到了php的base64編碼,這樣是為了儘量避免由於檔案內容的特殊性,產生xml解析器錯誤

Payload如下:

如圖,我們先宣告一個外部的DTD引用,然後再xml文件內容中引用外部DTD中的一般實體。

開始攻擊:

然後檢視我們的埠監聽情況,會發現我們收到了一個連線請求,問號後面的內容就是我們讀取到的檔案內容經過編碼後的字串

有時候也會出現報錯的情況(這是我們在漏洞的程式碼中沒有遮蔽錯誤和警告),比如我們這裡的payload沒有選用php的base64編碼,這裡報錯了,但是同時也將所讀取的內容爆了出來,只是特殊字元經過了HTML實體編碼。

XXE的其他攻擊方式

通過XXE漏洞進行內網探測

當然進行內網探測我們還需要做一些準備工作,就是獲取目標主機在內網中的IP地址,或是內網的網路劃分資訊,我們可以先利用 file 協議讀取我們作為支點伺服器的網路配置檔案,看一下有沒有內網,以及網段大概是什麼樣子(我以linux 為例),我們可以嘗試讀取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 等跟內網配置有關的檔案,我們可以通過這些檔案的內容來獲取更多有關內網的資訊。

如果實在沒有辦法獲取目標主機的內網配置相關資訊,那就花費時間爆破吧。

內網存活主機探測:

如下,其實payload就是簡單的一個外部實體的注入payload:

只不過是將http://後面的部分替換為目標主機:

就像這樣,如果目標主機對應的埠開啟了http服務,或是其他服務,如ftp,也是可以通過http協議來訪問,這樣根據目標主機的響應內容或者狀態碼,就可以判斷主機的存活與否,根據這個原理,我們在網上找到了相應的py指令碼:

執行該指令碼就能夠找出相應網段是否存在開啟http服務的主機。

內網主機埠探測:

同樣的,根據內網存活主機的掃描方式,我們也可以針對某個主機進行埠的掃描:

構造payload:

掃描的大概原理是這樣的:
比如你掃描一個關閉的埠,在等待了一段時間後,返回協議連線失敗且超過了最長時間限制30秒:

而你掃描開放的埠,也可能是內容錯誤等其他的警告資訊:

有時候,埠掃描時判斷埠的開放和關閉並不是虛擬機器中這個樣子,由於環境的不同,版本的不同,你可能會遇到的狀況是:關閉的埠在超時之後,返回的是500狀態碼。

而開放的埠,返回的是200的狀態碼,以及一些xml警告資訊,有時候還會附帶上我們payload中期望輸出的字串。

所以,在進行內網探測時,探測結果判定的依據,還需要你自己來判斷。你可以是根據返回狀態碼,也可以是根據返回的警告資訊的內容,又或者你也可以嘗試根據返回資訊的時間長短。(我們在實驗中就不寫了,當然你也可以想辦法,將掃描存活主機與埠同時進行,我覺得這樣準確率反而會更高一些,就是速度會很慢)。

通過XXE漏洞進行命令執行

這種情況比較少見,所需的前提條件除了真實存在XXE漏洞外,大概還需要:

a. 目標系統為Linux系統
b. 目標系統成功安裝PHP的expect擴充套件

而且這個漏洞所執行的命令也有限制:

a. 可執行的命令與當前使用者的許可權大小有關
b. 命令中不能有空格,否則會報錯

一般情況下payload:

通過XXE漏洞進行DOS攻擊

上面的payload就是著名的“billionlaughs”攻擊,該程式碼可以在目標主機的記憶體中生成十億個“lol”字串,從而導致 Dos攻擊

它也被稱為指數實體擴充套件攻擊,是一種名副其實的XML炸彈。原理為:通過建立一項遞迴的 XML 定義,構造惡意的XML實體檔案耗盡可用記憶體,如以上程式碼所示,在XMl中定義了一個實體lol9,它的值包含了十個實體lol8的值,而每個lol8又包含了十個lol7的值...最後產生10億個“lol”字串,佔用記憶體約高達3GB。因為許多XML解析器在解析XML文件時傾向於將它的整個結構保留在記憶體中,解析非常慢,這樣,就會佔用大量的記憶體資源,造成了拒絕伺服器攻擊。

XXE的防禦:

方案一:

過濾使用者輸入的xml資料,比如尖括號,一些關鍵字:<!DOCTYPE和<!ENTITY,或者,SYSTEM和PUBLIC等

方案二:

禁用外部實體:
PHP:

libxml_disable_entity_loader(true);

JAVA:

DocumentBuilderFactorydbf =DocumentBuilderFactory.newInstance();

dbf.setExpandEntityReferences(false);

Python:

from lxml import etree

xmlData= etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

另外,可以自己研究一下php的其他協議在xxe注入中的使用,比如,讀取本地檔案的時候,如果檔案過大,可能會報錯,那麼這個時候就可以使用php的某個協議對檔案的內容進行壓縮……(自己思考研究)

其他資源

https://xz.aliyun.com/t/3357

https://blog.csdn.net/lileiyuyanqin/article/details/72828922

https://www.cnblogs.com/backlion/p/9302528.html

https://www.freebuf.com/column/156863.html

https://www.freebuf.com/column/208904.html

https://www.freebuf.com/articles/web/126788.html

https://www.freebuf.com/vuls/194112.html

https://www.cnblogs.com/r00tuser/p/7255939.html

https://www.freebuf.com/column/188849.html