web安全之XSS實例解析

XSS

跨站腳本攻擊(Cross Site Script),原本縮寫是 CSS, 可是爲了和層疊樣式表(Cascading Style Sheet, CSS)有所區分,因此安全領域叫作 「XSS」;html

XSS攻擊,一般是指攻擊者經過 「HTML注入」篡改了網頁,插入了惡意的腳本,從而在用戶瀏覽網頁時,對用戶的瀏覽器進行控制或者獲取用戶的敏感信息(Cookie, SessionID等)的一種攻擊方式。前端

頁面被注入了惡意JavaScript腳本,瀏覽器沒法判斷區分這些腳本是被惡意注入的,仍是正常的頁面內容,因此惡意注入Javascript腳本也擁有了全部的腳本權限。若是頁面被注入了惡意 JavaScript腳本,它能夠作哪些事情呢?git

  1. 能夠竊取 cookie信息。惡意 JavaScript能夠經過 」doccument.cookie「獲取cookie信息,而後經過 XMLHttpRequest或者Fetch加上CORS功能將數據發送給惡意服務器;惡意服務器拿到用戶的cookie信息以後,就能夠在其餘電腦上模擬用戶的登錄,而後進行轉帳操做。
  2. 能夠監聽用戶行爲。惡意JavaScript可使用 "addEventListener"接口來監聽鍵盤事件,好比能夠獲取用戶輸入的銀行卡等信息,又能夠作不少違法的事情。
  3. 能夠修改DOM 僞造假的登錄窗口,用來欺騙用戶輸入用戶名和密碼等信息。
  4. 還能夠在頁面內生成浮窗廣告,這些廣告會嚴重影響用戶體驗。

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

反射型XSS1

舉一個常見的場景,咱們經過頁面的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。瀏覽器

反射型XSS2

經過這個操做,咱們會發現用戶將一段含有惡意代碼的請求提交給服務器,服務器在接收到請求時,又將惡意代碼反射給瀏覽器端,這就是反射型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代碼。

存儲型攻擊大體須要經歷如下幾個步驟

  1. 首先攻擊者利用站點漏洞將一段惡意JavaScript代碼提交到網站數據庫中
  2. 而後用戶向網站請求包含了惡意 JavaScript腳本的頁面
  3. 當用戶瀏覽該頁面的時候,惡意腳本就會將用戶的cookie信息等數據上傳到服務器

存儲型XSS

舉一個簡單的例子,一個登錄頁面,點擊登錄的時候,把數據存儲在後端,登錄完成以後跳轉到首頁,首頁請求一個接口將當前的用戶名顯示到頁面

客戶端代碼

<!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>,執行結果以下

存儲型XSS

基於DOM(DOM based XSS)

經過惡意腳本修改頁面的DOM節點,是發生在前端的攻擊

基於DOM攻擊大體須要經歷如下幾個步驟

  1. 攻擊者構造出特殊的URL,其中包含惡意代碼
  2. 用戶打開帶有惡意代碼的URL
  3. 用戶瀏覽器接受到響應後執行解析,前端JavaScript取出URL中的惡意代碼並執行
  4. 惡意代碼竊取用戶數據併發送到攻擊者的網站,冒充用戶行爲,調用目標網站接口執行攻擊者指定的操做。

舉個例子:

<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('哈哈,你被攻擊了')

執行結果以下

基於DOM型XSS

首先用兩個單引號閉合調 href屬性,而後插入一個onclick事件。點擊這個新生成的連接,腳本將被執行。

上面的代碼是經過執行 執行 alert來演示的攻擊類型,一樣你能夠把上面的腳本代碼修改成任何你想執行的代碼,好比獲取 用戶的 cookie等信息, <script>alert(document.cookie)</script>,一樣也是能夠的.

防護XSS

HttpOnly

因爲不少XSS攻擊都是來盜用Cookie的,所以能夠經過 使用HttpOnly屬性來防止直接經過 document.cookie 來獲取 cookie

一個Cookie的使用過程以下

  1. 瀏覽器向服務器發起請求,這時候沒有 Cookie
  2. 服務器返回時設置 Set-Cookie 頭,向客戶端瀏覽器寫入Cookie
  3. 在該 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

須要注意的一點是:HttpOnly 並不是阻止 XSS 攻擊,而是能阻止 XSS 攻擊後的 Cookie 劫持攻擊。

輸入和輸出的檢查

永遠不要相信用戶的輸入

輸入檢查通常是檢查用戶輸入的數據是都包含一些特殊字符,如 <>, '"等。若是發現特殊字符,則將這些字符過濾或編碼。這種能夠稱爲 「XSS Filter」。

安全的編碼函數

針對HTML代碼的編碼方式是 HtmlEncode(是一種函數實現,將字符串轉成 HTMLEntrities)

& --> &amp;
< --> &lt;
> --> &gt;
" --> &quot;

相應的, JavaScript的編碼方式可使用 JavascriptEncode。

假如說用戶輸入了 <script>alert("你被攻擊了")</script>,咱們要對用戶輸入的內容進行過濾(若是包含了 <script> 等敏感字符,就過濾掉)或者對其編碼,若是是惡意的腳本,則會變成下面這樣

&lt;script&gt;alert("你被攻擊了");&lt;/script&gt;

通過轉碼以後的內容,如 <script>標籤被轉換爲 &lt;script&gt;,即便這段腳本返回給頁面,頁面也不會指向這段代碼。

防護 DOM Based XSS

咱們能夠回看一下上面的例子

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輸出的過程,須要根據不一樣場景進行不一樣的編碼處理

  1. 變量輸出到 <script>,執行一次 JavascriptEncode
  2. 經過JS輸出到HTML頁面

    • 輸出事件或者腳本,作 JavascriptEncode 處理
    • 輸出 HTML內容或者屬性,作 HtmlEncode 處理

會觸發 DOM Based XSS的地方有不少,好比

  • xxx.interHTML
  • xxx.outerHTML
  • document.write
  • 頁面中全部的inputs框
  • XMLHttpRequest返回的數據

...

項目中若是用到,必定要避免在字符串中拼接不可信的數據。

利用CSP

CSP (Content Security Policy) 即內容安全策略,是一種可信白名單機制,能夠在服務端配置瀏覽器哪些外部資源能夠加載和執行。咱們只須要配置規則,如何攔截是由瀏覽器本身實現的。咱們能夠經過這種方式來儘可能減小 XSS 攻擊。

一般能夠經過兩種方式來開啓 CSP:

  • 設置 HTTP Header 的 Content-Security-Policy
Content-Security-Policy: default-src 'self'; // 只容許加載本站資源
Content-Security-Policy: img-src https://*  // 只容許加載 HTTPS 協議圖片
Content-Security-Policy: child-src 'none'    // 容許加載任何來源框架
  • 設置 meta 標籤的方式
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

更多配置策略能夠查看 Content-Security-Policy文檔

參考

相關文章
相關標籤/搜索