淺說 XSS 和 CSRF

在 Web 安全領域中诲侮,XSS 和 CSRF 是最常見的攻擊方式褂萧。本文將會簡單介紹 XSS 和 CSRF 的攻防問題盲憎。

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

XSS

XSS兔乞,即 Cross Site Script艾杏,中譯是跨站腳本攻擊;其原本縮寫是 CSS极颓,但為了和層疊樣式表(Cascading Style Sheet)有所區(qū)分朱盐,因而在安全領域叫做 XSS。

XSS 攻擊是指攻擊者在網(wǎng)站上注入惡意的客戶端代碼菠隆,通過惡意腳本對客戶端網(wǎng)頁進行篡改兵琳,從而在用戶瀏覽網(wǎng)頁時狂秘,對用戶瀏覽器進行控制或者獲取用戶隱私數(shù)據(jù)的一種攻擊方式。

攻擊者對客戶端網(wǎng)頁注入的惡意腳本一般包括 JavaScript躯肌,有時也會包含 HTML 和 Flash者春。有很多種方式進行 XSS 攻擊,但它們的共同點為:將一些隱私數(shù)據(jù)像 cookie清女、session 發(fā)送給攻擊者钱烟,將受害者重定向到一個由攻擊者控制的網(wǎng)站,在受害者的機器上進行一些惡意操作嫡丙。

XSS攻擊可以分為3類:反射型(非持久型)拴袭、存儲型(持久型)、基于DOM曙博。

反射型

反射型 XSS 只是簡單地把用戶輸入的數(shù)據(jù) “反射” 給瀏覽器拥刻,這種攻擊方式往往需要攻擊者誘使用戶點擊一個惡意鏈接,或者提交一個表單父泳,或者進入一個惡意網(wǎng)站時般哼,注入腳本進入被攻擊者的網(wǎng)站。

看一個示例惠窄。我先準備一個如下的靜態(tài)頁:

反射型xss1

惡意鏈接的地址指向了 localhost:8001/?q=111&p=222蒸眠。然后,我再啟一個簡單的 Node 服務處理惡意鏈接的請求:

const http = require('http');
function handleReequest(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();
}

const server = new http.Server();
server.listen(8001, '127.0.0.1');
server.on('request', handleReequest);

當用戶點擊惡意鏈接時睬捶,頁面跳轉到攻擊者預先準備的頁面黔宛,會發(fā)現(xiàn)在攻擊者的頁面執(zhí)行了 js 腳本:

執(zhí)行腳本

這樣就產(chǎn)生了反射型 XSS 攻擊。攻擊者可以注入任意的惡意腳本進行攻擊擒贸,可能注入惡作劇腳本臀晃,或者注入能獲取用戶隱私數(shù)據(jù)(如cookie)的腳本,這取決于攻擊者的目的介劫。

存儲型

存儲型 XSS 會把用戶輸入的數(shù)據(jù) "存儲" 在服務器端徽惋,當瀏覽器請求數(shù)據(jù)時,腳本從服務器上傳回并執(zhí)行座韵。這種 XSS 攻擊具有很強的穩(wěn)定性险绘。

比較常見的一個場景是攻擊者在社區(qū)或論壇上寫下一篇包含惡意 JavaScript 代碼的文章或評論,文章或評論發(fā)表后誉碴,所有訪問該文章或評論的用戶宦棺,都會在他們的瀏覽器中執(zhí)行這段惡意的 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 服務監(jiān)聽 save 請求代咸。為了簡化,用一個變量來保存用戶的輸入:

const http = require('http');

let userInput = '';

function handleReequest(req, res) {
    const method = 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();
    }
}

const server = new http.Server();
server.listen(8001, '127.0.0.1');

server.on('request', handleReequest);

當用戶點擊提交按鈕將輸入信息提交到服務端時成黄,服務端通過 userInput 變量保存了輸入內(nèi)容呐芥。當用戶通過 http://localhost:8001/${id} 訪問時逻杖,服務端會返回與 id 對應的內(nèi)容(本示例簡化了處理)。如果用戶輸入了惡意腳本內(nèi)容思瘟,則其他用戶訪問該內(nèi)容時荸百,惡意腳本就會在瀏覽器端執(zhí)行:

