淺說 XSS 和 CSRF(防止圖片盜鏈)

原文連接:淺說 XSS 和 CSRF

在 Web 安全領域中,XSS 和 CSRF 是最多見的攻擊方式。本文將會簡單介紹 XSS 和 CSRF 的攻防問題。html

聲明:本文的示例僅用於演示相關的攻擊原理前端

XSS

XSS,即 Cross Site Script,中譯是跨站腳本攻擊;其本來縮寫是 CSS,但爲了和層疊樣式表(Cascading Style Sheet)有所區分,於是在安全領域叫作 XSS。vue

XSS 攻擊是指攻擊者在網站上注入惡意的客戶端代碼,經過惡意腳本對客戶端網頁進行篡改,從而在用戶瀏覽網頁時,對用戶瀏覽器進行控制或者獲取用戶隱私數據的一種攻擊方式。git

攻擊者對客戶端網頁注入的惡意腳本通常包括 JavaScript,有時也會包含 HTML 和 Flash。有不少種方式進行 XSS 攻擊,但它們的共同點爲:將一些隱私數據像 cookie、session 發送給攻擊者,將受害者重定向到一個由攻擊者控制的網站,在受害者的機器上進行一些惡意操做。github

XSS攻擊能夠分爲3類:反射型(非持久型)、存儲型(持久型)、基於DOM。web

反射型

反射型 XSS 只是簡單地把用戶輸入的數據 「反射」 給瀏覽器,這種攻擊方式每每須要攻擊者誘使用戶點擊一個惡意連接,或者提交一個表單,或者進入一個惡意網站時,注入腳本進入被攻擊者的網站。chrome

看一個示例。我先準備一個以下的靜態頁:瀏覽器

反射型xss1

惡意連接的地址指向了 localhost:8001/?q=111&p=222。而後,我再啓一個簡單的 Node 服務處理惡意連接的請求:安全

consthttp=require('http');
functionhandleReequest(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200, {'Content-Type':'text/html; charset=UTF-8'});
    res.write('<script>alert("反射型 XSS 攻擊")</script>');
    res.end();
}

constserver=newhttp.Server();
server.listen(8001, '127.0.0.1');
server.on('request', handleReequest);
複製代碼

當用戶點擊惡意連接時,頁面跳轉到攻擊者預先準備的頁面,會發如今攻擊者的頁面執行了 js 腳本:前端框架

執行腳本

這樣就產生了反射型 XSS 攻擊。攻擊者能夠注入任意的惡意腳本進行攻擊,可能注入惡做劇腳本,或者注入能獲取用戶隱私數據(如cookie)的腳本,這取決於攻擊者的目的。

存儲型

存儲型 XSS 會把用戶輸入的數據 "存儲" 在服務器端,當瀏覽器請求數據時,腳本從服務器上傳回並執行。這種 XSS 攻擊具備很強的穩定性。

比較常見的一個場景是攻擊者在社區或論壇上寫下一篇包含惡意 JavaScript 代碼的文章或評論,文章或評論發表後,全部訪問該文章或評論的用戶,都會在他們的瀏覽器中執行這段惡意的 JavaScript 代碼。

舉一個示例。

先準備一個輸入頁面:

<input type="text" id="input">
<button id="btn">Submit</button>   

<script>
    const input = document.getElementById('input');
    const btn = document.getElementById('btn');

    let val;
     
    input.addEventListener('change', (e) => {
        val = e.target.value;
    }, false);

    btn.addEventListener('click', (e) => {
        fetch('http://localhost:8001/save', {
            method: 'POST',
            body: val
        });
    }, false);
</script>     
複製代碼

啓動一個 Node 服務監聽 save 請求。爲了簡化,用一個變量來保存用戶的輸入:

consthttp=require('http');

let userInput ='';

functionhandleReequest(req, res) {
    constmethod=req.method;
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
    
    if (method ==='POST'&&req.url==='/save') {
        let body ='';
        req.on('data', chunk=> {
            body += chunk;
        });

        req.on('end', () => {
            if (body) {
                userInput = body;
            }
            res.end();
        });
    } else {
        res.writeHead(200, {'Content-Type':'text/html; charset=UTF-8'});
        res.write(userInput);
        res.end();
    }
}

constserver=newhttp.Server();
server.listen(8001, '127.0.0.1');

server.on('request', handleReequest);
複製代碼

當用戶點擊提交按鈕將輸入信息提交到服務端時,服務端經過 userInput 變量保存了輸入內容。當用戶經過 http://localhost:8001/${id} 訪問時,服務端會返回與 id 對應的內容(本示例簡化了處理)。若是用戶輸入了惡意腳本內容,則其餘用戶訪問該內容時,惡意腳本就會在瀏覽器端執行:

存儲型xss

基於DOM

基於 DOM 的 XSS 攻擊是指經過惡意腳本修改頁面的 DOM 結構,是純粹發生在客戶端的攻擊。

看以下代碼:

