淨化 HTML 程式碼以防止 XSS 攻擊
1. 簡介
跨站點腳本 (XSS) 是一種允許攻擊者將惡意腳本注入 Web 應用程式的漏洞。攻擊者可以在使用者的瀏覽器中執行這些腳本,導致資料竊取、會話劫持或網站破壞。
在本教程中,我們將探討如何清理 Java 應用程式中的 HTML 輸入以防止 XSS 攻擊。
2. 設定項目
首先,我們需要將OWASP Java HTML 清理庫加入到我們的pom.xml
中:
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20240325.1</version>
</dependency>
該庫提供了一個高度可配置的策略驅動的清理器,可以處理複雜的 HTML,同時防止 XSS 攻擊。
3. 實施基本的 OWASP HTML 清理
有了依賴關係,讓我們定義一個實用方法,使用該程式庫來清除潛在的有害 HTML 輸入。我們將創建一個可重複使用的實用程式類,該類使用僅允許基本格式標籤的預設策略來清理 HTML:
public class HtmlSanitizerUtil {
private static final PolicyFactory POLICY = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
public static String sanitize(String htmlContent) {
return POLICY.sanitize(htmlContent);
}
}
在上面的例子中,我們透過組合兩個內建的清理程序( Sanitizers.FORMATTING
和Sanitizers.LINKS
來設定清理策略。此策略允許使用基本 HTML 格式標籤,例如<b>
、 <i>
、 <u>
以及透過<a
標籤的超連結。然後, sanitize()
方法將此策略應用於輸入字串並傳回 HTML 內容的清理版本。
讓我們透過輸入不安全的 HTML 並斷言輸出僅包含允許的標籤來驗證我們的清理器:
String input = "<script>alert('XSS')</script><b>Hello</b> <a href='https://example.com'>link</a>";
String expectedOutput = "<b>Hello</b> <a href=\"https://example.com\" rel=\"nofollow\">link</a>";
String sanitized = HtmlSanitizerUtil.sanitize(input);
assertEquals(expectedOutput, sanitized);
在此測試中,我們傳遞一個包含惡意<script>
標籤以及有效格式和超連結元素的字串。清理程式會刪除腳本並保留安全標籤。 rel=”nofollow”
屬性會自動加入連結中作為額外的保護措施。
4. 使用 OWASP HtmlPolicyBuilder
進行彈性的清理
儘管內建策略提供了便利,但我們通常需要對允許哪些 HTML 元素和屬性進行更多控制。 HtmlPolicyBuilder
API 提供了一種流暢的方法來定義此類自訂策略。
讓我們實作一個允許區塊級和內聯格式化元素的清理器:
private static final PolicyFactory POLICY = new HtmlPolicyBuilder()
.allowCommonBlockElements()
.allowCommonInlineFormattingElements()
.toFactory();
public static String sanitize(String html) {
return POLICY.sanitize(html);
}
此實作建立了一個策略,允許常見的區塊級元素(如<div>
、 <p>
、 <ul>
和<ol>
)以及內聯元素(如<b>
、 <i>
和<em>
)。 sanitize()
方法使用此策略刪除任何危險的標籤和屬性,同時保留常見的佈局和樣式元素。 PolicyFactory
實例是執行緒安全的,可以在多個清理操作中重複使用而無需重新實例化。
接下來,我們透過基於斷言的測試來驗證此實現,該測試將清理後的結果與預期輸出進行比較:
String input = "<div onclick='alert(1)'><p><b>Text</b></p></div><script>alert('x')</script>";
String expectedOutput = "<div><p><b>Text</b></p></div>";
String sanitized = HtmlSanitizer.sanitize(input);
assertEquals(expectedOutput, sanitized);
在這種情況下,輸入包含不安全的事件處理程序和<script>
標籤。我們的自訂策略刪除了危險的屬性和元素,只留下允許的結構和格式標籤。這種方法使我們在安全性和保留部落格評論、CMS 內容或討論版的使用者格式之間取得了良好的平衡。
5.建立自訂策略
在某些應用程式中,我們可能希望允許不同的 HTML 元素集或更嚴格地限制某些屬性。 OWASP Java HTML Sanitizer 提供了用於建立自訂策略的流暢 API。以下是更複雜的策略配置的範例:
public class CustomHtmlSanitizer {
private static final PolicyFactory POLICY = new HtmlPolicyBuilder()
.allowElements("a", "p", "div", "span", "h1", "h2", "h3")
.allowUrlProtocols("https")
.allowAttributes("href").onElements("a")
.requireRelNofollowOnLinks()
.allowAttributes("class").globally()
.allowStyling()
.toFactory();
public static String sanitize(String html) {
return POLICY.sanitize(html);
}
}
在此範例中,我們建立了一個具有以下規則的自訂清理策略:
- 允許的元素:此策略允許使用結構標籤,例如
<div>
、<p>
和標題(<h1>
至<h3>
),以及<a>
和<span>
- 允許的 URL 協定:僅允許 HTTPS 鏈接,有助於防止不安全的 HTTP 鏈接,這可能會導致混合內容問題
- 連結屬性:
<a>
標籤上允許使用href
屬性,且每個連結都會自動指派一個rel=”nofollow”
屬性,以減少 SEO 濫用 - 全域屬性:所有元素都允許使用
class
屬性,支援 CSS 樣式鉤子 - 內嵌樣式:透過
style
屬性允許使用安全的 CSS 樣式,例如color
、font-weight
和其他無害的聲明
這種方法使我們能夠完全控制已清理內容的允許結構和外觀,同時確保有效地去除任何不安全的行為,例如內聯 JavaScript、事件處理程序或不允許的協定。
讓我們透過測試用例來驗證這一點:
String input = "<h1 class='title' style='color:red;'>Welcome</h1>"
+ "<a href='https://example.com' onclick='stealCookies()'>Click</a>"
+ "<script>alert('xss');</script>";
String expectedOutput =
"<h1 class=\"title\" style=\"color:red\">Welcome</h1><a href=\"https://example.com\" rel=\"nofollow\">Click</a>";
String sanitized = CustomHtmlSanitizer.sanitize(input);
assertEquals(expectedOutput, sanitized);
這種自訂策略在清理部落格、論壇或 CMS 系統的使用者生成內容時非常有用,因為這些系統需要一定的格式彈性,但不能以犧牲安全性為代價。
6. 替代方法:JSoup HTML Cleaner
雖然 OWASP Java HTML Sanitizer 高度安全且由策略驅動,但另一個用於清理 Java HTML 的流行函式庫是 JSoup。 JSoup 提供了強大的 HTML 解析和清理功能,使其成為我們需要在清理之外檢查或操作 DOM 的場景的理想選擇。
首先,我們把JSoup依賴項加入到我們的pom.xml
中:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.20.1</version>
</dependency>
新增後,我們可以實作一個清理器,定義允許的 HTML 元素和屬性的安全性清單。以下是一個範例實作:
public class JsoupHtmlSanitizer {
public static String sanitize(String html) {
Safelist safelist = Safelist.basic()
.addTags("h1", "h2", "h3")
.addAttributes("a", "target")
.addProtocols("a", "href", "http", "https");
return Jsoup.clean(html, safelist);
}
}
在這個範例中,我們從Safelist.basic(),
它允許使用基本的 HTML 標籤,例如<b>
、 <i>
、 <u>
和<a>
。接下來,我們對其進行擴展,以允許使用<h1>
、 <h2>
和<h3>
等標題標籤。
最後,我們還允許在錨標籤上使用target
屬性,當使用target=”_blank”
時,可以在新選項卡中打開鏈接,並將鏈接協議限制為http
和https
。
為了驗證這個實現,讓我們執行一個簡單的測試:
String input = "<h1 onclick='x()'>Title</h1><a href='javascript:alert(1)' target='_blank'>Click</a>";
String expectedOutput = "<h1>Title</h1><a target=\"_blank\" rel=\"nofollow\">Click</a>";
String sanitized = JsoupHtmlSanitizer.sanitize(input);
assertEquals(expectedOutput, sanitized);
與 OWASP 清理器不同,JSoup 使用「 safelist
」模型,在某些情況下更直觀,尤其是在處理預先定義的 HTML 結構時,或者當我們需要在清理之前提取或修改特定的 HTML 節點時。
此外,JSoup 會自動將rel=”nofollow”
新增至使用target=”_blank”
<a>
標籤中,以防止反向製表攻擊,從而預設增強安全性。
7. 結論
在本文中,我們探討了多種在 Java 應用程式中清理 HTML 以防禦 XSS 攻擊的方法。
當需要嚴格的 XSS 保護和細粒度的策略驅動控制時,OWASP Java HTML Sanitizer 是理想的選擇。 JSoup 更適合涉及 HTML 解析、操作的場景,或更簡單、基於安全清單的方法就足夠的場景。
與往常一樣,原始碼可在 GitHub 上取得。