存儲型xss

基于DOM

基于 DOM 的 XSS 攻擊是指通過惡意腳本修改頁面的 DOM 結構,是純粹發(fā)生在客戶端的攻擊滨攻。

看如下代碼:

<h2>XSS: </h2>
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
    const input = document.getElementById('input');
    const btn = document.getElementById('btn');
    const div = document.getElementById('div');

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

    btn.addEventListener('click', () => {
        div.innerHTML = `<a href=${val}>testLink</a>`
    }, false);
</script>

點擊 Submit 按鈕后够话,會在當前頁面插入一個鏈接,其地址為用戶的輸入內(nèi)容光绕。如果用戶在輸入時構造了如下內(nèi)容:

'' onclick=alert(/xss/)

用戶提交之后更鲁,頁面代碼就變成了:

<a href onlick="alert(/xss/)">testLink</a>

此時,用戶點擊生成的鏈接奇钞,就會執(zhí)行對應的腳本:

dom-xss

XSS 攻擊的防范

現(xiàn)在主流的瀏覽器內(nèi)置了防范 XSS 的措施,例如 CSP漂坏。但對于開發(fā)者來說景埃,也應該尋找可靠的解決方案來防止 XSS 攻擊。

HttpOnly 防止劫取 Cookie

HttpOnly 最早由微軟提出顶别,至今已經(jīng)成為一個標準谷徙。瀏覽器將禁止頁面的Javascript 訪問帶有 HttpOnly 屬性的Cookie。

上文有說到驯绎,攻擊者可以通過注入惡意腳本獲取用戶的 Cookie 信息完慧。通常 Cookie 中都包含了用戶的登錄憑證信息,攻擊者在獲取到 Cookie 之后剩失,則可以發(fā)起 Cookie 劫持攻擊屈尼。所以,嚴格來說拴孤,HttpOnly 并非阻止 XSS 攻擊脾歧,而是能阻止 XSS 攻擊后的 Cookie 劫持攻擊。

輸入檢查

不要相信用戶的任何輸入演熟。 對于用戶的任何輸入要進行檢查鞭执、過濾和轉義。建立可信任的字符和 HTML 標簽白名單芒粹,對于不在白名單之列的字符或者標簽進行過濾或編碼兄纺。

在 XSS 防御中,輸入檢查一般是檢查用戶輸入的數(shù)據(jù)中是否包含 <化漆,> 等特殊字符估脆,如果存在,則對特殊字符進行過濾或編碼获三,這種方式也稱為 XSS Filter旁蔼。

而在一些前端框架中锨苏,都會有一份 decodingMap, 用于對用戶輸入所包含的特殊字符或標簽進行編碼或過濾棺聊,如 <伞租,>script限佩,防止 XSS 攻擊:

// vuejs 中的 decodingMap
// 在 vuejs 中葵诈,如果輸入帶 script 標簽的內(nèi)容,會直接過濾掉
const decodingMap = {
  '&lt;': '<',
  '&gt;': '>',
  '&quot;': '"',
  '&amp;': '&',
  '&#10;': '\n'
}

輸出檢查

用戶的輸入會存在問題祟同,服務端的輸出也會存在問題作喘。一般來說,除富文本的輸出外晕城,在變量輸出到 HTML 頁面時泞坦,可以使用編碼或轉義的方式來防御 XSS 攻擊。例如利用 sanitize-html 對輸出內(nèi)容進行有規(guī)則的過濾之后再輸出到頁面中砖顷。

CSRF

CSRF贰锁,即 Cross Site Request Forgery,中譯是跨站請求偽造滤蝠,是一種劫持受信任用戶向服務器發(fā)送非預期請求的攻擊方式豌熄。

