本文摘譯整理自PHP最佳實踐——PHP淨化HTML輸入和輸出,也就是做HTML安全轉義,避免惡意代碼。
對於簡單的數據淨化,使用 htmlentities() 函數, 複雜的數據淨化則使用 HTML Purifier 庫
經 HTML Purifier 4.4.0 測試
在任何 wbe 應用中展示用戶輸出時,首先對其進行“淨化”去除任何潛在危險的 HTML 是非常必要的。 一個惡意的用戶可以製作某些 HTML,若被你的 web 應用直接輸出,對查看它的人來說會很危險。
雖然可以嘗試使用正則表達式來淨化 HTML,但不要這樣做。HTML是一種複雜的語言,試圖使用正則表達式來淨化 HTML 幾乎總是失敗的。
你可能會找到建議你使用 strip_tags() 函數的觀點。 雖然 strip_tags() 從技術上來說是安全的,但如果輸入的不合法的 HTML(比如, 沒有結束標簽),它就成了一個「愚蠢」的函數,可能會去除比你期望的更多的內容。 由於非技術用戶在通信中經常使用 < 和 > 字符,strip_tags() 也就不是一個好的選擇了。
如果閱讀了驗證郵件地址一節, 你也許也會考慮使用 filter_var() 函數。 然而 filter_var() 函數在遇到斷行時會出現問題, 並且需要不直觀的配置以接近 htmlentities() 函數的效果, 因此也不是一個好的選擇。
對於簡單需求的淨化
如果你的 web 應用僅需要完全地轉義(因此可以無害地呈現,但不是完全去除) HTML, 則使用 PHP 的內建 htmlentities() 函數。 這個函數要比 HTML Purifier 快得多,因此它不對 HTML 做任何驗證—僅轉義所有東西。
htmlentities() 不同於類似功能的函數htmlspecialchars(), 它會編碼所有適用的 HTML 實體,而不僅僅是一個小的子集。
示例
<?php
// Oh no! The user has submitted malicious HTML, and we have to display it in our web app!
$evilHtml = '<div onclick="xss();">Mua-ha-ha! Twiddling my evil mustache...</div>';
// Use the ENT_QUOTES flag to make sure both single and double quotes are escaped.
// Use the UTF-8 character encoding if you've stored the text as UTF-8 (as you should have).
// See the UTF-8 section in this document for more details.
$safeHtml = htmlentities($evilHtml, ENT_QUOTES, 'UTF-8');
// $safeHtml is now fully escaped HTML. You can output $safeHtml to your users without fear!
?>
對於複雜需求的淨化
對於很多 web 應用來說,簡單地轉義 HTML 是不夠的。 你可能想完全去除任何HTML,或者允許一小部分子集的 HTML 存在。 若是如此,則使用 HTML Purifier 庫。
HTML Purifier 是一個經過充分測試但效率比較低的庫。 這就是為什麽如果你的需求並不複雜就應使用 htmlentities(), 因為它的效率要快得多。
HTML Purifier 相比 strip_tags() 是有優勢的, 因為它在淨化 HTML 之前會對其校驗。 這意味著如果用戶輸入無效 HTML,HTML Purifier 相比 strip_tags() 更能保留 HTML 的原意。 HTML Purifier 高度可定製,允許你為 HTML 的一個子集建立白名單來允許這個 HTML 子集的實體存在輸出中。
但其缺點就是相當的慢,它要求一些設置,在一個共享主機的環境裏可能是不可行的。 其文檔通常也複雜而不易理解。 以下示例是一個基本的使用配置。 查看文檔閱讀 HTML Purifier 提供的更多更高級的特性。
示例
<?php
// Include the HTML Purifier library
require_once('htmlpurifier-4.4.0/HTMLPurifier.auto.php');
// Oh no! The user has submitted malicious HTML, and we have to display it in our web app!
$evilHtml = '<div onclick="xss();">Mua-ha-ha! Twiddling my evil mustache...</div>';
// Set up the HTML Purifier object with the default configuration.
$purifier = new HTMLPurifier(HTMLPurifier_Config::createDefault());
$safeHtml = $purifier->purify($evilHtml);
// $safeHtml is now sanitized. You can output $safeHtml to your users without fear!
?>
可能踩的坑
- 以錯誤的字符編碼使用 htmlentities() 會造成意想不到的輸出。 在調用該函數時始終確認指定了一種字符編碼,並且該編碼與將被淨化的字符串的編碼相匹配。 更多細節請查看 UTF-8 一節。
- 使用 htmlentities() 時,始終包含 ENT_QUOTES 和字符編碼參數。 默認情況下,htmlentities() 不會對單引號編碼。多愚蠢的默認做法!
- HTML Purifier 對於複雜的 HTML 效率極其的低。可以考慮設置一個緩存方案如APC來保存經過淨化的結果以備後用。
進一步閱讀
- PHP HTML 淨化工具對比 (英文)
- Laruence:PHP Taint – 一個用來檢測 XSS/SQL/Shell 注入漏洞的擴展
- Stack Overflow: 使用 strip_tags() 來防止 XSS?
- Stack Overflow: PHP中淨化用戶輸入的最佳方法是什麽?
- Stack Overflow: 斷行時的 FILTER_SANITIZE_SPECIAL_CHARS 問題