1. 程式人生 > 實用技巧 >前端安全之防範XSS

前端安全之防範XSS

由於前段時間業務有接觸到富文字編輯器,且編輯器由使用者直接使用,所以不可避免需要對其涉及到的XSS防護有所瞭解,因此對XSS防護做一個實戰小結。

前言

XSS大部分前端coder都不會陌生,全稱:跨站指令碼漏洞(Cross Site Scripting,簡寫作XSS)是Web應用程式在將資料輸出或者展示到網頁的時候存在問題,導致攻擊者可以將對網站的正常功能造成影響甚至竊取或篡改使用者個人資訊,其誘發的主要原因是沒有針對使用者輸入來源的資料以及不可信資料來源的過濾。本文將針對XSS進行簡單的歸類總結,並且提供相關的實戰處理手段。

一、XSS 型別劃分

對於常見的XSS攻擊方式,大部分人再熟悉不過了,這裡再幫大家複習一下。

1、反射性XSS:常見情況是攻擊者通過構造一個惡意連結的形式,誘導使用者傳播和開啟,由於連結內所攜帶的引數會回顯於頁面中或作為頁面的處理資料來源,最終造成XSS攻擊。

2、儲存型XSS:與前者不同,儲存型XSS是持久化的XSS攻擊方式,通過使用者輸入個人資訊或者發表文章的方式將惡意程式碼儲存於伺服器端,當其他使用者再次訪問頁面時觸發,造成XSS攻擊。

3、DOM based型XSS:同樣也是利用對資料來源不可靠,缺乏過濾,由於開發過程中,不可避免部分資料需要回填到DOM中,例如href、src、>二、XSS防範手段

針對XSS的防範,需要開發者養成意識,針對使用者輸入源的資料進行過濾,針對頁面不可信任的資料來源也要做好過濾,類似於響應頭中的欄位、url中的引數、refer等欄位都是不可信的,並且結合其他手段和方法,讓頁面更加安全可控。

1、htmlencode轉義特殊字元

大部分的論壇或者部落格平臺,註冊賬號都會允許使用者填寫個人資訊,包括暱稱、郵箱和個性簽名等,此類文字資訊屬於非富文字型別,最常見的方法就是對尖括號標籤轉義成實體字元儲存,由於是非富文字資訊,並且以標籤內容的形式展示,如果不使用框架渲染,直接原生js通過docoment.getElementById('xxx').innerText = 'your name'展示回頁面中即可。

常見的處理方式如下:

const htmlEncode = function (handleString){
    return handleString
    .replace(/&/g,"&")
    .replace(/</g,"&lt;")
    .replace(/>/g,"&gt;")
    .replace(/ /g,"&nbsp;")
    .replace(/\'/g,"&#39;")
    .replace(/\"/g,"&quot;");
}

2、引入XSS庫針對使用者輸入源過濾,設定標籤白名單

前面說明了在非富文字的情況下較為快捷方便的處理方法,然而在大部分論壇中發帖發文章都是富文字編輯的形式,即最後回顯於頁面中的是一段HTML內容,不能單純以文字形式處理。

大部分的富文字編輯器原理,都是提供一個具備contenteditable屬性的dom元素,讓使用者對一段富文字進行編輯,其本質是對一段html進行處理,新增或刪除樣式等,最後通過回傳富文字框中html的方式提供給開發者,意味著我們要允許使用者填充一段html於我們的頁面中。而獲取到的html字串我們不能直接進行簡單的標籤替換,否則會導致原有的樣式丟失,最終展示在頁面中的也不再是一篇排版精緻的文章,因此我們要另尋他路。

首先需要明確的是,無論是這份來自於富文字編輯器的html,還是來自於終端使用者發起請求所獲取到的html,都是不可信的,意味著在前端進行過濾是沒有任何實際意義和價值的,因為攻擊者可以輕易的偽造請求繞過限制,所以我們需要在我們的伺服器端針對這段html進行過濾處理。針對html的標籤白名單過濾,不同的語言有不同的庫實現,這裡主要介紹nodejs中常用的標籤過濾庫,nodejs中常用的庫主要是xss和xss-filter,下面以xss庫的使用為例:

import * as xss from 'xss';
function handleXss(content: Content) {
  // 設定HTML過濾器的白名單
  const options = {
    whiteList: {
      p: ['class', 'style'],
      em: ['class', 'style'],
      strong: ['class', 'style'],
      br: ['class', 'style'],
      u: ['class', 'style'],
      s: ['class', 'style'],
      blockquote: ['class', 'style'],
      li: ['class', 'style'],
      ol: ['class', 'style'],
      ul: ['class', 'style'],
      h1: ['class', 'style'],
      h2: ['class', 'style'],
      h3: ['class', 'style'],
      h4: ['class', 'style'],
      h5: ['class', 'style'],
      h6: ['class', 'style'],
      span: ['class', 'style'],
      div: ['class', 'style'],
      img: ['src', 'class', 'style', 'width'],
    },
  }; 
  // 自定義規則
  const myxss = new xss.FilterXSS(options);
  // 直接呼叫 myxss.process() 即可
  content.content = myxss.process(content.content);
  return content;
}

通過限定白名單,僅允許常見的文字展示標籤以及圖片img標籤進入白名單,這部分會再過濾後被保留,並且標籤內的class和style屬性也會被保留;其他屬性和諸如script、iframe等標籤都會被直接過濾掉。

這裡為何要嚴格限定標籤的以及屬性的原因,例如一個a標籤可以通過href屬性注入一段類似這樣的js程式碼:

<ahref\="JavaScript:alert('xss')"\>click me</a\>

更有甚者會對注入的字元進行大小寫字元轉換,或轉義成等效字元,或插入空格或tab,但指令碼仍然能正常執行,造成攻擊。

<p onclick="alert('xss')">this is a text</p>
<img src="xxx.com/test.jpg" onerror="alert('xss')"/>

其次,普通的標籤可以直接通過繫結onclick的方式攻擊,即便是img、video等資源載入標籤,也可以通過onload、onerror等事件注入指令碼,可見針對標籤內屬性的過濾也是不可或缺的。

然而本以為這樣已足夠,但是即使是隻開放了class和style屬性開放了,也是不安全的,關鍵在於style屬性,如果任由使用者自定義的話,可以通過style屬性實現:點選劫持(將元素鋪滿整個介面)、載入外域圖片、指令碼注入甚至可以給文章設定一些花裡胡哨的動畫:

<div style="position:absolute;top:0;left:0;width:2000px;height:2000px;z-index:9999;">
點選劫持的元素,阻止頁面其他操作</div>

<div style="background:url(javacript:alert('xss'))">
藉助style標籤注入指令碼,大部分xss過濾庫會幫我們過濾這部分指令碼</div>

<div style="background:url(//xxx.com/H圖.jpg)">
偷偷載入其他網站的小H圖,繞過過濾和稽核</div>

可見style屬性也不容忽視,因此我們需要在option引數中額外為style屬性設定白名單,確保style屬性安全可控:

...
   css: {
      whiteList: {
        color: true,
        'background-color': true,
      },
    },
...

其次為了確保不載入非本域名下的圖片資源,我們也可以再這一層做一些針對img標籤的過濾:

    ...
    stripIgnoreTag: true,
    onTagAttr: (tag:string, name:string, value:string, isWhiteAttr:boolean) => {
      // 判斷img下的src屬性 如果非本域名下 返回空
      if (isWhiteAttr && tag === 'img' && name === 'src' && !checkLegal(value)) 
      {
        return '#';
      }
    },
    ...

3、cookie設定HttpOnly,配合token或驗證碼防範

針對資訊源的過濾,針對不可信資料來源的過濾,已經能達到初步的效果,但這遠遠不夠,畢竟沒有絕對的安全。

由於大部分攻擊者會想要獲取到使用者的cookie去做別的壞事,所以我們需要在http的響應頭set-cookie時設定httpOnly,讓瀏覽器知道不能通過document.cookie的方式獲取到cookie內容。

app.get('/', (req, res) => {
    if(req.cookies.isVisit) {
        console.log(req.cookies)
        res.send('歡迎再次光臨')
    } else {
        res.cookie('isVisit', 1, {maxAge: 3600 * 1000, httpOnly: true}) 
        res.send('歡迎初次光臨')
    }
})

雖然避免了攻擊者直接獲取到cookie,但是攻擊者仍然可以頁面內發起別的請求,直接篡改使用者的資訊,因此需要我們配合token或者驗證碼的形式,防止攻擊者直接通過指令碼的方式篡改使用者個人資訊。而這個token類似於CSRF中我們所需要的token,不過如果攻擊者仔細研究了程式碼,並且知道的token在頁面中的來源,也是可以直接獲取到token的,因此,相比之下驗證碼安全性更高。

4、設定CSP安全策略

除了針對資料來源的嚴格過濾以外,CSP安全策略的限制也是主要的XSS防範手段之一,通過在頁面中設定允許載入的資源的來源,來嚴格限制頁面可載入的指令碼以及圖片等資源,防止外部的指令碼攻擊後注入其他指令碼以及內容。

CSP,內容安全策略,是一種基於內容的宣告式網路應用程式機制,對緩解內容注入漏洞的危害非常有效。通過一系列指令告訴客戶端(如瀏覽器)被保護資源(如頁面)內只允許載入和執行指令集中限定的內容,類似白名單機制,不滿足限定條件的資源和內容將被客戶端阻斷或不被執行。可以通過兩種方式設定CSP,一種是meta標籤,一種是HTTP響應頭Content-Security-Policy:

指令及其說明:
default-src 定義資源預設載入策略
connect-src 定義Ajax、 WebSocket等載入策略
font-src 定義Font 載入策略
frame- src 定義Frame載入策略
img-src 定義圖片載入策略
media- src 定義<audio>、 <video> 等引用資源載入策略
object-src 定義 <applet>、 <embed>、 <object> 等引用資源載入策略
script-src 定義JS載入策略
style- src 定義css載入策略

對應的指令value可以設定為:(圖片引自:https://www.jianshu.com/p/4bad03d89c04)

相信聰明的你看了這個規則一看就懂了,接下來,以meta標籤設定CSP為例,我們可以如下設定,以此來限制對白名單外資源載入:

<meta http-equiv="Content-Security-Policy" content=
    "script-src 'self' *.qq.com *.cdn-go.cn; 
    img-src 'self' *.cdn-go.cn *.gtimg.cn data:;
    style-src 'unsafe-inline' *.cdn-go.cn; 
    media-src 'none'; 
    child-src *.qq.com">

5、其他HTTP安全響應頭設定

除了CSP這個最常用最強勢的限制規則外,還有其他的HTTP安全響應頭,如果心情好的話,可以統統整上(滑稽):

1)、X-Frame-Options:X-Frame-Options HTTP 響應頭是用來給瀏覽器指示允許一個頁面可否在 <frame>, <iframe>或者 <object> 中展現的標記。網站可以使用此功能,來確保自己網站的內容沒有被嵌到別人的網站中去,也從而避免了點選劫持 (clickjacking) 的攻擊。
X-Frame-Options 有三個值:

DENY
表示該頁面不允許在 frame 中展示,即便是在相同域名的頁面中巢狀也不允許。
SAMEORIGIN
表示該頁面可以在相同域名頁面的 frame 中展示。
ALLOW-FROM uri
表示該頁面可以在指定來源的 frame 中展示。

2)、X-Content-Type-Options:X-Content-Type-Options 響應首部相當於一個提示標誌,被伺服器用來提示客戶端一定要遵循在 Content-Type 首部中對 MIME 型別 的設定,而不能對其進行修改。這就禁用了客戶端的 MIME 型別嗅探行為,(型別嗅探指的是瀏覽器在載入資源時,自動嗅探資源型別過程中,導致執行了注入指令碼),換句話說,也就是意味著網站管理員確定自己的設定沒有問題。

3)、X-XSS-Protection:當檢測到跨站指令碼攻擊 (XSS)時,瀏覽器將停止載入頁面。