通常情況下,CSRF 攻擊是攻擊者借助受害者的 Cookie 騙取服務器的信任物咳,可以在受害者毫不知情的情況下以受害者名義偽造請求發(fā)送給受攻擊服務器锣险,從而在并未授權的情況下執(zhí)行在權限保護之下的操作。

在舉例子之前览闰,先說說瀏覽器的 Cookie 策略芯肤。

瀏覽器的 Cookie 策略

Cookie 是服務器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù),它會在瀏覽器下次向同一服務器再發(fā)起請求時被攜帶并發(fā)送到服務器上压鉴。Cookie 主要用于以下三個方面:

  • 會話狀態(tài)管理(如用戶登錄狀態(tài)纷妆、購物車、游戲分數(shù)或其它需要記錄的信息)
  • 個性化設置(如用戶自定義設置晴弃、主題等)
  • 個性化設置(如用戶自定義設置掩幢、主題等)

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

  • Session Cookie(會話期 Cookie):會話期 Cookie 是最簡單的Cookie,它不需要指定過期時間(Expires)或者有效期(Max-Age)上鞠,它僅在會話期內(nèi)有效际邻,瀏覽器關閉之后它會被自動刪除。
  • 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;']);

上述代碼創(chuàng)建了兩個 Cookie:mycookietest,前者屬于會話期 Cookie谴咸,后者則屬于持久性 Cookie轮听。當我們?nèi)ゲ榭?Cookie 相關的屬性時骗露,不同的瀏覽器對會話期 Cookie 的 Expires 屬性值會不一樣:

Firefox:

firefox cookie

Chrome:

chrome cookie

此外,每個 Cookie 都會有與之關聯(lián)的域血巍,這個域的范圍一般通過 donmain 屬性指定萧锉。如果 Cookie 的域和頁面的域相同,那么我們稱這個 Cookie 為第一方 Cookie(first-party cookie)述寡,如果 Cookie 的域和頁面的域不同柿隙,則稱之為第三方 Cookie(third-party cookie)。一個頁面包含圖片或存放在其他域上的資源(如圖片)時鲫凶,第一方的 Cookie 也只會發(fā)送給設置它們的服務器禀崖。

通過 Cookie 進行 CSRF 攻擊

假設有一個 bbs 站點:http://www.c.com,當?shù)卿浐蟮挠脩舭l(fā)起如下 GET 請求時螟炫,會刪除 ID 指定的帖子:

http://www.c.com:8002/content/delete/:id

如發(fā)起 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 攻擊者準備的網(wǎng)站:</p>
<img src="http://www.c.com:8002/content/delete/87343">

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

A

可以看到换吧,當?shù)卿浻脩粼L問攻擊者的網(wǎng)站時,會向 www.c.com 發(fā)起一個刪除用戶帖子的請求钥星。此時若用戶在切換到 www.c.com 的帖子頁面刷新沾瓦,會發(fā)現(xiàn)ID 為 87343 的帖子已經(jīng)被刪除。

由于 Cookie 中包含了用戶的認證信息谦炒,當用戶訪問攻擊者準備的攻擊環(huán)境時贯莺,攻擊者就可以對服務器發(fā)起 CSRF 攻擊。在這個攻擊過程中宁改,攻擊者借助受害者的 Cookie 騙取服務器的信任缕探,但并不能拿到 Cookie,也看不到 Cookie 的內(nèi)容还蹲。而對于服務器返回的結果爹耗,由于瀏覽器同源策略的限制,攻擊者也無法進行解析谜喊。因此潭兽,攻擊者無法從返回的結果中得到任何東西,他所能做的就是給服務器發(fā)送請求斗遏,以執(zhí)行請求中所描述的命令山卦,在服務器端直接改變數(shù)據(jù)的值,而非竊取服務器中的數(shù)據(jù)诵次。

但若 CSRF 攻擊的目標并不需要使用 Cookie账蓉,則也不必顧慮瀏覽器的 Cookie 策略了枚碗。

CSRF 攻擊的防范

當前,對 CSRF 攻擊的防范措施主要有如下幾種方式铸本。

驗證碼

