在 Web 安全領(lǐng)域中,XSS 和 CSRF 是最常見的攻擊方式。本文將會簡單介紹 XSS 和 CSRF 的攻防問題。
1. xss
XSS,即 Cross Site Script,中譯是跨站腳本攻擊;其原本縮寫是 CSS,但為了和層疊樣式表(Cascading Style Sheet)有所區(qū)分,因而在安全領(lǐng)域叫做 XSS。
XSS 攻擊是指攻擊者在網(wǎng)站上注入惡意的客戶端代碼,通過惡意腳本對客戶端網(wǎng)頁進(jìn)行篡改,從而在用戶瀏覽網(wǎng)頁時,對用戶瀏覽器進(jìn)行控制或者獲取用戶隱私數(shù)據(jù)的一種攻擊方式。
攻擊者對客戶端網(wǎng)頁注入的惡意腳本一般包括 JavaScript,有時也會包含 HTML 和 Flash。有很多種方式進(jìn)行 XSS 攻擊,但它們的共同點(diǎn)為:將一些隱私數(shù)據(jù)像 cookie、session 發(fā)送給攻擊者,將受害者重定向到一個由攻擊者控制的網(wǎng)站,在受害者的機(jī)器上進(jìn)行一些惡意操作。
XSS攻擊可以分為3類:反射型(非持久型)、存儲型(持久型)、基于DOM。
1.1反射性
反射型 XSS 只是簡單地把用戶輸入的數(shù)據(jù) “反射” 給瀏覽器,這種攻擊方式往往需要攻擊者誘使用戶點(diǎn)擊一個惡意鏈接,或者提交一個表單,或者進(jìn)入一個惡意網(wǎng)站時,注入腳本進(jìn)入被攻擊者的網(wǎng)站。
看一個示例。我先準(zhǔn)備一個如下的靜態(tài)頁:
惡意鏈接的地址指向了 localhost:8001/?q=111p=222。然后,我再啟一個簡單的 Node 服務(wù)處理惡意鏈接的請求:
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);
當(dāng)用戶點(diǎn)擊惡意鏈接時,頁面跳轉(zhuǎn)到攻擊者預(yù)先準(zhǔn)備的頁面,會發(fā)現(xiàn)在攻擊者的頁面執(zhí)行了 js 腳本:
這樣就產(chǎn)生了反射型 XSS 攻擊。攻擊者可以注入任意的惡意腳本進(jìn)行攻擊,可能注入惡作劇腳本,或者注入能獲取用戶隱私數(shù)據(jù)(如cookie)的腳本,這取決于攻擊者的目的。
1.2存儲型
存儲型 XSS 會把用戶輸入的數(shù)據(jù) “存儲” 在服務(wù)器端,當(dāng)瀏覽器請求數(shù)據(jù)時,腳本從服務(wù)器上傳回并執(zhí)行。這種 XSS 攻擊具有很強(qiáng)的穩(wěn)定性。
比較常見的一個場景是攻擊者在社區(qū)或論壇上寫下一篇包含惡意 JavaScript 代碼的文章或評論,文章或評論發(fā)表后,所有訪問該文章或評論的用戶,都會在他們的瀏覽器中執(zhí)行這段惡意的 JavaScript 代碼。
舉一個示例。
先準(zhǔn)備一個輸入頁面:
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 服務(wù)監(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); //將輸入的腳本內(nèi)容返回給前端頁面
res.end();
}
}
const server = new http.Server();
server.listen(8001, '127.0.0.1');
server.on('request', handleReequest);
當(dāng)用戶點(diǎn)擊提交按鈕將輸入信息提交到服務(wù)端時,服務(wù)端通過 userInput 變量保存了輸入內(nèi)容。當(dāng)用戶通過 http://localhost:8001/${id} 訪問時,服務(wù)端會返回與 id 對應(yīng)的內(nèi)容(本示例簡化了處理)。如果用戶輸入了惡意腳本內(nèi)容,則其他用戶訪問該內(nèi)容時,惡意腳本就會在瀏覽器端執(zhí)行:
1.3 基于DOM
基于 DOM 的 XSS 攻擊是指通過惡意腳本修改頁面的 DOM 結(jié)構(gòu),是純粹發(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>
點(diǎn)擊 Submit 按鈕后,會在當(dāng)前頁面插入一個鏈接,其地址為用戶的輸入內(nèi)容。如果用戶在輸入時構(gòu)造了如下內(nèi)容:
用戶提交之后,頁面代碼就變成了:
a href onlick="alert(/xss/)">testLink/a>
此時,用戶點(diǎn)擊生成的鏈接,就會執(zhí)行對應(yīng)的腳本:
2.XSS攻擊的防范
現(xiàn)在主流的瀏覽器內(nèi)置了防范 XSS 的措施,例如 CSP。但對于開發(fā)者來說,也應(yīng)該尋找可靠的解決方案來防止 XSS 攻擊。
2.1 HttpOnly 防止劫取 Cookie
HttpOnly 最早由微軟提出,至今已經(jīng)成為一個標(biāo)準(zhǔn)。瀏覽器將禁止頁面的Javascript 訪問帶有 HttpOnly 屬性的Cookie。
上文有說到,攻擊者可以通過注入惡意腳本獲取用戶的 Cookie 信息。通常 Cookie 中都包含了用戶的登錄憑證信息,攻擊者在獲取到 Cookie 之后,則可以發(fā)起 Cookie 劫持攻擊。所以,嚴(yán)格來說,HttpOnly 并非阻止 XSS 攻擊,而是能阻止 XSS 攻擊后的 Cookie 劫持攻擊。
2.2 輸入檢查
不要相信用戶的任何輸入。 對于用戶的任何輸入要進(jìn)行檢查、過濾和轉(zhuǎn)義。建立可信任的字符和 HTML 標(biāo)簽白名單,對于不在白名單之列的字符或者標(biāo)簽進(jìn)行過濾或編碼。
在 XSS 防御中,輸入檢查一般是檢查用戶輸入的數(shù)據(jù)中是否包含 ,> 等特殊字符,如果存在,則對特殊字符進(jìn)行過濾或編碼,這種方式也稱為 XSS Filter。
而在一些前端框架中,都會有一份 decodingMap, 用于對用戶輸入所包含的特殊字符或標(biāo)簽進(jìn)行編碼或過濾,如 ,>,script,防止 XSS 攻擊:
// vuejs 中的 decodingMap
// 在 vuejs 中,如果輸入帶 script 標(biāo)簽的內(nèi)容,會直接過濾掉
const decodingMap = {
'': '',
'>': '>',
'"': '"',
'': '',
'
': '\n'
}
2.3輸出檢查
用戶的輸入會存在問題,服務(wù)端的輸出也會存在問題。一般來說,除富文本的輸出外,在變量輸出到 HTML 頁面時,可以使用編碼或轉(zhuǎn)義的方式來防御 XSS 攻擊。例如利用 sanitize-html 對輸出內(nèi)容進(jìn)行有規(guī)則的過濾之后再輸出到頁面中。
3.CSRF
CSRF,即 Cross Site Request Forgery,中譯是跨站請求偽造,是一種劫持受信任用戶向服務(wù)器發(fā)送非預(yù)期請求的攻擊方式。
通常情況下,CSRF 攻擊是攻擊者借助受害者的 Cookie 騙取服務(wù)器的信任,可以在受害者毫不知情的情況下以受害者名義偽造請求發(fā)送給受攻擊服務(wù)器,從而在并未授權(quán)的情況下執(zhí)行在權(quán)限保護(hù)之下的操作。
在舉例子之前,先說說瀏覽器的 Cookie 策略
3.1 瀏覽器的 Cookie 策略
Cookie 是服務(wù)器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù),它會在瀏覽器下次向同一服務(wù)器再發(fā)起請求時被攜帶并發(fā)送到服務(wù)器上。Cookie 主要用于以下三個方面:
- 會話狀態(tài)管理(如用戶登錄狀態(tài)、購物車、游戲分?jǐn)?shù)或其它需要記錄的信息)
- 個性化設(shè)置(如用戶自定義設(shè)置、主題等)
- 個性化設(shè)置(如用戶自定義設(shè)置、主題等)
而瀏覽器所持有的 Cookie 分為兩種:
- Session Cookie(會話期 Cookie):會話期 Cookie 是最簡單的Cookie,它不需要指定過期時間(Expires)或者有效期(Max-Age),它僅在會話期內(nèi)有效,瀏覽器關(guān)閉之后它會被自動刪除。
- 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:mycookie 和 test,前者屬于會話期 Cookie,后者則屬于持久性 Cookie。當(dāng)我們?nèi)ゲ榭?Cookie 相關(guān)的屬性時,不同的瀏覽器對會話期 Cookie 的 Expires 屬性值會不一樣:
此外,每個 Cookie 都會有與之關(guān)聯(lián)的域,這個域的范圍一般通過 donmain 屬性指定。如果 Cookie 的域和頁面的域相同,那么我們稱這個 Cookie 為第一方 Cookie(first-party cookie),如果 Cookie 的域和頁面的域不同,則稱之為第三方 Cookie(third-party cookie)。一個頁面包含圖片或存放在其他域上的資源(如圖片)時,第一方的 Cookie 也只會發(fā)送給設(shè)置它們的服務(wù)器。
3.2 通過 Cookie 進(jìn)行 CSRF 攻擊
假設(shè)有一個 bbs 站點(diǎn):http://www.c.com,當(dāng)?shù)卿浐蟮挠脩舭l(fā)起如下 GET 請求時,會刪除 ID 指定的帖子:
http://www.c.com:8002/content/delete/:id
如發(fā)起 http://www.c.com:8002/content/delete/87343 請求時,會刪除 id 為 87343 的帖子。當(dāng)用戶登錄之后,會設(shè)置如下 cookie:
res.setHeader('Set-Cookie', ['user=22333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']);
user 對應(yīng)的值是用戶 ID。然后構(gòu)造一個頁面 A:
CSRF 攻擊者準(zhǔn)備的網(wǎng)站:
p>CSRF 攻擊者準(zhǔn)備的網(wǎng)站:/p>
img src="http://www.c.com:8002/content/delete/87343">
頁面 A 使用了一個 img 標(biāo)簽,其地址指向了刪除用戶帖子的鏈接:
可以看到,當(dāng)?shù)卿浻脩粼L問攻擊者的網(wǎng)站時,會向 www.c.com 發(fā)起一個刪除用戶帖子的請求。此時若用戶在切換到 www.c.com 的帖子頁面刷新,會發(fā)現(xiàn)ID 為 87343 的帖子已經(jīng)被刪除。
由于 Cookie 中包含了用戶的認(rèn)證信息,當(dāng)用戶訪問攻擊者準(zhǔn)備的攻擊環(huán)境時,攻擊者就可以對服務(wù)器發(fā)起 CSRF 攻擊。在這個攻擊過程中,攻擊者借助受害者的 Cookie 騙取服務(wù)器的信任,但并不能拿到 Cookie,也看不到 Cookie 的內(nèi)容。而對于服務(wù)器返回的結(jié)果,由于瀏覽器同源策略的限制,攻擊者也無法進(jìn)行解析。因此,攻擊者無法從返回的結(jié)果中得到任何東西,他所能做的就是給服務(wù)器發(fā)送請求,以執(zhí)行請求中所描述的命令,在服務(wù)器端直接改變數(shù)據(jù)的值,而非竊取服務(wù)器中的數(shù)據(jù)。
但若 CSRF 攻擊的目標(biāo)并不需要使用 Cookie,則也不必顧慮瀏覽器的 Cookie 策略了。
4.CSRF 攻擊的防范
當(dāng)前,對 CSRF 攻擊的防范措施主要有如下幾種方式。
4.1 驗(yàn)證碼
驗(yàn)證碼被認(rèn)為是對抗 CSRF 攻擊最簡潔而有效的防御方法。
從上述示例中可以看出,CSRF 攻擊往往是在用戶不知情的情況下構(gòu)造了網(wǎng)絡(luò)請求。而驗(yàn)證碼會強(qiáng)制用戶必須與應(yīng)用進(jìn)行交互,才能完成最終請求。因?yàn)橥ǔG闆r下,驗(yàn)證碼能夠很好地遏制 CSRF 攻擊。
但驗(yàn)證碼并不是萬能的,因?yàn)槌鲇谟脩艨紤],不能給網(wǎng)站所有的操作都加上驗(yàn)證碼。因此,驗(yàn)證碼只能作為防御 CSRF 的一種輔助手段,而不能作為最主要的解決方案。
4.2 Referer Check
根據(jù) HTTP 協(xié)議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。通過 Referer Check,可以檢查請求是否來自合法的”源”。
比如,如果用戶要刪除自己的帖子,那么先要登錄 www.c.com,然后找到對應(yīng)的頁面,發(fā)起刪除帖子的請求。此時,Referer 的值是 http://www.c.com;當(dāng)請求是從 www.a.com 發(fā)起時,Referer 的值是 http://www.a.com 了。因此,要防御 CSRF 攻擊,只需要對于每一個刪帖請求驗(yàn)證其 Referer 值,如果是以 www.c.com 開頭的域名,則說明該請求是來自網(wǎng)站自己的請求,是合法的。如果 Referer 是其他網(wǎng)站的話,則有可能是 CSRF 攻擊,可以拒絕該請求。
針對上文的例子,可以在服務(wù)端增加如下代碼:
if (req.headers.referer !== 'http://www.c.com:8002/') {
res.write('csrf 攻擊');
return;
}
Referer Check 不僅能防范 CSRF 攻擊,另一個應(yīng)用場景是 “防止圖片盜鏈”。
4.3 添加 token 驗(yàn)證(token==令牌)
CSRF 攻擊之所以能夠成功,是因?yàn)楣粽呖梢酝耆珎卧煊脩舻恼埱螅撜埱笾兴械挠脩趄?yàn)證信息都是存在于 Cookie 中,因此攻擊者可以在不知道這些驗(yàn)證信息的情況下直接利用用戶自己的 Cookie 來通過安全驗(yàn)證。要抵御 CSRF,關(guān)鍵在于在請求中放入攻擊者所不能偽造的信息,并且該信息不存在于 Cookie 之中??梢栽?HTTP 請求中以參數(shù)的形式加入一個隨機(jī)產(chǎn)生的 token,并在服務(wù)器端建立一個攔截器來驗(yàn)證這個 token,如果請求中沒有 token 或者 token 內(nèi)容不正確,則認(rèn)為可能是 CSRF 攻擊而拒絕該請求。
總結(jié)
本文主要介紹了 XSS 和 CSRF 的攻擊原理和防御措施。當(dāng)然,在 Web 安全領(lǐng)域,除了這兩種常見的攻擊方式,也存在這 SQL 注入等其它攻擊方式,這不在本文的討論范圍之內(nèi),如果你對其感興趣,可以閱讀SQL注入技術(shù)專題的專欄詳細(xì)了解相關(guān)信息。最后,總結(jié)一下 XSS 攻擊和 CSRF 攻擊的常見防御措施:
1.防御XSS攻擊
- HttpOnly 防止劫取 Cookie
- 用戶的輸入檢查
- 服務(wù)端的輸出檢查
2.防御CSRF攻擊
- 驗(yàn)證碼
- Referer Check
- Token 驗(yàn)證
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
您可能感興趣的文章:- Yii框架防止sql注入,xss攻擊與csrf攻擊的方法
- PHP開發(fā)中常見的安全問題詳解和解決方法(如Sql注入、CSRF、Xss、CC等)