使用 PHP 構建的 Web 應用如何避免 XSS 攻擊

周 婷 ([email protected]), 軟件工程師, IBM 中國軟件開發技術實驗室
劉 鑫 ([email protected]), 軟件工程師, IBM 中國系統技術實驗室
劉 堅 ([email protected]), 軟件工程師, IBM 中國系統技術實驗室

簡介: 本文首先簡單介紹開發測試人員如何對 Web 應用進行 XSS 漏洞測試,如何藉助工具繞過客戶端 JavaScript 校驗輸入惡意數據;然後針對使用 PHP 語言構建的 Web 站點,從在輸出端對動態內容進行編碼、以及在服務器端對輸入進行檢測兩方面介紹如何避免惡意的 XSS 攻擊。

發佈日期: 2009 年 3 月 19 日
級別: 初級
訪問情況 2573 次瀏覽
建議: 0 (添加評論)

使用 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 檢測的攻擊路徑。


圖 1. XSS 攻擊測試路徑 – 繞過 JavaScript 校驗

常見的 XSS 輸入

  • XSS 輸入通常包含 JavaScript 腳本,如彈出惡意警告框:<script>alert("XSS");</script>
  • XSS 輸入也可能是 HTML 代碼段,譬如:
    • 網頁不停地刷新 <meta http-equiv="refresh" content="0;">
    • 嵌入其它網站的鏈接 <iframe src=http://xxxx width=250 height=250></iframe>

XSS (Cross Site Scripting) Cheat Sheet 維護了一份常見的 XSS 攻擊腳本列表,可用來作爲檢測 WEB 應用是否存在 XSS 漏洞的測試用例輸入。初次接觸 XSS 攻擊的開發人員可能會對列表提供的一些 XSS 輸入不是很理解,本文第二部分將會針對不同代碼上下文的 XSS 輸入作進一步的解釋。

測試工具

很多工具可以在瀏覽器發送 Get/Post 請求前將其截取,攻擊者可以修改請求中的數據,從而繞過 JavaScript 的檢驗將惡意數據注入服務器。以下是一些常用的截取 HTTP 請求的工具列表。

  • Paros proxy (http://www.parosproxy.org)
  • Fiddler (http://www.fiddlertool.com/fiddler)
  • Burp proxy (http://www.portswigger.net/proxy/)
  • TamperIE (http://www.bayden.com/dl/TamperIESetup.exe)

筆者曾經使用 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 服務器。


圖 2. 使用 TamperIE 截取 Post 請求


在輸出端對動態內容進行編碼

對一個 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 中一些特殊字符的編碼。有的字符實體只有實體編號,沒有對應的實體名字,譬如單引號。


表 1. 一些 HTML 特殊字符的實體編碼
顯示實體名字實體編號
<&lt;&#60;
>&gt;&#62;
&&amp;&#38;
&quot;&#34;
N/A&#39;

PHP 提供了 htmlspecialchars() 函數可以將 HTML 特殊字符轉化成在網頁上顯示的字符實體編碼。這樣即使用戶輸入了各種 HTML 標記,在讀回到瀏覽器時,會直接顯示這些 HTML 標記,而不是解釋執行。htmlspecialchars() 函數可以將以下五種 HTML 特殊字符轉成字符實體編碼:

  • & 轉成 &amp;
  • “ 轉成 &quot;
  • < 轉成 &lt;
  • > 轉成 &gt;
  • ‘ 轉成 &#39;

當直接調用 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 οnmοuseοver=evil_script()

將動態內容替換

$msg 替換爲惡意 XSS 輸入:

<form…><INPUT type=text name="msg" id="msg" size=10 
maxlength=8 value=Hello οnmοuseοver=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&quot;);evil_script(&quot;

將動態內容替換

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 庫函數用於過濾非法輸入可作爲參考。一些常見的檢測和過濾包括:

    1. 輸入是否僅僅包含合法的字符;
    2. 輸入如果爲數字,數字是否在指定的範圍;
    3. 輸入字符串是否超過最大長度限制;
    4. 輸入是否符合特殊的格式要求,譬如email 地址、IP 地址;
    5. 不同的輸入框在邏輯上存在的耦合和限制的關係;
    6. 除去輸入首尾的空格;

總結

Web 應用的安全性是一個很重要、覆蓋範圍很廣泛的主題。爲了防止常見的 XSS 的攻擊,Web 開發人員必須明白不能僅僅只在客戶端使用 JavaScript 對輸入進行檢測、過濾;同時還應建立服務器端的輸入校驗、輸出編碼庫函數;在服務器端檢測、過濾輸入;根據動態內容所處的背景將特殊字符進行編碼後再傳送給瀏覽器端顯示。


參考資料

作者簡介

周婷,軟件工程師,目前在 IBM 中國軟件開發技術實驗室從事刀片服務器管理固件的開發工作。您可以通過 [email protected] 和她聯繫。

劉鑫,目前在 IBM 中國系統技術實驗室從事服務器管理固件的開發工作。

劉堅, 軟件工程師, Linux愛好者, 目前在 IBM 中國軟件開發技術實驗室從事刀片服務器管理固件的開發工作。您可以通過 [email protected] 和他聯繫。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章