前端安全
隨着互聯網的高速發展,信息安全問題已經成為企業關注的焦點之一,而前端又是引發企業項目安全問題的高危據點。在移動互聯網時代,前端人員除了受到傳統的XSS、CSRF攻擊之外,還時常遇到網絡劫持,非法的Hybrid API 等新型的網絡安全問題。當然,隨着瀏覽器的不斷發展和優化,不斷引入了 CSP、Same-Site Cookies 等新的技術來解決問題。下面討論一下前端人員如何防止XSS攻擊。
XSS攻擊的介紹
XSS(Cross-Site Scripting,跨域腳本攻擊)攻擊是最常見的 Web 攻擊,是一種代碼注入攻擊。攻擊者通過在目標網站上注入惡意腳本,使之在用戶的瀏覽器上運行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進而危害數據安全。其重點是『跨域』和『客戶端執行』。
XSS攻擊分類
- 反射型XSS:常見情況是攻擊者通過構造一個惡意鏈接的形式,誘導用戶傳播和打開,由於鏈接內所攜帶的參數會回顯於頁面中或作為頁面的處理數據源,最終造成XSS攻擊。
- 存儲型XSS:與前者不同,存儲型XSS是持久化的XSS攻擊方式,通過用戶輸入個人信息或者發表文章的方式將惡意代碼存儲於服務器端,當其他用戶再次訪問頁面時觸發,造成XSS攻擊。
- DOM based型XSS:同樣也是利用對數據源不可靠,缺乏過濾,由於開發過程中,不可避免部分數據需要回填到DOM中,例如href、src、data-id等標簽屬性,而通過構造特殊的字符串閉合原有的DOM標簽,在其中注入script標簽的方式造成攻擊。
XSS的防范手段
針對XSS的防范,需要開發者養成意識,針對用戶輸入源的數據進行過濾,針對頁面不可信任的數據源也要做好過濾,類似於響應頭中的字段、url中的參數、refer等字段都是不可信的,並且結合其他手段和方法,讓頁面更加安全可控。
1. Htmlencode 轉義特殊字符
在大部分的表單填寫中,比如注冊賬號,會運行用戶填寫個人信息,包括昵稱、郵箱、簡介描述等,此類信息屬於非富文本類型,最常用的處理方法是,對尖括號等特殊字符轉義成實體字符進行存儲,由於是非富文本信息,並且以標簽內容形式展示,推薦使用 innerText 展示到頁面中。
const htmlEncode = function (handleString){ return handleString .replace(/&/g,"&") .replace(/</g,"<") .replace(/>/g,">") .replace(/ /g," ") .replace(/\'/g,"'") .replace(/\"/g,"""); }
2. 引入XSS庫針對用戶輸入源過濾,設置標簽白名單
上面提到的都是非富文本的處理方法,然而在很多論壇、博客、商城中的頁面排版,都是通過富文本的形式編輯的,即回顯到頁面中的就是一段HTML內容,不能再用純文本的形式處理了。
大部分的富文本編輯器原理,都是提供一個具備contenteditable屬性的dom元素,讓用戶對一段富文本進行編輯,其本質是對一段html進行處理,新增或刪除樣式等,最后通過回傳富文本框中html的方式提供給開發者,意味着我們要允許用戶填充一段html於我們的頁面中。而獲取到的html字符串我們不能直接進行簡單的標簽替換,否則會導致原有的樣式丟失,最終展示在頁面中的也不再是一篇排版精致的文章,因此我們要另尋他路。
無論是這份來自於富文本編輯器的html,還是來自於最終用戶發起請求所獲取到的html,都是不可信的,意味着在前端進行過濾是沒有任何實際意義和價值的,因為攻擊者可以輕易的偽造請求繞過限制,所以我們需要在我們的服務器端針對這段html進行過濾處理。針對html的標簽白名單過濾,不同的語言有不同的庫實現,這里主要介紹nodejs中常用的標簽過濾庫,nodejs中常用的庫主要是xss和xss-filter,下面以xss庫的使用為例:
// npm install xss const xss = require("xss"); function handleXss(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= myxss.process(content); return content; }
通過限定白名單,僅允許常見的文本展示標簽以及圖片img標簽進入白名單,這部分會再過濾后被保留,並且標簽內的class和style屬性也會被保留;其他屬性和諸如script、iframe等標簽都會被直接過濾掉。
const html = ` <p class="test" onclick="alert('xss');" ab="cd">123</p> <img src="/xxx.png" onerror="alert('xss')" /> <12a></12a> <script></script> `; console.log(handleXss(html)); /* <p class="test">123</p> <img src="/xxx.png" /> <12a></12a> <script></script> */
普通的標簽可以直接通過綁定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加載策略
接下來,以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">
參考:
https://github.com/leizongmin/js-xss/blob/master/README.zh.md
https://www.jianshu.com/p/4bad03d89c04