本文首先簡單介紹開發測試人員如何對 Web 應用進行 XSS 漏洞測試,如何藉助工具繞過客戶端 JavaScript 校驗輸入惡意數據;然後針對使用 PHP 語言構建的 Web 站點,從在輸出端對動態內容進行編碼、以及在伺服器端對輸入進行檢測兩方面介紹如何避免惡意的 XSS 攻擊。
使用 PHP 構建的 Web 應用如何避免 XSS 攻擊
Web 2.0 的發展為網路用戶的互動提供了更多機會。用戶通過在論壇發表評論,或是在博客發表留言都可能有意或無意輸入一些破壞性的內容,從而造成網頁不能正常顯示,影響其它用戶的使用。XSS 全稱為 Cross Site Scripting,因為 CSS 已經用作樣式表的簡稱,故稱為 XSS。 XSS 是一種常見的網站攻擊的方法。其原理是通過在網頁的輸入框輸入一些惡意的內容,通常是 JavaScript 腳本片段,而這些惡意輸入在提交之後並重新讀回到客戶端時,瀏覽器會解釋執行這些惡意的腳本內容,從而影響網頁的正常顯示。
本文首先簡單介紹開發測試人員如何對 Web 應用進行 XSS 漏洞測試,如何藉助工具繞過客戶端 JavaScript 校驗輸入惡意數據;然後針對使用 PHP 語言構建的 Web 站點,從在輸出端對動態內容進行編碼、以及在伺服器端對輸入進行檢測兩方面介紹如何避免惡意的 XSS 攻擊。
對 Web 應用進行 XSS 漏洞測試
測試路徑
對 WEB 應用進行 XSS 漏洞測試,不能僅僅局限於在 WEB 頁面輸入 XSS 攻擊欄位,然後提交。繞過 JavaScript 的檢測,輸入 XSS 腳本,通常被測試人員忽略。下圖為 XSS 惡意輸入繞過 JavaScript 檢測的攻擊路徑。
常見的 XSS 輸入
XSS (Cross Site Scripting) Cheat Sheet 維護了一份常見的 XSS 攻擊腳本列表,可用來作為檢測 WEB 應用是否存在 XSS 漏洞的測試用例輸入。初次接觸 XSS 攻擊的開發人員可能會對列表提供的一些 XSS 輸入不是很理解,本文第二部分將會針對不同代碼上下文的 XSS 輸入作進一步的解釋。
測試工具
很多工具可以在瀏覽器發送 Get/Post 請求前將其截取,攻擊者可以修改請求中的數據,從而繞過 JavaScript 的檢驗將惡意數據注入伺服器。以下是一些常用的截取 HTTP 請求的工具列表。
筆者曾經使用 TamperIE 對 WEB 應用進行安全性測試。TamperIE 小巧易用,能夠截取 IE 瀏覽器發送的 Get/Post 請求,甚至能繞過 SSL 加密。不過 TamperIE + IE7 工作不穩定。IE7 提供了對 IPV6 的支持,如果你並不計劃測試你的 Web 應用對 IPV6 的支持,建議還是使用 TamperIE + IE6 的組合。
如圖2所示: TamperIE 繞過客戶端瀏覽器 JavaScript 的校驗,在 POST 請求提交時將其截取,用戶可以任意修改表單輸入項 name 和 message 的值,譬如將 message 的值修改為 "<script>alert(“XSS hole!!”);</script>",然後點擊 ”Send altered data” 按鈕,將修改後的惡意數據發送給 Web 伺服器。
在輸出端對動態內容進行編碼
對一個 Web 應用而言,其動態內容可能來源於用戶輸入、後台資料庫、硬體狀態改變或是網路信息等。動態內容特別是來自用戶輸入的動態內容很有可能包含惡意數據,從而影響網頁的正常顯示或是執行惡意腳本。將動態內容安全地顯示在瀏覽器端與動態內容所處的上下文背景有關,譬如動態內容處在 HTML 正文、表單元素的屬性、或是 JavaScript 代碼段中。對於一個基於 PHP 語言的 Web 應用,當執行 "echo"、"print"、"printf"、"<?=" 等語句時表示正在處理動態內容。本節將首先介紹 PHP 提供的庫函數 htmlspecialchars() 的用法,此函數能將 5 個 HTML 特殊字元轉化為可在網頁顯示的 HTML 實體編碼;然後將介紹一些常見背景下的 XSS 攻擊輸入,以及如何在輸出端對動態內容進行轉義、編碼從而避免 XSS 攻擊。
使用 PHP 的 htmlspecialchars() 顯示 HTML 特殊字元
從上文列舉的 XSS 惡意輸入可以看到,這些輸入中包含了一些特殊的 HTML 字元如 "<"、">"。當傳送到客戶端瀏覽器顯示時,瀏覽器會解釋執行這些 HTML 或JavaScript 代碼而不是直接顯示這些字元串。< > & “ 等字元在HTML語言中有特殊含義,對於用戶輸入的特殊字元,如何直接顯示在網頁中而不是被瀏覽器當作特殊字元進行解析?
HTML字元實體由 & 符號、實體名字或者 # 加上實體編號、分號三部分組成。以下為 HTML 中一些特殊字元的編碼。有的字元實體只有實體編號,沒有對應的實體名字,譬如單引號。
顯示 | 實體名字 | 實體編號 |
< | < | < |
> | > | > |
& | & | & |
“ | " | " |
‘ | N/A | ' |
PHP 提供了 htmlspecialchars() 函數可以將 HTML 特殊字元轉化成在網頁上顯示的字元實體編碼。這樣即使用戶輸入了各種 HTML 標記,在讀回到瀏覽器時,會直接顯示這些 HTML 標記,而不是解釋執行。htmlspecialchars() 函數可以將以下五種 HTML 特殊字元轉成字元實體編碼:
當直接調用 htmlspecialchars($str) 時, & " < > 被轉義。
當設置 ENT_QUOTES 標記時, 即調用 htmlspecialchars($str, ENT_QUOTES) 時,單引號也被轉義。
當設置 ENT_NOQUOTES 標記時,單引號和雙引號都不會被轉義。即調用 htmlspecialchars($str, ENT_NOQUOTES) 時,只有& < > 被轉義。
不同背景下的動態內容的 XSS 攻擊及解決方案
XSS 攻擊輸入與動態內容所處的代碼背景相關,譬如動態內容為表單元素屬性的值、位於 HTML 正文、或是 Javascript 代碼段中等等。
HTML 標記的屬性為動態內容
Web 應用中,"input"、"style"、"color" 等 HTML 標記的屬性都可能為動態內容,其中"input" 標記的 "value" 屬性通常為動態內容。
例子1
<form…><INPUT type=text name="msg" id="msg" size=10 maxlength=8 value="<?= $msg?>"></form> |
攻擊 XSS 輸入
Hello"><script>evil_script()</script> |
將動態內容替換
將 $msg 替換為惡意 XSS 輸入:
<form…><INPUT type=text name="msg" id="msg" size=10 maxlength=8 value="Hello"><script>evil_script()</script>"></form> |
例子2
<form…><INPUT type=text name="msg" id="msg" size=10 maxlength=8 value=<?= $msg?>></form> |
攻擊 XSS 輸入
Hello onmouseover=evil_script() |
將動態內容替換
將 $msg 替換為惡意 XSS 輸入:
<form…><INPUT type=text name="msg" id="msg" size=10 maxlength=8 value=Hello onmouseover=evil_script()></form> |
分析
從例子 1 可以看到其 XSS攻擊輸入中包含了 HTML 特殊字元 < > "
從例子 2 可以看到其 XSS 攻擊輸入中沒有包含上節中提到的五種 HTML 字元, 但是 "value"屬性值沒有使用雙引號包圍。
解決方案
調用 htmlspecialchars($str, ENT_QUOTES) 將以下 5 種 HTML 特殊字元 < > &‘ “ 轉義;同時使屬性值被雙引號包圍。譬如:
<form…><INPUT type=text name="msg" id="msg" size=10 maxlength=8 value="<?= htmlspecialchars($msg, ENT_QUOTES))?>"></form> |
注意事項
將 input 的 value 進行轉義,必須考慮顯示和存儲數據的一致性問題,即顯示在瀏覽器端和存儲在伺服器端後台的數據可能因為轉義而變得不一致。譬如存儲在伺服器端的後台原始數據包含了以上 5 種特殊字元,但是沒有轉義,為了防止 XSS 攻擊,在瀏覽器端輸出時對 HTML 特殊字元進行了轉義:
1. 當再度將表單提交時,存儲的內容將會變成轉義后的值。
2. 當使用 JavaScript 操作表單元素,需要使用到表單元素的值時,必須考慮到值可能已經被轉義。
HTML 文本為動態內容
例子
<b> 歡迎:<?= $welcome_msg?></b> |
攻擊XSS輸入
<script>evil_script()</script> |
將動態內容替換
將 $welcome_msg 替換為惡意 XSS 輸入:
<b>歡迎:<script>evil_script()</script></b> |
分析
在 HTML 正文背景下,< > 字元會引入 HTML 標記,& 可能會認為字元實體編碼的開始,所以需要將 < > & 轉義
解決方案
為簡潔起見,直接使用 htmlspecialchars() 將 5 種 HTML 特殊字元轉義,如:
<b>歡迎:<?= htmlspecialchars($welcome_msg,, ENT_NOQUOTES)?></b> |
URL 的值為動態內容
Script/Style/Img/ActiveX/Applet/Frameset… 等標記的 src 或 href 屬性如果為動態內容,必須確保這些 URL 沒有指向惡意鏈接。
例子1
<script src=<?= "$script_url>"> |
攻擊XSS輸入
http://evil.org/evil.js |
將動態內容替換
將 $script_url 替換為惡意 XSS 輸入:
<script src="http://evil.org/evil.js"> |
例子2
<img src=”<?= $img_url>”> |
攻擊XSS輸入
javascript:evil_script() |
將動態內容替換
將 $img_url 替換為惡意XSS輸入:
<img src=” javascript:evil_script()”> |
分析
一般情況下盡量不要讓 URL 的值被用戶控制。如果用戶需要自己定義自己的風格及顯示效果,也不能讓用戶直接控制整個 URL 的內容,而是提供預定義好的風格供用戶設置、裝配,然後由後台程序根據用戶的選擇組合成安全的 URL 輸出。
字符集編碼
瀏覽器需要知道字符集編碼才能正確地顯示網頁。如果字符集編碼沒有顯式在 content-type 或meta 中定義,瀏覽器會有演算法猜測網頁的字符集編碼。譬如<script>alert(document.cookie)</script> 的 UTF-7 編碼為:
+ADw-script+AD4-alert(document.cookie)+ADw-/script+AD4- |
如果 +ADw-script+AD4-alert(document.cookie)+ADw-/script+AD4- 作為動態內容位於網頁的頂端並傳送到瀏覽器端,IE 會認為此網頁是 UTF-7 編碼,從而使網頁不能正常顯示。
解決方案
顯式定義網頁的字符集編碼,譬如
<meta http-equiv=content-type content="text/html; charset=UTF-8"> |
動態內容為 JavaScript 事件處理函數的參數
JavaScript 事件處理函數如 onClick/onLoad/onError/onMouseOver/ 的參數可能包含動態內容。
例子
<input type="button" value="go to" onClick='goto_url("<?= $target_url>");'> |
攻擊XSS輸入
foo");evil_script(" |
將動態內容替換
HTML 解析器會先於 JavaScript 解析器解析網頁,將 $target_url 替換為惡意 XSS 輸入:
<input type="button" value="go to" onClick='goto_url("foo");evil_script("");'> |
動態內容位於 JavaScript 代碼段中
例子
<SCRIPT language="javascript1.2"> var msg='<?= $welcome_msg?> '; // … </SCRIPT> |
攻擊XSS輸入1
Hello'; evil_script(); // |
將動態內容替換
將 $welcome_msg 替換為惡意 XSS 輸入:
<SCRIPT language="javascript1.2"> var msg='Hello'; evil_script(); //'; // … </SCRIPT> |
攻擊XSS輸入2
Hello</script><script>evil_script();</script><script> |
將動態內容替換
將 $welcome_msg 替換為惡意 XSS 輸入:
<script> var msg = 'Hello</script> <script>evil_script();</script> <script>' // ... // do something with msg_text </script> |
分析
如上文所示,在 JavaScript 背景中使用動態內容需要非常謹慎。一般情況下,盡量避免或減少在 Javascript 的背景下使用動態內容,如果必須使用動態內容,在開發或代碼審計時必須考慮這些動態內容可能的取值,是否會導致 XSS 攻擊。
建立PHP庫函數校驗輸入
Web 開發人員必須了解,僅僅在客戶端使用 JavaScript 函數對非法輸入進行檢測過濾對於構建安全的 WEB 應用是不夠的。如上文所述,攻擊者可以輕易地藉助工具繞過 JavaScript 校驗甚至 SSL 加密輸入惡意數據。在輸出端對動態內容進行編碼也只能起到一種雙重保護的作用,更重要的應該在伺服器端對輸入進行校驗。PHP 提供了strpos()、strstr()、preg_match() 等函數可用於檢測非法字元和字元串;preg_replace() 函數可用於替換非法字元串。OWASP PHP Filters 開源項目提供了一些 PHP 庫函數用於過濾非法輸入可作為參考。一些常見的檢測和過濾包括:
總結
Web 應用的安全性是一個很重要、覆蓋範圍很廣泛的主題。為了防止常見的 XSS 的攻擊,Web 開發人員必須明白不能僅僅只在客戶端使用 JavaScript 對輸入進行檢測、過濾;同時還應建立伺服器端的輸入校驗、輸出編碼庫函數;在伺服器端檢測、過濾輸入;根據動態內容所處的背景將特殊字元進行編碼后再傳送給瀏覽器端顯示。(責任編輯:A6)
[火星人 ] 使用 PHP 構建的 Web 應用如何避免 XSS 攻擊已經有4843次圍觀