[Web 安全] 瞭解XSS與防範

名一發表於2015-10-19

跨站指令碼(XSS, Cross Site Script)攻擊指的是,攻擊者可以讓某網站執行一段非法指令碼。這種情況很常見,比如提交一個表單用於修改使用者名稱,我們可以在文字框中輸入一些特殊字元,比如 <, >, ', " 等,檢查一下使用者名稱是否正確修改了。

XSS 如何發生

XSS 一定是由使用者的輸入引起的,無論是提交表單、還是點選連結(引數)的方式,只要是對使用者的輸入不做任何轉義就寫到資料庫,或者寫到 htmljs 中,就很有可能出錯。舉兩個例子。

假設需要顯示一個新聞標題的列表,服務端渲染的話,用 jade 來實現的話也許是這樣的

h1 娛樂速遞
ul
  each val in newslist
    li= val

newslist 就當是 ['新聞1', '新聞2', ...] 這樣格式的陣列,如果直接把內容迭代渲染到 html 上的話,一旦某個新聞標題有特殊字元,比如標題中恰好包含一個 <p> 標籤,那麼它就不會顯示出來。

另一個例子,使用者在寫部落格,先不考慮實時儲存吧,現在就僅僅需要預覽一下,那麼可能的程式碼就是

var preview = document.getElementById('#preview'),
    title   = document.getElementById('#blog-title'),
    content = document.getElementById('#blog-content');

preview.innerHTML = 
  '<h1>' + title.value + '</h1>' +
  '<pre>' + content.value + '</pre>';

這裡同樣是把使用者的輸入直接顯示在了 html 上,如果使用者的輸入中,正好輸入了 </h1>,把 <h1> 標籤提前結束,然後再輸入 <script>...</script> 就可以直接執行 js 程式碼了。

XSS 的發生至少需要一個條件,就是這些非法的指令碼必須得在瀏覽器中解析。

從一個請求發出開始,到瀏覽器顯示內容,與 XSS 相關的有三個地方:URL、HTML、JavaScript。至於後臺方面,它分兩個功能,一個是將資料寫到資料庫,這時候也要對資料進行轉義,但不是XSS的範疇,它更多是防止資料破壞 SQL 語句的結構;另一個是從資料庫讀取資料,直接生成 HTML 或者以 JSON 的方式傳給前端,這些資料都必須轉義後才能顯示到瀏覽器中。

HTML 特殊字元

HTML 本身是一個文字文件,但在瀏覽器中卻可以顯現得花樣百出,是因為很多字元對於瀏覽器來說是有特殊含義的,比如在 <script> 中的內容,瀏覽器會做一些動畫等等。那麼對這些特殊字元進行轉義,就意味著讓瀏覽器對待它們的時候,就像普通字元一樣,比如 &lg;script&gt; 這段文字在瀏覽器中就會正常顯示為 <script>

當我們在程式碼中生成 HTML 時,一定要注意,變數是否轉義了。像這種

el.innerHTML = title.value;

就是非常危險的。因為輸入框的內容來源於使用者,而使用者的輸入是不可靠的。無論是前端還是後臺,一定要有一個類似於 escapeHTML 的方法,然後在程式碼中這樣使用

el.innerHTML = escapeHTML(title.value);

這邊貼一段簡單的用來轉義 HTML 的 JavaScript 方法

function encodeHTML (a) {
  return String(a)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
};

那麼有哪些字元需要轉義呢?這裡列了一些常見的。

" --> &#34;
# --> &#35;
$ --> &#36;
& --> &#38;
' --> &#39;
( --> &#40;
) --> &#41;
; --> &#59;
< --> &#60;
> --> &#62;

在 escapeHTML 方法中,我使用了別名的方式轉義,因為它比較容易記一點。無論是別名還是十六進位制,它們表示的含義都是一樣的,比如 &amp;&#38; 都表示 & 符號。想要看更具體的列表可以參考這個網站

在瀏覽器收到 HTML 之後,首先會對所有的內容進行解碼,它會把所有能識別的編碼符號,解碼成字面值。比如有