<h2>XSS: </h2>
<inputtype="text"id="input">
<buttonid="btn">Submit</button>
<divid="div"></div>
<script>constinput=document.getElementById('input');constbtn=document.getElementById('btn');constdiv=document.getElementById('div');let val;input.addEventListener('change', (e) => {        val =e.target.value;    }, false);btn.addEventListener('click', () => {div.innerHTML=`<ahref=${val}>testLink</a>`    }, false);</script>
複製代碼

點擊 Submit 按鈕後,會在當前頁面插入一個連接,其地址爲用戶的輸入內容。若是用戶在輸入時構造了以下內容:

'' onclick=alert(/xss/)
複製代碼

用戶提交以後,頁面代碼就變成了:

<ahrefonlick="alert(/xss/)">testLink</a>
複製代碼

此時,用戶點擊生成的連接,就會執行對應的腳本:

dom-xss

XSS 攻擊的防範

如今主流的瀏覽器內置了防範 XSS 的措施,例如 CSP。但對於開發者來講,也應該尋找可靠的解決方案來防止 XSS 攻擊。

HttpOnly 防止劫取 Cookie

HttpOnly 最先由微軟提出,至今已經成爲一個標準。瀏覽器將禁止頁面的Javascript 訪問帶有 HttpOnly 屬性的Cookie。

上文有說到,攻擊者能夠經過注入惡意腳本獲取用戶的 Cookie 信息。一般 Cookie 中都包含了用戶的登陸憑證信息,攻擊者在獲取到 Cookie 以後,則能夠發起 Cookie 劫持攻擊。因此,嚴格來講,HttpOnly 並不是阻止 XSS 攻擊,而是能阻止 XSS 攻擊後的 Cookie 劫持攻擊。

輸入檢查

不要相信用戶的任何輸入。 對於用戶的任何輸入要進行檢查、過濾和轉義。創建可信任的字符和 HTML 標籤白名單,對於不在白名單之列的字符或者標籤進行過濾或編碼。

在 XSS 防護中,輸入檢查通常是檢查用戶輸入的數據中是否包含 <> 等特殊字符,若是存在,則對特殊字符進行過濾或編碼,這種方式也稱爲 XSS Filter。

而在一些前端框架中,都會有一份 decodingMap, 用於對用戶輸入所包含的特殊字符或標籤進行編碼或過濾,如 <>script,防止 XSS 攻擊:

// vuejs 中的 decodingMap
// 在 vuejs 中,若是輸入帶 script 標籤的內容,會直接過濾掉
const decodingMap = {
  '&lt;': '<',
  '&gt;': '>',
  '&quot;': '"',
  '&amp;': '&',
  '&#10;': '\n'
}
複製代碼

輸出檢查

用戶的輸入會存在問題,服務端的輸出也會存在問題。通常來講,除富文本的輸出外,在變量輸出到 HTML 頁面時,可使用編碼或轉義的方式來防護 XSS 攻擊。例如利用 sanitize-html 對輸出內容進行有規則的過濾以後再輸出到頁面中。

CSRF

CSRF,即 Cross Site Request Forgery,中譯是跨站請求僞造,是一種劫持受信任用戶向服務器發送非預期請求的攻擊方式。

一般狀況下,CSRF 攻擊是攻擊者藉助受害者的 Cookie 騙取服務器的信任,能夠在受害者絕不知情的狀況下以受害者名義僞造請求發送給受攻擊服務器,從而在並未受權的狀況下執行在權限保護之下的操做。

在舉例子以前,先說說瀏覽器的 Cookie 策略。

瀏覽器的 Cookie 策略

Cookie 是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。Cookie 主要用於如下三個方面:

  • 會話狀態管理(如用戶登陸狀態、購物車、遊戲分數或其它須要記錄的信息)
  • 個性化設置(如用戶自定義設置、主題等)
  • 個性化設置(如用戶自定義設置、主題等)

而瀏覽器所持有的 Cookie 分爲兩種:

  • Session Cookie(會話期 Cookie):會話期 Cookie 是最簡單的Cookie,它不須要指定過時時間(Expires)或者有效期(Max-Age),它僅在會話期內有效,瀏覽器關閉以後它會被自動刪除。

  • Permanent Cookie(持久性 Cookie):與會話期 Cookie 不一樣的是,持久性 Cookie 能夠指定一個特定的過時時間(Expires)或有效期(Max-Age)。

    res.setHeader('Set-Cookie', ['mycookie=222', 'test=3333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']);

上述代碼建立了兩個 Cookie:mycookietest,前者屬於會話期 Cookie,後者則屬於持久性 Cookie。當咱們去查看 Cookie 相關的屬性時,不一樣的瀏覽器對會話期 Cookie 的 Expires 屬性值會不同:

Firefox:

firefox cookie

Chrome:

chrome cookie

此外,每一個 Cookie 都會有與之關聯的域,這個域的範圍通常經過 donmain 屬性指定。若是 Cookie 的域和頁面的域相同,那麼咱們稱這個 Cookie 爲第一方 Cookie(first-party cookie),若是 Cookie 的域和頁面的域不一樣,則稱之爲第三方 Cookie(third-party cookie)。一個頁面包含圖片或存放在其餘域上的資源(如圖片)時,第一方的 Cookie 也只會發送給設置它們的服務器。

