XML Attacks#
Extensible Markup Language (XML) 是 1990 年代隨著網際網路爆發而廣泛採用的資料格式,用於在組織之間交換機器可讀的資料。XML 可視為 HTML 的通用化版本——標籤和屬性名稱由文件作者自行定義,而非由規範固定。
雖然近年來 JSON 和 YAML 等格式逐漸取代 XML 的部分用途,但每個網頁伺服器仍然在某種程度上實作了 XML 解析。XML 漏洞通常發生在 validation(驗證)過程中,即使你的程式碼不直接處理 XML,你使用的第三方依賴很可能也會解析 XML。
XML 的用途#
- XML 將資料項目包裹在標籤之間,標籤可以巢狀嵌套
- 文件作者可選擇具有語意的標籤名稱,使文件具備自我描述性
- XML 廣泛用於 API、設定檔、網頁伺服器組態等場景
<?xml version="1.0"?>
<catalog>
<book id="7991728882998">
<author>Sponden, Phillis</author>
<title>The Evil Horse That Knew Karate</title>
<genre>Young Adult Fiction</genre>
</book>
</catalog>驗證 XML(Validating XML)#
由於 XML 的標籤名稱是自由定義的,讀取資料的應用程式需要知道預期的文件結構。XML 文件的結構通常由正式的 grammar(文法)描述,用來驗證文件是否符合預期格式。
描述 XML 結構的兩種主要方式:
- Document Type Definition (DTD):類似 Backus-Naur Form (BNF) 表示法,用於描述標籤、子標籤和資料類型
- XML Schema Definition (XSD):更現代、更具表達力的替代方案
DTD 包含一些可能暴露 parser 的功能特性,是 XML 攻擊的主要來源。特別是支援 inline DTD(內嵌 DTD)的 parser 更容易受到攻擊,因為惡意使用者可以控制 DTD 的內容。
Document Type Definitions#
DTD 透過指定標籤、子標籤和預期的資料類型來描述 XML 文件的結構:
<!DOCTYPE catalog [
<!ELEMENT catalog (book+)>
<!ELEMENT book (author,title,genre,description)>
<!ENTITY author (#PCDATA)>
<!ENTITY title (#PCDATA)>
<!ENTITY genre (#PCDATA)>
<!ENTITY description (#PCDATA)>
<!ATTLIST book id CDATA>
]>XML Bombs#
XML bomb 利用 inline DTD 來爆炸性地增加 XML parser 的記憶體使用量,耗盡伺服器所有可用記憶體,導致伺服器當機。
Internal Entity Declarations#
DTD 支援 internal entity declarations(內部實體宣告),這是一種在解析時展開的字串替換巨集。例如:
<?xml version="1.0"?>
<!DOCTYPE employees [
<!ELEMENT employees (employee)*>
<!ELEMENT employee (#PCDATA)>
<!ENTITY company "Rock and Gravel Company">
]>
<employees>
<employee>Fred Flintstone, &company;</employee>
<employee>Barney Rubble, &company;</employee>
</employees>解析時,&company; 會被替換為 Rock and Gravel Company。
Billion Laughs 攻擊#
當內部實體宣告引用其他內部實體宣告時,問題就出現了。經典的 billion laughs 攻擊:
<?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 lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>這個簡單的 XML 檔案在 DTD 完全展開時會佔用超過 3GB 的記憶體,包含超過十億次
lol字串。攻擊者只需找到網站上接受 XML 上傳的 URL,就能輕鬆發動 denial-of-service 攻擊。
XML External Entity Attacks#
如果 XML parser 被設定為處理 inline DTD,攻擊者可以使用 external entity declarations(外部實體宣告)來探索本地檔案系統或觸發伺服器的網路請求。
外部實體的運作方式#
<?xml version="1.0" standalone="no"?>
<!DOCTYPE copyright [
<!ELEMENT copyright (#PCDATA)>
<!ENTITY copy PUBLIC "http://www.w3.org/xmlspec/copyright.xml">
]>
<copyright>©</copyright>外部實體宣告中的 URL 可以使用不同的網路協定前綴:
http://— 觸發 HTTP 請求file://— 讀取本地磁碟上的檔案
駭客如何利用外部實體#
- 攻擊者製作惡意 XML 檔案,引用
file:///etc/passwd等敏感檔案 - 當 parser 將外部檔案插入 XML 文件後,文件格式可能變得不合法,導致解析失敗
- Parser 會在錯誤訊息中包含被引用檔案的內容,攻擊者藉此讀取敏感資料
外部實體也可被用於發動 Server-Side Request Forgery (SSRF) 攻擊。天真設定的 XML parser 遇到帶有網路協定前綴的外部實體 URL 時,就會發出網路請求。駭客利用這個特性來探測內部網路、對第三方發動 denial-of-service 攻擊,或偽裝惡意 URL 呼叫。
保護你的 XML Parser(Securing Your XML Parser)#
最簡單的修復方式是:停用 inline DTD 的處理。DTD 是過時的技術,inline DTD 更是不良實踐。事實上,許多現代 XML parser 預設已經停用了這些功能。
各語言的安全設定#
Python#
使用 defusedxml 函式庫,它是 Python 標準 XML 解析函式庫的安全替代品,會明確拒絕 inline DTD。
Ruby#
使用 Nokogiri 函式庫,從 1.5.4 版本起已針對 XML 攻擊進行強化。
Node.js#
Node.js 有多種 XML 解析模組(xml2js、parse-xml、node-xml),大多數預設不處理 DTD,但仍應查閱你使用的 parser 的文件確認。
Java#
透過 javax.xml.parsers.DocumentBuilderFactory 設定安全解析:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);.NET#
XmlDictionaryReader、XmlNodeReader、XmlReader 預設是安全的。對於其他 parser(如 System.Xml.XmlDocument),設定 ProhibitDtd 屬性:
XmlTextReader reader = new XmlTextReader(stream);
reader.ProhibitDtd = true;其他考量#
- 遵循 principle of least privilege(最小權限原則):XML parser 幾乎沒有理由需要發出對外網路請求,考慮鎖定伺服器的對外網路存取
- 如需對外網路存取(例如呼叫第三方 API),應在防火牆規則中設定白名單
- 限制網頁伺服器可存取的磁碟目錄:Linux 上使用 chroot jail,Windows 上手動設定可存取的目錄白名單
總結#
- XML 是廣泛使用的資料格式,你的 XML parser 若設定為接受並處理 inline DTD,就可能受到攻擊
- XML bombs 利用 inline DTD 爆炸性地增加記憶體使用,可能導致伺服器當機
- XML external entity attacks 利用外部實體引用本地檔案或網路位址,竊取敏感資訊或發動惡意網路請求
- 確保使用經過安全強化的 XML parser,並停用 inline DTD 解析