<p>my name is&#58;&#32;<a href="http&#58;&#47;&#47;www.jchen.cc">名一</a></p>

經過瀏覽器解碼就變成

<p>my name is: <a href="http://www.jchen.cc">名一</a></p>

這裡要說的是,瀏覽器只會對兩個地方解碼,一個是標籤的內容(即 textContent,除了 <script><style> 標籤),另一個是標籤的屬性值。對於屬性名是不會解碼的。

URL

早些時候,服務端還不支援在 URL 中直接傳輸 Unicode,比如 http://jchen.cc/find?q=你好 這樣的地址,服務端無法識別“你好”這個值,所以必須編碼之後進行傳輸。

那麼對於 URL,我們只需要對引數的值進行編碼就可以了。比如上面這個連結,編碼之後就是 http://jchen.cc/find?q=%E4%BD%A0%E5%A5%BD

如果對整個 URL 編碼,那麼連結就無效了。

編碼的方式很簡單,瀏覽器提供了全域性的 encodeURI 方法,呼叫之後就可以實現轉義了。

有一點很重要,encodeURI 是不會轉義 :, /, ?, &, = 這些在 URL 中有特殊含義的字元的,那麼如果有個引數正好包含了這些字元,就不會轉義,比如

encodeURI('http://jchen.cc/login?name=名一&from=http://other.com'); 

// -> http://jchen.cc/login?name=%E5%90%8D%E4%B8%80&from=http://other.com

from 引數的值並沒有轉義,這時候,就需要用到另一個方法 encodeURIComponent

var param = encodeURIComponent('http://other.com');
encodeURI('http://jchen.cc/login?name=名一&from=') + param;

// -> http://jchen.cc/login?name=%E5%90%8D%E4%B8%80&from=http%3A%2F%2Fother.com

所以結論就是,如果要對整個 URL 進行轉義,使用 encodeURI,如果對引數的值進行轉義,使用 encodeURIComponent

當動態生成的連結地址需要賦值給 href 或者 src 屬性時,需要對這些地址進行 URL 轉義。當然,如果服務端支援在 URL 中包含 UTF-8 的字元的話,其實不轉義也不會錯,這就是為什麼我們平時不會太注意對錶單和 URL 引數進行轉義的原因,因為服務端表現良好。

JavaScript 特殊字元

JS 中的轉義都是通過反斜槓完成,有三種型別,以 '" 為例

  • 直接反斜槓 --> \'\"

  • 十六進位制 --> \x22\x27

  • Unicode --> \u0022\u0027

一般情況下可以直接通過反斜槓轉義,但有些字元我們不知道怎麼輸入,很常見的比如 Web Font,在 CSS 中可以看到類似這樣的程式碼

.glyphicon-home::before {
    content: "";
}

那個 content 中的值可以通過十六進位制或者 Unicode 的方式來代替。

JS 轉義一般用於顯示使用者輸入的時候,比如使用者輸入了反斜槓,需要顯示時,就必須 alert('\\');

解碼順序

當瀏覽器進行繪製時,首先會對 HTML 進行解碼,然後是 URL,最後是執行 JS 時對它進行解碼。

現在考慮這三種編碼同時存在的情況

<a href="javascript&#58;&#32;alert('\<http&#58;&#47;&#47;jchen.cc/find?q=%E4%BD%A0%E5%A5%BD\>');">click</a>

首先是 HTML 解碼,結果為

<a href="javascript: alert('\<http://jchen.cc/find?q=%E4%BD%A0%E5%A5%BD\>');">click</a>

然後是 URL 解碼,結果為

<a href="javascript: alert('\<http://jchen.cc/find?q=你好\>');">click</a>

最後是 JS 解碼,結果為

<a href="javascript: alert('<http://jchen.cc/find?q=你好>');">click</a>

單擊連結後,應該會出現一個彈窗,內容是 <http://jchen.cc/find?q=你好>

本文更多的是介紹如何防止XSS的發生,而不是它的危害。核心就是用適當的方法對 HTML, JS 進行轉義。

相關文章