經過 Cookie 進行 CSRF 攻擊

假設有一個 bbs 站點:http://www.c.com,當登陸後的用戶發起以下 GET 請求時,會刪除 ID 指定的帖子:

http://www.c.com:8002/content/delete/:id
複製代碼

如發起 http://www.c.com:8002/content/delete/87343 請求時,會刪除 id 爲 87343 的帖子。當用戶登陸以後,會設置以下 cookie:

res.setHeader('Set-Cookie', ['user=22333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']);
複製代碼

user

user 對應的值是用戶 ID。而後構造一個頁面 A:

<p>CSRF 攻擊者準備的網站:</p>
<img src="http://www.c.com:8002/content/delete/87343">
複製代碼

頁面 A 使用了一個 img 標籤,其地址指向了刪除用戶帖子的連接:

A

能夠看到,當登陸用戶訪問攻擊者的網站時,會向 www.c.com 發起一個刪除用戶帖子的請求。此時若用戶在切換到 www.c.com 的帖子頁面刷新,會發現ID 爲 87343 的帖子已經被刪除。

因爲 Cookie 中包含了用戶的認證信息,當用戶訪問攻擊者準備的攻擊環境時,攻擊者就能夠對服務器發起 CSRF 攻擊。在這個攻擊過程當中,攻擊者藉助受害者的 Cookie 騙取服務器的信任,但並不能拿到 Cookie,也看不到 Cookie 的內容。而對於服務器返回的結果,因爲瀏覽器同源策略的限制,攻擊者也沒法進行解析。所以,攻擊者沒法從返回的結果中獲得任何東西,他所能作的就是給服務器發送請求,以執行請求中所描述的命令,在服務器端直接改變數據的值,而非竊取服務器中的數據。

但若 CSRF 攻擊的目標並不須要使用 Cookie,則也沒必要顧慮瀏覽器的 Cookie 策略了。

CSRF 攻擊的防範

當前,對 CSRF 攻擊的防範措施主要有以下幾種方式。

驗證碼

驗證碼被認爲是對抗 CSRF 攻擊最簡潔而有效的防護方法。

從上述示例中能夠看出,CSRF 攻擊每每是在用戶不知情的狀況下構造了網絡請求。而驗證碼會強制用戶必須與應用進行交互,才能完成最終請求。由於一般狀況下,驗證碼可以很好地遏制 CSRF 攻擊。

但驗證碼並非萬能的,由於出於用戶考慮,不能給網站全部的操做都加上驗證碼。所以,驗證碼只能做爲防護 CSRF 的一種輔助手段,而不能做爲最主要的解決方案。

Referer Check

根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。經過 Referer Check,能夠檢查請求是否來自合法的"源"。

好比,若是用戶要刪除本身的帖子,那麼先要登陸 www.c.com,而後找到對應的頁面,發起刪除帖子的請求。此時,Referer 的值是 http://www.c.com;當請求是從 www.a.com 發起時,Referer 的值是 http://www.a.com 了。所以,要防護 CSRF 攻擊,只須要對於每個刪帖請求驗證其 Referer 值,若是是以 www.c.com 開頭的域名,則說明該請求是來自網站本身的請求,是合法的。若是 Referer 是其餘網站的話,則有多是 CSRF 攻擊,能夠拒絕該請求。

針對上文的例子,能夠在服務端增長以下代碼:

if (req.headers.referer!=='http://www.c.com:8002/') {
    res.write('csrf 攻擊');
    return;
}
複製代碼

referer check

Referer Check 不只能防範 CSRF 攻擊,另外一個應用場景是 "防止圖片盜鏈"。

添加 token 驗證

CSRF 攻擊之因此可以成功,是由於攻擊者能夠徹底僞造用戶的請求,該請求中全部的用戶驗證信息都是存在於 Cookie 中,所以攻擊者能夠在不知道這些驗證信息的狀況下直接利用用戶本身的 Cookie 來經過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入攻擊者所不能僞造的信息,而且該信息不存在於 Cookie 之中。能夠在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端創建一個攔截器來驗證這個 token,若是請求中沒有 token 或者 token 內容不正確,則認爲多是 CSRF 攻擊而拒絕該請求。

總結

本文主要介紹了 XSS 和 CSRF 的攻擊原理和防護措施。固然,在 Web 安全領域,除了這兩種常見的攻擊方式,也存在這 SQL 注入等其它攻擊方式,這不在本文的討論範圍以內,若是你對其感興趣,能夠閱讀SQL注入技術專題的專欄詳細瞭解相關信息。最後,總結一下 XSS 攻擊和 CSRF 攻擊的常見防護措施:

  1. 防護 XSS 攻擊
  • HttpOnly 防止劫取 Cookie
  • 用戶的輸入檢查
  • 服務端的輸出檢查
  1. 防護 CSRF 攻擊
  • 驗證碼
  • Referer Check
  • Token 驗證

<完>

參考資料

相關文章
相關標籤/搜索