👆 這是第 70 篇 不摻水的原創 ,想要了解更多 ,請戳上方藍色字體: 政採雲前端團隊 關注咱們吧~javascript
本文首發於政採雲前端團隊博客:淺談 React 中的 XSS 攻擊css
https://www.zoo.team/article/xss-in-reacthtml
前言
前端通常會面臨 XSS 這樣的安全風險,但隨着 React 等現代前端框架的流行,使咱們在平時開發時不用太關注安全問題。以 React 爲例,React 從設計層面上就具有了很好的防護 XSS 的能力。本文將以源碼角度,看看 React 作了哪些事情來實現這種安全性的。前端
XSS 攻擊是什麼
Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼注入攻擊。XSS 攻擊一般指的是利用網頁的漏洞,攻擊者經過巧妙的方法注入 XSS 代碼到網頁,由於瀏覽器沒法分辨哪些腳本是可信的,致使 XSS 腳本被執行。XSS 腳本一般可以竊取用戶數據併發送到攻擊者的網站,或者冒充用戶,調用目標網站接口並執行攻擊者指定的操做。java
XSS 攻擊類型
反射型 XSS
-
XSS 腳原本自當前 HTTP 請求react
-
當服務器在 HTTP 請求中接收數據並將該數據拼接在 HTML 中返回時,例子:數據庫
// 某網站具備搜索功能,該功能經過 URL 參數接收用戶提供的搜索詞: https://xxx.com/search?query=123 // 服務器在對此 URL 的響應中回顯提供的搜索詞: <p>您搜索的是: 123</p> // 若是服務器不對數據進行轉義等處理,則攻擊者能夠構造以下連接進行攻擊: https://xxx.com/search?query=<img src="empty.png" onerror ="alert('xss')"> // 該 URL 將致使如下響應,並運行 alert('xss'): <p>您搜索的是: <img src="empty.png" onerror ="alert('xss')"></p> // 若是有用戶請求攻擊者的 URL ,則攻擊者提供的腳本將在用戶的瀏覽器中執行。
存儲型 XSS
-
XSS 腳原本自服務器數據庫中後端
-
攻擊者將惡意代碼提交到目標網站的數據庫中,普通用戶訪問網站時服務器將惡意代碼返回,瀏覽器默認執行,例子:promise
// 某個評論頁,能查看用戶評論。 // 攻擊者將惡意代碼當作評論提交,服務器沒對數據進行轉義等處理 // 評論輸入: <textarea> <img src="empty.png" onerror ="alert('xss')"> </textarea> // 則攻擊者提供的腳本將在全部訪問該評論頁的用戶瀏覽器執行
DOM 型 XSS
該漏洞存在於客戶端代碼,與服務器無關瀏覽器
-
相似反射型,區別在於 DOM 型 XSS 並不會和後臺進行交互,前端直接將 URL 中的數據不作處理並動態插入到 HTML 中,是純粹的前端安全問題,要作防護也只能在客戶端上進行防護。
React 如何防止 XSS 攻擊
不管使用哪一種攻擊方式,其本質就是將惡意代碼注入到應用中,瀏覽器去默認執行。React 官方中提到了 React DOM 在渲染全部輸入內容以前,默認會進行轉義。它能夠確保在你的應用中,永遠不會注入那些並不是本身明確編寫的內容。全部的內容在渲染以前都被轉換成了字符串,所以惡意代碼沒法成功注入,從而有效地防止了 XSS 攻擊。咱們具體看下:
自動轉義
React 在渲染 HTML 內容和渲染 DOM 屬性時都會將 "'&<>
這幾個字符進行轉義,轉義部分源碼以下:
for (index = match.index; index < str.length; index++) { switch (str.charCodeAt(index)) { case 34: // " escape = '"'; break; case 38: // & escape = '&'; break; case 39: // ' escape = '''; break; case 60: // < escape = '<'; break; case 62: // > escape = '>'; break; default: continue; } }
這段代碼是 React 在渲染到瀏覽器前進行的轉義,能夠看到對瀏覽器有特殊含義的字符都被轉義了,惡意代碼在渲染到 HTML 前都被轉成了字符串,以下:
// 一段惡意代碼 <img src="empty.png" onerror ="alert('xss')"> // 轉義後輸出到 html 中 <img src="empty.png" onerror ="alert('xss')">
這樣就有效的防止了 XSS 攻擊。
JSX 語法
JSX 其實是一種語法糖,Babel 會把 JSX 編譯成 React.createElement()
的函數調用,最終返回一個 ReactElement
,如下爲這幾個步驟對應的代碼:
// JSX const element = ( <h1 className="greeting"> Hello, world! </h1> ); // 經過 babel 編譯後的代碼 const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' ); // React.createElement() 方法返回的 ReactElement const element = { $$typeof: Symbol('react.element'), type: 'h1', key: null, props: { children: 'Hello, world!', className: 'greeting' } ... }
咱們能夠看到,最終渲染的內容是在 Children 屬性中,那瞭解了 JSX 的原理後,咱們來試試可否經過構造特殊的 Children 進行 XSS 注入,來看下面一段代碼:
const storedData = `{ "ref":null, "type":"body", "props":{ "dangerouslySetInnerHTML":{ "__html":"<img src=\"empty.png\" onerror =\"alert('xss')\"/>" } } }`; // 轉成 JSON const parsedData = JSON.parse(storedData); // 將數據渲染到頁面 render () { return <span> {parsedData} </span>; }
這段代碼中, 運行後會報如下錯誤,提示不是有效的 ReactChild
Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {ref, type, props}). If you meant to render a collection of children, use an array instead.
那到底是哪裏出問題了?咱們看一下 ReactElement 的源碼:
const symbolFor = Symbol.for; REACT_ELEMENT_TYPE = symbolFor('react.element'); const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // 這個 tag 惟一標識了此爲 ReactElement $$typeof: REACT_ELEMENT_TYPE, // 元素的內置屬性 type: type, key: key, ref: ref, props: props, // 記錄建立此元素的組件 _owner: owner, }; ... return element; }
注意到其中有個屬性是 $$typeof
,它是用來標記此對象是一個 ReactElement
,React 在進行渲染前會經過此屬性進行校驗,校驗不經過將會拋出上面的錯誤。React 利用這個屬性來防止經過構造特殊的 Children 來進行的 XSS 攻擊,緣由是 $$typeof
是個 Symbol 類型,進行 JSON 轉換後會 Symbol 值會丟失,沒法在先後端進行傳輸。若是用戶提交了特殊的 Children,也沒法進行渲染,利用此特性,能夠防止存儲型的 XSS 攻擊。
在 React 中可引發漏洞的一些寫法
使用 dangerouslySetInnerHTML
dangerouslySetInnerHTML
是 React 爲瀏覽器 DOM 提供 innerHTML
的替換方案。一般來說,使用代碼直接設置 HTML 存在風險,由於很容易使用戶暴露在 XSS 攻擊下,由於當使用 dangerouslySetInnerHTML
時,React 將不會對輸入進行任何處理並直接渲染到 HTML 中,若是攻擊者在 dangerouslySetInnerHTML 傳入了惡意代碼,那麼瀏覽器將會運行惡意代碼。看下源碼:
function getNonChildrenInnerMarkup(props) { const innerHTML = props.dangerouslySetInnerHTML; // 有dangerouslySetInnerHTML屬性,會不經轉義就渲染__html的內容 if (innerHTML != null) { if (innerHTML.__html != null) { return innerHTML.__html; } } else { const content = props.children; if (typeof content === 'string' || typeof content === 'number') { return escapeTextForBrowser(content); } } return null; }
因此平時開發時最好避免使用 dangerouslySetInnerHTML
,若是不得不使用的話,前端或服務端必須對輸入進行相關驗證,例如對特殊輸入進行過濾、轉義等處理。前端這邊處理的話,推薦使用白名單過濾 (https://jsxss.com/zh/index.html),經過白名單控制容許的 HTML 標籤及各標籤的屬性。
經過用戶提供的對象來建立 React 組件
舉個例子:
// 用戶的輸入 const userProvidePropsString = `{"dangerouslySetInnerHTML":{"__html":"<img onerror='alert(\"xss\");' src='empty.png' />"}}"`; // 通過 JSON 轉換 const userProvideProps = JSON.parse(userProvidePropsString); // userProvideProps = { // dangerouslySetInnerHTML: { // "__html": `<img onerror='alert("xss");' src='empty.png' />` // } // }; render() { // 出於某種緣由解析用戶提供的 JSON 並將對象做爲 props 傳遞 return <div {...userProvideProps} /> }
這段代碼將用戶提供的數據進行 JSON 轉換後直接當作 div
的屬性,當用戶構造了相似例子中的特殊字符串時,頁面就會被注入惡意代碼,因此要注意平時在開發中不要直接使用用戶的輸入做爲屬性。
使用用戶輸入的值來渲染 a 標籤的 href 屬性,或相似 img 標籤的 src 屬性等
const userWebsite = "javascript:alert('xss');"; <a href={userWebsite}></a>
若是沒有對該 URL 進行過濾以防止經過 javascript:
或 data:
來執行 JavaScript,則攻擊者能夠構造 XSS 攻擊,此處會有潛在的安全問題。用戶提供的 URL 須要在前端或者服務端在入庫以前進行驗證並過濾。
服務端如何防止 XSS 攻擊
服務端做爲最後一道防線,也須要作一些措施以防止 XSS 攻擊,通常涉及如下幾方面:
-
在接收到用戶輸入時,須要對輸入進行儘量嚴格的過濾,過濾或移除特殊的 HTML 標籤、JS 事件的關鍵字等。
-
在輸出時對數據進行轉義,根據輸出語境 (html/javascript/css/url),進行對應的轉義
-
對關鍵 Cookie 設置 http-only 屬性,JS腳本就不能訪問到 http-only 的 Cookie 了
-
利用 CSP (https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP) 來抵禦或者削弱 XSS 攻擊,一個 CSP 兼容的瀏覽器將會僅執行從白名單域獲取到的腳本文件,忽略全部的其餘腳本 (包括內聯腳本和 HTML 的事件處理屬性)
總結
出現 XSS 漏洞本質上是輸入輸出驗證不充分,React 在設計上已經很安全了,可是一些反模式的寫法仍是會引發安全漏洞。Vue 也是相似,Vue 作的安全措施主要也是轉義,HTML 的內容和動態綁定的屬性都會進行轉義。不管使用 React 或 Vue 等前端框架,都不能百分百的防止 XSS 攻擊,因此服務端必須對前端參數作一些驗證,包括但不限於特殊字符轉義、標籤、屬性白名單過濾等。一旦出現安全問題通常都是挺嚴重的,不論是敏感數據被竊取或者用戶資金被盜,損失每每沒法挽回。咱們平時開發中須要保持安全意識,保持代碼的可靠性和安全性。
小遊戲
看完文章能夠嘗試下 XSS 的 小遊戲 (https://xss-game.appspot.com/),本身動手實踐模擬 XSS 攻擊,能夠對 XSS 有更進一步的認識。
看完兩件事
若是你以爲這篇內容對你挺有啓發,我想邀請你幫我兩件小事
1.點個「 在看」,讓更多人也能看到這篇內容(點了 「 在看 」,bug -1 😊)
2.關注公衆號「 政採雲前端團隊」,持續爲你推送精選好文
本文分享自微信公衆號 - 政採雲前端團隊(Zoo-Team)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。