跨站腳本攻擊(Cross Site Script),原本縮寫是 CSS, 可是爲了和層疊樣式表(Cascading Style Sheet, CSS)有所區分,因此安全領域叫作 「XSS」;html
XSS攻擊,一般是指攻擊者經過 「HTML注入」篡改了網頁,插入了惡意的腳本,從而在用戶瀏覽網頁時,對用戶的瀏覽器進行控制或者獲取用戶的敏感信息(Cookie, SessionID等)的一種攻擊方式。前端
頁面被注入了惡意JavaScript腳本,瀏覽器沒法判斷區分這些腳本是被惡意注入的,仍是正常的頁面內容,因此惡意注入Javascript腳本也擁有了全部的腳本權限。若是頁面被注入了惡意 JavaScript腳本,它能夠作哪些事情呢?git
XSS攻擊能夠分爲三類:反射型,存儲型,基於DOM型(DOM based XSS)github
惡意腳本做爲網絡請求的一部分。數據庫
const Koa = require("koa"); const app = new Koa(); app.use(async ctx => { // ctx.body 即服務端響應的數據 ctx.body = '<script>alert("反射型 XSS 攻擊")</script>'; }) app.listen(3000, () => { console.log('啓動成功'); });
訪問 http://127.0.0.1:3000/
能夠看到 alert執行json
舉一個常見的場景,咱們經過頁面的url的一個參數來控制頁面的展現內容,好比咱們把上面的一部分代碼改爲下面這樣後端
app.use(async ctx => { // ctx.body 即服務端響應的數據 ctx.body = ctx.query.userName; })
此時訪問 http://127.0.0.1:3000?userName=xiaoming 能夠看到頁面上展現了xiaoming
,此時咱們訪問 http://127.0.0.1:3000?userName=<script>alert("反射型 XSS 攻擊")</script>
, 能夠看到頁面彈出 alert。瀏覽器
經過這個操做,咱們會發現用戶將一段含有惡意代碼的請求提交給服務器,服務器在接收到請求時,又將惡意代碼反射給瀏覽器端,這就是反射型XSS攻擊。另一點須要注意的是,Web 服務器不會存儲反射型 XSS 攻擊的惡意腳本,這是和存儲型 XSS 攻擊不一樣的地方。安全
在實際的開發過程當中,咱們會碰到這樣的場景,在頁面A中點擊某個操做,這個按鈕操做是須要登陸權限的,因此須要跳轉到登陸頁面,登陸完成以後再跳轉會A頁面,咱們是這麼處理的,跳轉登陸頁面的時候,會加一個參數 returnUrl,表示登陸完成以後須要跳轉到哪一個頁面,即這個地址是這樣的 http://xxx.com/login?returnUrl=http://xxx.com/A
,假如這個時候把returnUrl改爲一個script腳本,而你在登陸完成以後,若是沒有對returnUrl進行合法性判斷,而直接經過window.location.href=returnUrl
,這個時候這個惡意腳本就會執行。服務器
存儲型會把用戶輸入的數據「存儲」在服務器。
比較常見的一個場景就是,攻擊者在社區或論壇寫下一篇包含惡意 JavaScript代碼的博客文章或評論,文章或評論發表後,全部訪問該博客文章或評論的用戶,都會在他們的瀏覽器中執行這段惡意的JavaScript代碼。
存儲型攻擊大體須要經歷如下幾個步驟
舉一個簡單的例子,一個登錄頁面,點擊登錄的時候,把數據存儲在後端,登錄完成以後跳轉到首頁,首頁請求一個接口將當前的用戶名顯示到頁面
客戶端代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>XSS-demo</title> <style> .login-wrap { height: 180px; width: 300px; border: 1px solid #ccc; padding: 20px; margin-bottom: 20px; } input { width: 300px; } </style> </head> <body> <div class="login-wrap"> <input type="text" placeholder="用戶名" class="userName"> <br> <input type="password" placeholder="密碼" class="password"> <br> <br> <button class="btn">登錄</button> </div> </body> <script> var btn = document.querySelector('.btn'); btn.onclick = function () { var userName = document.querySelector('.userName').value; var password = document.querySelector('.password').value; fetch('http://localhost:3200/login', { method: 'POST', body: JSON.stringify({ userName, password }), headers:{ 'Content-Type': 'application/json' }, mode: 'cors' }) .then(function (response) { return response.json(); }) .then(function (res) { alert(res.msg); window.location.href= "http://localhost:3200/home"; }) .catch(err => { message.error(`本地測試錯誤 ${err.message}`); console.error('本地測試錯誤', err); }); } </script> </html>
服務端代碼
const Koa = require("koa"); const app = new Koa(); const route = require('koa-route'); var bodyParser = require('koa-bodyparser'); const cors = require('@koa/cors'); // 臨時用一個變量來存儲,實際應該存在數據庫中 let currentUserName = ''; app.use(bodyParser()); // 處理post請求的參數 const login = ctx => { const req = ctx.request.body; const userName = req.userName; currentUserName = userName; ctx.response.body = { msg: '登錄成功' }; } const home = ctx => { ctx.body = currentUserName; } app.use(cors()); app.use(route.post('/login', login)); app.use(route.get('/home', home)); app.listen(3200, () => { console.log('啓動成功'); });
點擊登錄將輸入信息提交大服務端,服務端使用變量 currentUserName
來存儲當前的輸入內容,登錄成功後,跳轉到 首頁, 服務端會返回當前的用戶名。若是用戶輸入了惡意腳本內容,則惡意腳本就會在瀏覽器端執行。
在用戶名的輸入框輸入 <script>alert('存儲型 XSS 攻擊')</script>
,執行結果以下
經過惡意腳本修改頁面的DOM節點,是發生在前端的攻擊
基於DOM攻擊大體須要經歷如下幾個步驟
舉個例子:
<body> <div class="login-wrap"> <input type="text" placeholder="輸入url" class="url"> <br> <br> <button class="btn">提交</button> <div class="content"></div> </div> </body> <script> var btn = document.querySelector('.btn'); var content = document.querySelector('.content'); btn.onclick = function () { var url = document.querySelector('.url').value; content.innerHTML = `<a href=${url}>跳轉到輸入的url</a>` } </script>
點擊提交按鈕,會在當前頁面插入一個超連接,其地址爲文本框的內容。
在輸入框輸入 以下內容
'' onclick=alert('哈哈,你被攻擊了')
執行結果以下
首先用兩個單引號閉合調 href屬性,而後插入一個onclick事件。點擊這個新生成的連接,腳本將被執行。
上面的代碼是經過執行 執行alert
來演示的攻擊類型,一樣你能夠把上面的腳本代碼修改成任何你想執行的代碼,好比獲取 用戶的 cookie等信息,<script>alert(document.cookie)</script>
,一樣也是能夠的.
因爲不少XSS攻擊都是來盜用Cookie
的,所以能夠經過 使用HttpOnly屬性來防止直接經過 document.cookie
來獲取 cookie
。
一個Cookie
的使用過程以下
Cookie
Set-Cookie
頭,向客戶端瀏覽器寫入Cookie
Cookie
到期前,瀏覽器訪問該域下的全部頁面,都將發送該Cookie
HttpOnly
是在 Set-Cookie
時標記的:
一般服務器能夠將某些 Cookie 設置爲 HttpOnly 標誌,HttpOnly 是服務器經過 HTTP 響應頭來設置的。
const login = ctx => { // 簡單設置一個cookie ctx.cookies.set( 'cid', 'hello world', { domain: 'localhost', // 寫cookie所在的域名 path: '/home', // 寫cookie所在的路徑 maxAge: 10 * 60 * 1000, // cookie有效時長 expires: new Date('2021-02-15'), // cookie失效時間 httpOnly: true, // 是否只用於http請求中獲取 overwrite: false // 是否容許重寫 } ) }
須要注意的一點是:HttpOnly 並不是阻止 XSS 攻擊,而是能阻止 XSS 攻擊後的 Cookie 劫持攻擊。
永遠不要相信用戶的輸入。
輸入檢查通常是檢查用戶輸入的數據是都包含一些特殊字符,如 <
、>
, '
及"
等。若是發現特殊字符,則將這些字符過濾或編碼。這種能夠稱爲 「XSS Filter」。
安全的編碼函數
針對HTML代碼的編碼方式是 HtmlEncode(是一種函數實現,將字符串轉成 HTMLEntrities)
& --> & < --> < > --> > " --> "
相應的, JavaScript的編碼方式可使用 JavascriptEncode。
假如說用戶輸入了 <script>alert("你被攻擊了")</script>
,咱們要對用戶輸入的內容進行過濾(若是包含了 <script>
等敏感字符,就過濾掉)或者對其編碼,若是是惡意的腳本,則會變成下面這樣
<script>alert("你被攻擊了");</script>
通過轉碼以後的內容,如 <script>
標籤被轉換爲 <script>
,即便這段腳本返回給頁面,頁面也不會指向這段代碼。
咱們能夠回看一下上面的例子
btn.onclick = function () { var url = document.querySelector('.url').value; content.innerHTML = `<a href=${url}>跳轉到輸入的url</a>` }
事實上,DOM Based XSS 是從 JavaScript中輸出數據到HTML頁面裏。
用戶輸入 '' onclick=alert('哈哈,你被攻擊了')
,而後經過 innerHTML 修改DOM的內容,就變成了 <a href='' onclick=alert('哈哈,你被攻擊了')>跳轉到輸入的url</a>
, XSS所以產生。
那麼正確的防護方法是什麼呢?
從JavaScript輸出到HTML頁面,至關於一次 XSS輸出的過程,須要根據不一樣場景進行不一樣的編碼處理
<script>
,執行一次 JavascriptEncode經過JS輸出到HTML頁面
會觸發 DOM Based XSS的地方有不少,好比
...
項目中若是用到,必定要避免在字符串中拼接不可信的數據。
CSP (Content Security Policy) 即內容安全策略,是一種可信白名單機制,能夠在服務端配置瀏覽器哪些外部資源能夠加載和執行。咱們只須要配置規則,如何攔截是由瀏覽器本身實現的。咱們能夠經過這種方式來儘可能減小 XSS 攻擊。
一般能夠經過兩種方式來開啓 CSP:
Content-Security-Policy
Content-Security-Policy: default-src 'self'; // 只容許加載本站資源 Content-Security-Policy: img-src https://* // 只容許加載 HTTPS 協議圖片 Content-Security-Policy: child-src 'none' // 容許加載任何來源框架
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
更多配置策略能夠查看 Content-Security-Policy文檔