驗證碼被認為是對抗 CSRF 攻擊最簡潔而有效的防御方法肮雨。

從上述示例中可以看出,CSRF 攻擊往往是在用戶不知情的情況下構造了網(wǎng)絡請求归敬。而驗證碼會強制用戶必須與應用進行交互酷含,才能完成最終請求。因為通常情況下汪茧,驗證碼能夠很好地遏制 CSRF 攻擊椅亚。

但驗證碼并不是萬能的,因為出于用戶考慮舱污,不能給網(wǎng)站所有的操作都加上驗證碼呀舔。因此,驗證碼只能作為防御 CSRF 的一種輔助手段扩灯,而不能作為最主要的解決方案媚赖。

Referer Check

根據(jù) HTTP 協(xié)議,在 HTTP 頭中有一個字段叫 Referer珠插,它記錄了該 HTTP 請求的來源地址惧磺。通過 Referer Check,可以檢查請求是否來自合法的"源"捻撑。

比如磨隘,如果用戶要刪除自己的帖子,那么先要登錄 www.c.com顾患,然后找到對應的頁面番捂,發(fā)起刪除帖子的請求叼风。此時驶社,Referer 的值是 http://www.c.com挫酿;當請求是從 www.a.com 發(fā)起時晨继,Referer 的值是 http://www.a.com 了豆胸。因此闽铐,要防御 CSRF 攻擊庄敛,只需要對于每一個刪帖請求驗證其 Referer 值梅誓,如果是以 www.c.com 開頭的域名桨螺,則說明該請求是來自網(wǎng)站自己的請求耕魄,是合法的。如果 Referer 是其他網(wǎng)站的話彭谁,則有可能是 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 請求中以參數(shù)的形式加入一個隨機產(chǎn)生的 token粥脚,并在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內(nèi)容不正確包个,則認為可能是 CSRF 攻擊而拒絕該請求刷允。

總結

本文主要介紹了 XSS 和 CSRF 的攻擊原理和防御措施。當然碧囊,在 Web 安全領域树灶,除了這兩種常見的攻擊方式,也存在這 SQL 注入等其它攻擊方式糯而,這不在本文的討論范圍之內(nèi)天通,如果你對其感興趣,可以閱讀SQL注入技術專題的專欄詳細了解相關信息熄驼。最后像寒,總結一下 XSS 攻擊和 CSRF 攻擊的常見防御措施:

  1. 防御 XSS 攻擊

    • HttpOnly 防止劫取 Cookie
    • 用戶的輸入檢查
    • 服務端的輸出檢查
  2. 防御 CSRF 攻擊

    • 驗證碼
    • Referer Check
    • Token 驗證

<完>

參考資料

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谜洽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吴叶,老刑警劉巖阐虚,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚌卤,居然都是意外死亡实束,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門逊彭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咸灿,“玉大人,你說我怎么就攤上這事侮叮”苁福” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長审胸。 經(jīng)常有香客問我亥宿,道長,這世上最難降的妖魔是什么砂沛? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任烫扼,我火速辦了婚禮,結果婚禮上碍庵,老公的妹妹穿的比我還像新娘映企。我一直安慰自己,他們只是感情好静浴,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布堰氓。 她就那樣靜靜地躺著,像睡著了一般马绝。 火紅的嫁衣襯著肌膚如雪豆赏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天富稻,我揣著相機與錄音掷邦,去河邊找鬼。 笑死椭赋,一個胖子當著我的面吹牛抚岗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哪怔,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼宣蔚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了认境?” 一聲冷哼從身側響起胚委,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叉信,沒想到半個月后亩冬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡硼身,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年硅急,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佳遂。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡营袜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丑罪,到底是詐尸還是另有隱情荚板,我是刑警寧澤凤壁,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站啸驯,受9級特大地震影響客扎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罚斗,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一徙鱼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧针姿,春花似錦袱吆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至榕暇,卻和暖如春蓬衡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彤枢。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工狰晚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缴啡。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓壁晒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親业栅。 傳聞我的和親對象是個殘疾皇子秒咐,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359