然而這個響應頭由於只有IE、谷歌等少部分瀏覽器支援,且不同瀏覽器的實現不一致,存在錯誤過濾行為,甚至可能導致帶額外的漏洞,所以在實際情況中較少使用。(為避免錯誤過濾,部分入口網站會將其值設定為0)

X-XSS-Protection: 0
X-XSS-Protection: 1
X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; report=<reporting-uri>
0禁止XSS過濾。
1啟用XSS過濾(通常瀏覽器是預設的)。 如果檢測到跨站指令碼攻擊,瀏覽器將清除頁面(刪除不安全的部分)。
1;mode=block啟用XSS過濾。 如果檢測到攻擊,瀏覽器將不會清除頁面,而是阻止頁面載入。
1; report=<reporting-URI> (Chromium only)啟用XSS過濾。 如果檢測到跨站指令碼攻擊,瀏覽器將清除頁面並使用CSP report-uri指令的功能傳送違規報告。

以推特網站為例,其設定的響應頭如下:
x-content-type-options: nosniff
x-frame-options: DENY
x-xss-protection: 0

廣州vi設計公司 http://www.maiqicn.com 我的007辦公資源網 https://www.wode007.com

三、總結

本文主要總結歸納了實踐過程中針對XSS漏洞的防護措施,首先針對使用者輸入源和不可信資料來源需要我們做好必要的過濾和校驗,其次,通過CSP安全策略和其他HTTP響應頭的設定等進一步確保安全性。

只有綜合多種手段和方法才能確保自己的網站更加安全,如果不安全,那麼就刪git倉庫跑路。