在 XML 中編碼特殊字符
一、簡介
在本文中,我們將探討 XML 實體、它們是什麼以及它們可以為我們做什麼。特別是,我們將看到哪些實體作為 XML 的標準存在,以及我們如何在必要時定義我們自己的實體。
2. XML 的結構是怎樣的?
XML 是一種用於表示任意數據的標記格式。它使用 XML 元素的層次結構來實現這一點,每個元素都可以具有屬性。例如:
<part number="1976">
<name>Windscreen Wiper</name>
</part>
這顯示了一個名為“part”的元素,它有一個屬性——“number”——和一個嵌套元素——“name”。
值得注意的是,XML 語言使用一些特殊字符來管理它。例如,元素始終以小於號“<”開頭,以大於號“>”結尾。
但是,如果這些字符對 XML 有特殊含義,那就意味著我們不能在我們的內容中使用它們。這樣做至少是模棱兩可的,最壞的情況下是完全無法解析的。
例如,如果我們嘗試使用 XML 來表示一個簡單的數學方程式,我們可能會這樣寫:
<math> 1 < x > 5 </math>
這試圖表示x
具有 1 到 5 之間的值。但是,XML 進程無法知道其意圖不是在兩個數字之間有一個元素“x”。
3. 標準 XML 實體
XML 通過使用 XML 實體解決了這個問題。這些是代表其他字符的特殊序列。
XML 實體始終以與字符“&”開頭,以分號字符“;”結尾。實體的名稱則位於這兩個字符之間。例如,實體“<”用來表示小於號——“<”。
有一組五個標準實體是表示對 XML 具有特殊含義的字符所必需的:
實體 | 人物代表 |
& | & 符號 – & |
' | 撇號 – ' |
> | 大於號-> |
< | 小於號 - < |
“ | 引號 - “ |
知道了這一點,我們上面表示數學方程式的嘗試將變成:
<math> 1 < x > 5 </math>
突然間,如何理解這一點就沒有歧義了。
4.角色實體
除了上述之外, XML 還提供了表示任意 Unicode 字符的能力。我們通過直接引用十進製或十六進制形式的 Unicode 代碼點來做到這一點。
這些是標準的 XML 實體——這意味著它們以“&”字符為前綴,以“;”為後綴特點。十進制代碼點以“#”字符為前綴,十六進制代碼點以“#x”為前綴。
例如,字符“÷”是除號。 Unicode 將此表示為代碼點 U+00F7。因此,我們可以用 XML 將其表示為
或者作為
.
如果我們不使用 Unicode 字符集來編碼我們的 XML 文檔——例如,如果我們使用 ISO-8859-1——但仍想表示 Unicode 字符,這將特別有用。它也可以用於表示某些特殊字符,例如非打印字符或組合字符,以便閱讀它的開發人員可以看到它們的存在。
最後,我們可以用它來表示文檔中不能出現的控製字符——例如,U+0000 是 Nul 字符,但是在文檔中出現這個裸露的字符可能會讓很多讀者失望。
5.自定義實體
我們也可以定義自己的 XML 實體。這讓我們可以指定一個我們選擇的實體名稱並定義它將被替換的值。如果我們有某些重複的值並且需要輕鬆管理,這會有所幫助,但如果不小心使用,它確實會帶來一些潛在的安全風險。
我們需要使用文檔類型定義 (DTD) 來定義自定義實體。這是 XML 文檔開始之前的一個部分,可用於定義其結構——類似於 XSD。我們使用“”構造來實現這一點,其中“name”是 DTD 的任意名稱:
<!DOCTYPE example [
....
]>
<part number="1976">
<name>Windscreen Wiper</name>
</part>
在這個構造中,我們包含了 DTD 定義。這可以包括自定義實體定義——作為內部或外部實體。
5.1.內部實體
內部實體直接在線定義,給它一個名稱和一個值。完成此操作後,可以按原樣使用此名稱的實體並將其視為任何其他實體。例如:
<!DOCTYPE example [
<!ENTITY windscreen "Windscreen Wiper">
]>
<part number="1976">
<name>&windscreen;</name>
</part>
在這裡,我們定義了一個名為“windscreen”的自定義實體和一個替換值“Windscreen Wiper”。我們將其與“&windscreen;”一起使用。我們的 XML 過程將用“Windscreen Wiper”值替換它。
5.2.外部實體
外部實體的工作方式相同,但我們不是直接在 DTD 中提供值,而是提供查找它的位置。例如:
<!DOCTYPE example [
<!ENTITY windscreen SYSTEM "http://example.com/parts/windscreen.txt">
]>
<part number="1976">
<name>&windscreen;</name>
</part>
在這裡,我們定義了一個名為“windscreen”的自定義實體,以及在 URL“http://example.com/parts/windscreen.txt”中找到的任何內容的替換值。我們可以像以前一樣使用它,XML 處理器將自動獲取此外部資源以在需要時包含在內。
5.3.潛在的安全風險
使用自定義實體可能很強大,但也會讓我們面臨一些潛在的安全風險。特別是,如果我們正在處理由不受信任的來源提供的 XML 文檔,那麼我們需要特別小心。
這裡最明顯的攻擊是 XML 外部實體 (XXE) 注入。這是某人可以製作 XML 文檔的地方,該文檔將惡意加載攻擊者不應訪問的資源。例如:
<!DOCTYPE example [
<!ENTITY windscreen SYSTEM "file:///etc/passwd">
]>
<part number="1976">
<name>&windscreen;</name>
</part>
此 XML 文檔聲明一個自定義實體,系統密碼文件的內容將替換該實體。顯然,這不是應該發生的事情,但如果我們不小心,攻擊者就可以做到這一點。
另一種潛在的攻擊有時稱為 XML 炸彈。這是一種使用 XML 實體重複擴展的 DoS 攻擊:
<!DOCTYPE test [
<!ENTITY a0 "someLargeString">
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;">
<!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;">
<!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;">
]>
<document>&a4;</document>
在這裡,我們有我們的“&a4;”實體。這擴展到 10 個“&a3;”實例,每個擴展到 10 個“&a2;”實例,依此類推。這導致我們的文檔包含 10,000 個“someLargeString”實例。如果我們的攻擊者走得更遠,我們可以獲得更多——深入 10 級將給我們 10,000,000,000 個實例,大小為 140 GB。
通常,避免這些風險的唯一方法是在 XML 處理器中完全禁用自定義實體。但是,這也消除了從中獲得的好處。如果我們正在處理來自不受信任來源的 XML 文檔,那麼這種風險可能不值得受益,但對於內部文檔,它可能是有益的。
六,結論
在本文中,我們了解瞭如何在 XML 文檔中使用 XML 實體來表示特殊字符。我們甚至了解瞭如何在必要時定義我們的實體。