Web 安全漏洞之 XSS 攻擊

ThinkJS發表於2018-11-19

編者說:作為JS系工程師接觸最多的漏洞我想就是 XSS 漏洞了,然鵝並不是所有的同學對其都有一個清晰的認識。今天我們請來了@盧士傑 同學為我們分享他眼中的 XSS 漏洞攻擊,希望能幫助到大家。


什麼是 XSS 攻擊

XSS(Cross-Site Scripting)又稱跨站指令碼,XSS的重點不在於跨站點,而是在於指令碼的執行。XSS是一種經常出現在 Web 應用程式中的電腦保安漏洞,是由於 Web 應用程式對使用者的輸入過濾不足而產生的。

常見的 XSS 攻擊有三種:反射型、DOM-based 型、儲存型。 其中反射型、DOM-based 型可以歸類為非持久型 XSS 攻擊,儲存型歸類為持久型 XSS 攻擊。

1.反射型

反射型 XSS 一般是攻擊者通過特定手法(如電子郵件),誘使使用者去訪問一個包含惡意程式碼的 URL,當受害者點選這些專門設計的連結的時候,惡意程式碼會直接在受害者主機上的瀏覽器執行。

對於訪問者而言是一次性的,具體表現在我們把我們的惡意指令碼通過 URL 的方式傳遞給了伺服器,而伺服器則只是不加處理的把指令碼“反射”回訪問者的瀏覽器而使訪問者的瀏覽器執行相應的指令碼。反射型 XSS 的觸發有後端的參與,要避免反射性 XSS,必須需要後端的協調,後端解析前端的資料時首先做相關的字串檢測和轉義處理。

此類 XSS 通常出現在網站的搜尋欄、使用者登入口等地方,常用來竊取客戶端 Cookies 或進行釣魚欺騙。

整個攻擊過程大約如下:

反射型

2.DOM-based 型

客戶端的指令碼程式可以動態地檢查和修改頁面內容,而不依賴於伺服器端的資料。例如客戶端如從 URL 中提取資料並在本地執行,如果使用者在客戶端輸入的資料包含了惡意的 JavaScript 指令碼,而這些指令碼沒有經過適當的過濾和消毒,那麼應用程式就可能受到 DOM-based XSS 攻擊。需要特別注意以下的使用者輸入源 document.URLlocation.hashlocation.searchdocument.referrer 等。

整個攻擊過程大約如下:

DOM-based

3.儲存型

攻擊者事先將惡意程式碼上傳或儲存到漏洞伺服器中,只要受害者瀏覽包含此惡意程式碼的頁面就會執行惡意程式碼。這就意味著只要訪問了這個頁面的訪客,都有可能會執行這段惡意指令碼,因此儲存型XSS的危害會更大。

儲存型 XSS 一般出現在網站留言、評論、部落格日誌等互動處,惡意指令碼儲存到客戶端或者服務端的資料庫中。

整個攻擊過程大約如下:

DOM-based

XSS 攻擊的危害

XSS 可以導致:

  1. 攻擊劫持訪問;
  2. 盜用 cookie 實現無密碼登入;
  3. 配合 csrf 攻擊完成惡意請求;
  4. 使用 js 或 css 破壞頁面正常的結構與樣式等;

防禦方法

1. XSS 防禦之 HTML 編碼

應用範圍:將不可信資料放入到 HTML 標籤內(例如div、span等)的時候進行HTML編碼。

編碼規則:將 & < > " ' / 轉義為實體字元(或者十進位制、十六進位制)。

示例程式碼:

  function encodeForHTML(str, kwargs){
    return ('' + str)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')     // DEC=> &#60; HEX=> &#x3c; Entity=> &lt;
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#x27;')   // &apos; 不推薦,因為它不在HTML規範中
      .replace(/\//g, '&#x2F;');
  };
複製程式碼

HTML 有三種編碼表現方式:十進位制、十六進位制、命名實體。例如小於號(<)可以編碼為 "十進位制> <", "十六進位制=> <", "命名實體=> <" 三種方式。對於單引號(')由於實體字元編碼方式不在 HTML 規範中,所以此處使用了十六進位制編碼。

2. XSS 防禦之 HTML Attribute 編碼

應用範圍:將不可信資料放入 HTML 屬性時(不含src、href、style 和事件處理屬性),進行 HTML Attribute 編碼

編碼規則:除了字母數字字元以外,使用 &#xHH;(或者可用的命名實體)格式來轉義ASCII值小於256所有的字元​​​​​​​

示例程式碼:

  function encodeForHTMLAttibute(str, kwargs){
    let encoded = '';
    for(let i = 0; i < str.length; i++) {
      let ch = hex = str[i];
      if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) {
        hex = '&#x' + ch.charCodeAt(0).toString(16) + ';';
      }
      encoded += hex;
    }
    return encoded;
  };
複製程式碼

3. XSS 防禦之 JavaScript 編碼

作用範圍:將不可信資料放入事件處理屬性、JavaScirpt值時進行 JavaScript 編碼

編碼規則:除字母數字字元外,請使用\xHH格式轉義ASCII碼小於256的所有字元

示例程式碼:

  function encodeForJavascript(str, kwargs) {
    let encoded = '';
    for(let i = 0; i < str.length; i++) {
      let cc = hex = str[i];
      if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) {
        hex = '\\x' + cc.charCodeAt().toString(16);
      }
      encoded += hex;
    }
    return encoded;
  };
複製程式碼

4. XSS 防禦之 URL 編碼

作用範圍:將不可信資料作為 URL 引數值時需要對引數進行 URL 編碼

編碼規則:將引數值進行 encodeURIComponent 編碼

示例程式碼:

  function encodeForURL(str, kwargs){
    return encodeURIComponent(str);
  };
複製程式碼

5. XSS 防禦之 CSS 編碼

作用範圍:將不可信資料作為 CSS 時進行 CSS 編碼

編碼規則:除了字母數字字元以外,使用\XXXXXX格式來轉義ASCII值小於256的所有字元

示例程式碼:

  function encodeForCSS (attr, str, kwargs){
    let encoded = '';
    for (let i = 0; i < str.length; i++) {
      let ch = str.charAt(i);
      if (!ch.match(/[a-zA-Z0-9]/) {
        let hex = str.charCodeAt(i).toString(16);
        let pad = '000000'.substr((hex.length));
        encoded += '\\' + pad + hex;
      } else {
        encoded += ch;
      }
    }
    return encoded;
  };
複製程式碼

後記

在任何時候使用者的輸入都是不可信的。對於 HTTP 引數,理論上都要進行驗證,例如某個欄位是列舉型別,其就不應該出現列舉以為的值;對於不可信資料的輸出要進行相應的編碼;此外httpOnlyCSPX-XSS-ProtectionSecure Cookie 等也可以起到有效的防護。

XSS 漏洞有時比較難發現,所幸當下React、Vue等框架都從框架層面引入了 XSS 防禦機制,一定程度上解放了我們的雙手。 但是作為開發人員依然要了解 XSS 基本知識、於細節處避免製造 XSS 漏洞。框架是輔助,我們仍需以人為本,規範開發習慣,提高 Web 前端安全意識。

參考文件

相關文章