web安全是前端開發者們需要關注和掌握的必要內容。在寫該記錄之前,我也總是對安全策略這方面點到為止;但是在真正瞭解之後才發現,頁面能安全活到現在也算是老天和後臺、運維同事的關照了。?
本次記錄主要有三點:xss、csrf、請求劫持和https。
這一篇,會詳細記錄xss的攻擊和防禦。另外兩點會分為兩篇記錄文:詳解二,和詳解三。
1. XSS 漏洞的發生和修復
XSS(cross-site scripting)跨站指令碼攻擊是指頁面被注入惡意程式碼。
例1:根據請求介面URL引數決定頁面展示內容時:
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜尋</button>
<div>
您搜尋的關鍵詞是:<%= getParameter("keyword") %>
</div>複製程式碼
getParameter('keyword')是http請求的函式
url:http://xxx/search?keyword="><script>alert('xss');</script>"
引數keyword會被重新拼接到HTML中:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜尋</button><div> 您搜尋的關鍵詞是:"><script>alert('XSS');</script> </div>複製程式碼
惡意程式碼將被執行。
面對這種情況如何防範?
這裡的原因是,瀏覽器把使用者的輸入當成指令碼執行了,那麼將這段內容轉成文字就行。
<input type="text" value="<%= escapeHTML(getParameter("keyword")) %>">
<button>搜尋</button>
<div>
您搜尋的關鍵詞是:<%= escapeHTML(getParameter("keyword")) %>
</div>複製程式碼
excapeHTML()按照以下規則進行轉譯:
|字元|轉義後的字元| |-|-| |&
|&
| |<
|<
| |>
|>
| |"
|"
| |'
|'
| |/
|/
|
經轉譯後,最終瀏覽器接收到的響應為:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜尋</button>
<div>
您搜尋的關鍵詞是:"><script>alert('XSS');</script>
</div>複製程式碼
例2:a標籤的href屬性
<a href="<%= escapeHTML(getParameter("redirect_to")) %>">跳轉...</a>複製程式碼
getParameter('redirect_to')為URL http://xxx/?redirect_to=javascript:alert('XSS')
即使做了轉譯,javascript:仍然是正確的href屬性值,包括在javascript:前面加空格%20,也依然會跳過HTML轉譯檢查。
解決辦法:白名單過濾,禁止'javascript:'連結、非法schema等。
例3:把資料透過JSON的方式內聯到HTML中
<script>
var initData = <%= data.toJSON() %>
</script>複製程式碼
這時不能使用escapeHTML(),因為轉譯"後,JSON格式會被破壞。
但是內聯JSON也有不安全的地方:
- 當 JSON 中包含
U+2028
或U+2029
這兩個字元時,不能作為 JavaScript 的字面量使用,否則會丟擲語法錯誤。 - 當 JSON 中包含字串
</script>
時,當前的 script 標籤將會被閉合,後面的字串內容瀏覽器會按照 HTML 進行解析;透過增加下一個<script>
標籤等方法就可以完成注入。
因此,需要escapeEmbedJSON()函式,對內聯JSON進行轉義:
|字元|轉義後的字元| |-|-| |U+2028
|\u2028
| |U+2029
|\u2029
| |<
|\u003c
|
2. 漏洞總結
- 惡意內容以<script>標籤形式注入HTML內嵌的文字中
- 內聯的javascript中,拼接的資料超過escapeHTML()的限制
- 標籤屬性中,惡意內容若包含引號,可以注入其他屬性或標籤
- 標籤的href、src屬性中,包含javascript:等可執行程式碼
- 在onload, onerror, onclick等事件中,注入惡意程式碼
3. XSS攻擊的分類
利用惡意指令碼攻擊,攻擊者可以獲取使用者的敏感資訊如Cookie、SessionID等。
使用者輸入行為,以下內容都不可信:
- 來自使用者的 UGC(user generated content) 資訊
- 來自第三方的連結
- URL 引數
- POST 引數
- Referer (可能來自不可信的來源)
- Cookie (可能來自其他子域注入)
根據攻擊來源,可以分為儲存型、反射型和DOM型三類。
|型別|儲存區|插入點|
|儲存型 XSS|後端資料庫|HTML| |反射型 XSS|URL|HTML| |DOM 型 XSS|後端資料庫/前端儲存/URL|前端 JavaScript
儲存型XSS攻擊步驟:
1. 攻擊者將惡意程式碼提交到目標網站資料庫
2. 使用者開啟目標網站時,網站服務端將惡意程式碼從資料庫提出,拼接到HTML中返回給瀏覽器
3. 使用者瀏覽器接收到響應後解析執行,惡意程式碼也被執行
4. 惡意程式碼竊取使用者資料併傳送到攻擊者網站,或者冒充使用者行為,呼叫目標網站介面執行惡意操作
這種攻擊常見於帶有使用者儲存資料的網站功能,如論壇發帖、商品評論、使用者私信等。
反射型XSS攻擊步驟:
1. 攻擊者構造出包含惡意程式碼的URL
2. 使用者開啟含惡意程式碼的URL,網站服務端取出惡意程式碼並拼接到HTML返回瀏覽器
3. 瀏覽器解析執行,竊取使用者資料或冒充使用者行為
與儲存型的區別,反射型的惡意程式碼儲存在URL,儲存型的儲存在資料庫。反射型XSS漏洞常見於透過URL傳參的功能,如網站搜尋、跳轉等。
由於需要使用者主動開啟,所以會有多種誘導使用者點選的手段。POST的內容也可以觸發反射型XSS,只不過需要構造表單提交頁面引導使用者點選,所以少見。
DOM型攻擊步驟:
1. 攻擊者構造出包含惡意程式碼的URL
2. 使用者開啟該URL
3. 瀏覽器接收到響應後解析執行,前端JS取出URL中的惡意程式碼並執行。
4. 惡意程式碼竊取使用者資料併傳送到攻擊者網站,或者冒充使用者行為,呼叫目標網站介面執行惡意操作
DOM型XSS屬於前端Javascript自身的安全漏洞,而前兩種屬於服務端的安全漏洞。
4. XSS攻擊的預防
據上所屬,XSS攻擊主要有兩大要素:
1. 攻擊者提交惡意程式碼
2. 瀏覽器執行惡意程式碼
從一開始的案列中得知,最簡單的是對使用者輸入文字的轉義。但是也知道轉義存在弊端。
除此轉義之外,也可以對一些必要的輸入做檢查,如電話號碼、數字、URL、郵件地址等。
預防儲存型和反射型XSS
儲存型和反射型都是在服務端取出惡意程式碼後,插入html的,被瀏覽器執行。所以常見的預防方式有兩種:改成純前端渲染,把程式碼和資料分隔開;對html做充分轉義。
純前端渲染:瀏覽器先載入靜態頁面(不包含任何業務相關的資料),再執行javascript,透過ajax載入業務資料,呼叫DOM API更新到頁面上。純前端渲染中,瀏覽器會明確文字(.innerText),屬性(.setAttribute),還是樣式(.style)等等。但仍須注意避免DOM型XSS(請參考下文‘預防DOM型XSS攻擊’)。
很多內部、管理系統中,適合使用純前端渲染;但對於效能要求高的,或有SEO需求的頁面,拼接HTML的問題仍存在。
轉義HTML:除了上面說到的excapeHTML()等方法外,還可以直接使用模板引擎,如ejs、doT.js、FreeMarker等,通常就是把& < > " ' /這些轉義掉,確實能起到一定的轉義作用,但並不完善。所以還可以結合後臺程式語言,找到合適的轉義庫。如JAVA工程裡,常用的轉義庫org.owasp.encode,不同上下文要使用相應的轉移規則。
預防DOM型XSS攻擊
DOM型XSS攻擊就與前段javascript程式碼本身是否嚴謹有關。
儘量避免使用.innerHTML、outerHTML、document.write(),而換成.textContent、.setAttribute()等。
如果用Vue/React技術棧,並且不使用v-html/dangerouslySetInnerHTML功能,就在前段render階段避免innerHTL、outerHTML的XSS隱患。
DOM 中的內聯事件監聽器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
標籤的 href
屬性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字串作為程式碼執行。如果不可信的資料拼接到字串中傳遞給這些 API,很容易產生安全隱患,請務必避免。
其他XSS預防措施
Content Security Policy,俗稱csp,在http請求時可在請求頭中顯示。作用:
- 禁止載入規定域以外的程式碼
- 禁止外域提交,網站被攻擊後,使用者的資料不會洩露到外域
- 禁止內聯指令碼執行
- 禁止未授權指令碼執行
- 合理上報XSS問題,利於儘快修復
輸入內容長度控制。
cookie的http-only限制:cookie只允許同域http請求攜帶,不允許讀取和修改。
驗證碼:圖片驗證碼、簡訊驗證碼等等。
5. XSS檢測
通用XSS攻擊字串手動檢測。
掃描工具自動檢測,如Arachni、Mozilla HTTP Observatory、w3af等。
6. XSS攻擊的總結
雖然很難透過技術手段完全避免XSS,但可以儘量減少漏洞的發生:
- 利用模板引擎 開啟模板引擎自帶的 HTML 轉義功能。例如: 在 ejs 中,儘量使用
<%= data %>
而不是<%- data %>
; 在 doT.js 中,儘量使用{{! data }
而不是{{= data }
; 在 FreeMarker 中,確保引擎版本高於 2.3.24,並且選擇正確的freemarker.core.OutputFormat
。 - 避免內聯事件 儘量不要使用
onLoad="onload('{{data}}')"
、onClick="go('{{action}}')"
這種拼接內聯事件的寫法。在 JavaScript 中透過.addEventlistener()
事件繫結會更安全。 - 避免拼接 HTML 前端採用拼接 HTML 的方法比較危險,如果框架允許,使用
createElement
、setAttribute
之類的方法實現。或者採用比較成熟的渲染框架,如 Vue/React 等。 - 時刻保持警惕 在插入位置為 DOM 屬性、連結等位置時,要打起精神,嚴加防範。
- 增加攻擊難度,降低攻擊後果 透過 CSP、輸入長度配置、介面安全措施等方法,增加攻擊的難度,降低攻擊的後果。
- 主動檢測和發現 可使用 XSS 攻擊字串和自動掃描工具尋找潛在的 XSS 漏洞。