作者: TooooBug
鏈接:http://www.imooc.com/article/18069
來源:慕課網(wǎng)
XSS 全稱 Cross Site Scripting 歧杏,跨站腳本攻擊棚品,因為 CSS 這名字老早就被樣式表拿走了蚜迅,所以只好叫 XSS 了捎泻。
XSS 是什么鬼
XSS 比如我只想在頁面上顯示一個名字:
<span class="name">{{name}}</span>
但是侦鹏,如果我的名字是長這樣的:
星辰<script>alert('SB')</script>
這時候就好玩了:
<span class="name">星辰<script>alert('SB')</script></span>
你看诡曙,頁面中憑空多了一段腳本。
JS可以干嘛略水?用戶登錄用的JS吧价卤,讀取資料用的JS吧,點擊買東西渊涝、消費(fèi)用的JS吧慎璧,查用戶有多少錢用的JS吧,基于 Cookies 也可以讀寫吧跨释。
XSS 怎么防御
一個經(jīng)典的防御方法就是對內(nèi)容進(jìn)行轉(zhuǎn)義和過濾胸私,比如
var escapeHtml = function(str) {
if(!str) return '';
str = str.replace(/&/g, '&');
str = str.replace(/</g, '<');
str = str.replace(/>/g, '>');
str = str.replace(/"/g, '&quto;');
str = str.replace(/'/g, ''');
// str = str.replace(/ /g, ' ');
return str;
};
var name = escapeHtml(`<script>alert('SB')</script>`);
此時 name 會變成
<script>alert('SB')</script>
這樣就會原樣顯示出來,再也無法耍流氓啦鳖谈。
當(dāng)然岁疼,富文本還要更麻煩一些,因為要保留一部分標(biāo)簽和屬性缆娃,要不然全變純文本了捷绒,就不富了瑰排。這種情況一般通過黑名單進(jìn)行過濾,或者白名單放行疙驾。即只允許一部分指定的標(biāo)簽和屬性凶伙,其它的全部轉(zhuǎn)義掉郭毕。
CSP 大法
前面轉(zhuǎn)義的方法的出發(fā)點它碎,是讓用戶的輸入不要變成程序,輸入的什么就讓它輸出成什么显押。
事實上現(xiàn)代瀏覽器為我們帶來了一個全新的安全策略扳肛,叫作內(nèi)容安全策略,Content Security Policy乘碑,簡稱CSP挖息。CSP的思路跟轉(zhuǎn)義不一樣,它的著手點是兽肤,如果一段代碼變成了程序套腹,我們是否應(yīng)該運(yùn)行它∽收。或者更準(zhǔn)確一點說电禀,它實際上是定義頁面上哪一些內(nèi)容是可被信任的,哪一些內(nèi)容是不被信任的笤休。
因為我們自己的腳本是預(yù)先就知道并放在頁面上的尖飞,所以我們可以設(shè)置好信任關(guān)系,當(dāng)有 XSS 腳本出現(xiàn)時店雅,它并不在我們的信任列表中政基,因此可以阻止它運(yùn)行。
它的具體使用方式是在 HTTP 頭中輸出 CSP 策略:
Content-Security-Policy: <policy-directive>; <policy-directive>
從語法上可以看到闹啦,一個頭可以輸出多個策略沮明,每一個策略由一個指令和指令對應(yīng)的值組成。指令可以理解為指定內(nèi)容類型的窍奋,比如script-src
指令用于指定腳本荐健,img-src
用于指定圖片。值則主要是來源费变,比如某個指定的URL摧扇,或者self
表示同源,或者unsafe-inline
表示在頁面上直接出現(xiàn)的腳本等挚歧。
詳細(xì)的指令和值扛稽,可以查看MDN相關(guān)頁面。
具體到上面的 XSS 例子滑负,可以使用
Content-Security-Policy: script-src 'self';
這樣除了在同一個域名下的JS文件外在张,其它的腳本都不可以執(zhí)行了用含,自然之前 XSS 的內(nèi)容也就失效啦。簡單粗暴有沒有帮匾?
當(dāng)然啄骇,如果你說,我就是要在頁面中放點內(nèi)聯(lián)的腳本瘟斜,不可以么缸夹?當(dāng)然可以啦,CSP 設(shè)計的時候也考慮了這些情況螺句,還是相當(dāng)靈活的虽惭。你只需要指定一個 nonce 屬性,或者計算一下 hash 值蛇尚,即可芽唇。詳細(xì)的用法看 MDN 哦。
說實話取劫,用 CSP 來處理 XSS 攻擊還是不如轉(zhuǎn)義來得優(yōu)雅匆笤,因為轉(zhuǎn)義可以不影響用戶輸入輸出,不改變內(nèi)容的本質(zhì)谱邪。但是 CSP 提供了足夠簡單而又靈活的方式來防御 XSS 炮捧,可以很好地作為我們前端 XSS 防御的最后一道防線。
CSRF
CSRF 也是個望文生不到義的詞虾标,它的全稱是 Cross Site Request Foggy寓盗,即跨站請求攻擊。雖然也有跨站璧函,但我覺得這個跨站還是相當(dāng)可以理解的傀蚌,它真的是從別的網(wǎng)站發(fā)起一個請求到我們的網(wǎng)站的。
當(dāng)一個用戶登錄我們的網(wǎng)站后蘸吓,在 Cookies 中會存放用戶的身份憑證善炫。在大部分時候,就是一個 SessionId 库继。當(dāng)用戶下次訪問我們的網(wǎng)站的時候箩艺,我們用這個憑證識別出用戶是誰,有沒有登錄態(tài)宪萄。
如果第三方網(wǎng)站的代碼請求了我們的網(wǎng)站艺谆,會發(fā)生什么呢?比如
<img src="http://www.example.com/haha" />
雖然它是一張圖片拜英,但它確實向www.example.com
發(fā)了一個請求静汤,如此用戶有登錄態(tài)的話,其實就相當(dāng)于是用戶自己發(fā)了一個請求。如果這個地址是一個發(fā)表文章虫给、發(fā)布微博甚至轉(zhuǎn)賬之類的鏈接藤抡,那用戶就在不知情的情況下進(jìn)行了一些操作。這也是比較嚴(yán)重的安全問題抹估。
當(dāng)然你可能會說缠黍,現(xiàn)在誰還這么弱智,把這么敏感的操作用 GET 耙摺瓷式?沒錯,你可以選擇用 POST 谷暮,但是這絲毫不能阻止 CSRF 攻擊的發(fā)生啊蒿往。
<iframe name="test"></iframe>
<form target="test" method="post" action="http://www.example.com/haha">
...
</form>
當(dāng)這個表單提交的時候,我們就發(fā)了一個 POST 請求湿弦。華麗麗的 CSRF 。
CSRF 的常規(guī)防御
CSRF 比較常規(guī)的防御方式是通過判斷來源和加 token腾夯。
判斷來源比較簡單颊埃,主要是判斷referer
這個頭,如果不是自己的網(wǎng)站蝶俱,就返回錯誤班利。
加 token 即同樣的隨機(jī) token,在 cookies 中放一份榨呆,在表單中再放一份罗标。這樣第三方網(wǎng)站就無法獲取到這個 token 是什么。
但是這樣做也有一個比較明顯的問題积蜻,就是無法保證站內(nèi)用戶的體驗闯割。雖然你防了站外的攻擊,但是也降低了站內(nèi)用戶的體驗竿拆。具體表現(xiàn)在如果同時打開多個表單宙拉,只有最后一個表單能成功提交。
same-site 的 Cookie
回想 CSRF 之所以能夠攻擊成功丙笋,核心原因就在于用戶的身份是放在 Cookies 中的谢澈,而不管你通過什么方式訪問網(wǎng)站,都會帶上這個網(wǎng)站的 Cookies 御板,從第三方來的訪問自然也不能例外锥忿。
但是,Chrome 在這個問題上給了我們不同的答案怠肋,可以放第三方訪問時不帶 Cookies 敬鬓。也就是說 Cookies 只有本站能用,來自第三方的訪問都不能使用。
具體的使用方式列林,是在打 Cookie 的時候瑞你,加上一個屬性:SameSite
,它的值有兩:
-
strict
任何來自第三方的請求都不能使用 Cookies 希痴,包括通過鏈接點進(jìn)來的 -
lex
只有比較敏感的操作不帶 Cookies 者甲,比如表單提交
針對 CSRF ,我們可以將 Cookies 設(shè)置成SameSite: strict
的砌创,這樣就可以有效防御 CSRF 了虏缸。不過比較可惜的是,目前只有 Chrome 才支持這一屬性嫩实。希望未來所有瀏覽器都能跟上腳步刽辙。
使用 SameSite 還會面臨一個問題,如果用戶是點擊鏈接進(jìn)來的甲献,那么是不能使用登錄態(tài)的宰缤。一般可以考慮將用戶不敏感的信息不設(shè)置這個屬性,點進(jìn)來仍然可以顯示當(dāng)前用戶是誰晃洒,但是在請求的時候要求一個比較敏感的 SameSite 的 Cookies慨灭。這里需要更多的實踐經(jīng)驗來探索。
小結(jié)
本文重點講了 XSS 和 CSRF 這兩種比較常見的前端安全問題的防御思路球及,尤其是如何使用一些新的規(guī)范氧骤、實現(xiàn)來幫助我們進(jìn)行防御。希望后面瀏覽器對這些安全相關(guān)防御辦法的普及率能再高一些吃引,讓前端工程師能花更少的時間寫出更安全的代碼筹陵。