2020-5-20 seo達(dá)人
XSS
跨站腳本攻擊(Cross Site Script),本來(lái)縮寫(xiě)是 CSS, 但是為了和層疊樣式表(Cascading Style Sheet, CSS)有所區(qū)分,所以安全領(lǐng)域叫做 “XSS”;
XSS攻擊,通常是指攻擊者通過(guò) “HTML注入”篡改了網(wǎng)頁(yè),插入了惡意的腳本,從而在用戶(hù)瀏覽網(wǎng)頁(yè)時(shí),對(duì)用戶(hù)的瀏覽器進(jìn)行控制或者獲取用戶(hù)的敏感信息(Cookie, SessionID等)的一種攻擊方式。
頁(yè)面被注入了惡意JavaScript腳本,瀏覽器無(wú)法判斷區(qū)分這些腳本是被惡意注入的,還是正常的頁(yè)面內(nèi)容,所以惡意注入Javascript腳本也擁有了所有的腳本權(quán)限。如果頁(yè)面被注入了惡意 JavaScript腳本,它可以做哪些事情呢?
可以竊取 cookie信息。惡意 JavaScript可以通過(guò) ”doccument.cookie“獲取cookie信息,然后通過(guò) XMLHttpRequest或者Fetch加上CORS功能將數(shù)據(jù)發(fā)送給惡意服務(wù)器;惡意服務(wù)器拿到用戶(hù)的cookie信息之后,就可以在其他電腦上模擬用戶(hù)的登陸,然后進(jìn)行轉(zhuǎn)賬操作。
可以監(jiān)聽(tīng)用戶(hù)行為。惡意JavaScript可以使用 "addEventListener"接口來(lái)監(jiān)聽(tīng)鍵盤(pán)事件,比如可以獲取用戶(hù)輸入的銀行卡等信息,又可以做很多違法的事情。
可以修改DOM 偽造假的登陸窗口,用來(lái)欺騙用戶(hù)輸入用戶(hù)名和密碼等信息。
還可以在頁(yè)面內(nèi)生成浮窗廣告,這些廣告會(huì)嚴(yán)重影響用戶(hù)體驗(yàn)。
XSS攻擊可以分為三類(lèi):反射型,存儲(chǔ)型,基于DOM型(DOM based XSS)
反射型
惡意腳本作為網(wǎng)絡(luò)請(qǐng)求的一部分。
const Koa = require("koa");
const app = new Koa();
app.use(async ctx => {
// ctx.body 即服務(wù)端響應(yīng)的數(shù)據(jù)
ctx.body = '<script>alert("反射型 XSS 攻擊")</script>';
})
app.listen(3000, () => {
console.log('啟動(dòng)成功');
});
訪問(wèn) http://127.0.0.1:3000/ 可以看到 alert執(zhí)行
反射型XSS1
舉一個(gè)常見(jiàn)的場(chǎng)景,我們通過(guò)頁(yè)面的url的一個(gè)參數(shù)來(lái)控制頁(yè)面的展示內(nèi)容,比如我們把上面的一部分代碼改成下面這樣
app.use(async ctx => {
// ctx.body 即服務(wù)端響應(yīng)的數(shù)據(jù)
ctx.body = ctx.query.userName;
})
此時(shí)訪問(wèn) http://127.0.0.1:3000?userName=xiaoming 可以看到頁(yè)面上展示了xiaoming,此時(shí)我們?cè)L問(wèn) http://127.0.0.1:3000?userName=<script>alert("反射型 XSS 攻擊")</script>, 可以看到頁(yè)面彈出 alert。
反射型XSS2
通過(guò)這個(gè)操作,我們會(huì)發(fā)現(xiàn)用戶(hù)將一段含有惡意代碼的請(qǐng)求提交給服務(wù)器,服務(wù)器在接收到請(qǐng)求時(shí),又將惡意代碼反射給瀏覽器端,這就是反射型XSS攻擊。另外一點(diǎn)需要注意的是,Web 服務(wù)器不會(huì)存儲(chǔ)反射型 XSS 攻擊的惡意腳本,這是和存儲(chǔ)型 XSS 攻擊不同的地方。
在實(shí)際的開(kāi)發(fā)過(guò)程中,我們會(huì)碰到這樣的場(chǎng)景,在頁(yè)面A中點(diǎn)擊某個(gè)操作,這個(gè)按鈕操作是需要登錄權(quán)限的,所以需要跳轉(zhuǎn)到登錄頁(yè)面,登錄完成之后再跳轉(zhuǎn)會(huì)A頁(yè)面,我們是這么處理的,跳轉(zhuǎn)登錄頁(yè)面的時(shí)候,會(huì)加一個(gè)參數(shù) returnUrl,表示登錄完成之后需要跳轉(zhuǎn)到哪個(gè)頁(yè)面,即這個(gè)地址是這樣的 http://xxx.com/login?returnUrl=http://xxx.com/A,假如這個(gè)時(shí)候把returnUrl改成一個(gè)script腳本,而你在登錄完成之后,如果沒(méi)有對(duì)returnUrl進(jìn)行合法性判斷,而直接通過(guò)window.location.href=returnUrl,這個(gè)時(shí)候這個(gè)惡意腳本就會(huì)執(zhí)行。
存儲(chǔ)型
存儲(chǔ)型會(huì)把用戶(hù)輸入的數(shù)據(jù)“存儲(chǔ)”在服務(wù)器。
比較常見(jiàn)的一個(gè)場(chǎng)景就是,攻擊者在社區(qū)或論壇寫(xiě)下一篇包含惡意 JavaScript代碼的博客文章或評(píng)論,文章或評(píng)論發(fā)表后,所有訪問(wèn)該博客文章或評(píng)論的用戶(hù),都會(huì)在他們的瀏覽器中執(zhí)行這段惡意的JavaScript代碼。
存儲(chǔ)型攻擊大致需要經(jīng)歷以下幾個(gè)步驟
首先攻擊者利用站點(diǎn)漏洞將一段惡意JavaScript代碼提交到網(wǎng)站數(shù)據(jù)庫(kù)中
然后用戶(hù)向網(wǎng)站請(qǐng)求包含了惡意 JavaScript腳本的頁(yè)面
當(dāng)用戶(hù)瀏覽該頁(yè)面的時(shí)候,惡意腳本就會(huì)將用戶(hù)的cookie信息等數(shù)據(jù)上傳到服務(wù)器
存儲(chǔ)型XSS
舉一個(gè)簡(jiǎn)單的例子,一個(gè)登陸頁(yè)面,點(diǎn)擊登陸的時(shí)候,把數(shù)據(jù)存儲(chǔ)在后端,登陸完成之后跳轉(zhuǎn)到首頁(yè),首頁(yè)請(qǐng)求一個(gè)接口將當(dāng)前的用戶(hù)名顯示到頁(yè)面
客戶(hù)端代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>XSS-demo</title>
<style>
.login-wrap {
height: 180px;
width: 300px;
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 20px;
}
input {
width: 300px;
}
</style>
</head>
<body>
<div class="login-wrap">
<input type="text" placeholder="用戶(hù)名" class="userName">
<br>
<input type="password" placeholder="密碼" class="password">
<br>
<br>
<button class="btn">登陸</button>
</div>
</body>
<script>
var btn = document.querySelector('.btn');
btn.onclick = function () {
var userName = document.querySelector('.userName').value;
var password = document.querySelector('.password').value;
fetch('http://localhost:3200/login', {
method: 'POST',
body: JSON.stringify({
userName,
password
}),
headers:{
'Content-Type': 'application/json'
},
mode: 'cors'
})
.then(function (response) {
return response.json();
})
.then(function (res) {
alert(res.msg);
window.location.href= "http://localhost:3200/home";
})
.catch(err => {
message.error(`本地測(cè)試錯(cuò)誤 ${err.message}`);
console.error('本地測(cè)試錯(cuò)誤', err);
});
}
</script>
</html>
服務(wù)端代碼
const Koa = require("koa");
const app = new Koa();
const route = require('koa-route');
var bodyParser = require('koa-bodyparser');
const cors = require('@koa/cors');
// 臨時(shí)用一個(gè)變量來(lái)存儲(chǔ),實(shí)際應(yīng)該存在數(shù)據(jù)庫(kù)中
let currentUserName = '';
app.use(bodyParser()); // 處理post請(qǐng)求的參數(shù)
const login = ctx => {
const req = ctx.request.body;
const userName = req.userName;
currentUserName = userName;
ctx.response.body = {
msg: '登陸成功'
};
}
const home = ctx => {
ctx.body = currentUserName;
}
app.use(cors());
app.use(route.post('/login', login));
app.use(route.get('/home', home));
app.listen(3200, () => {
console.log('啟動(dòng)成功');
});
點(diǎn)擊登陸將輸入信息提交大服務(wù)端,服務(wù)端使用變量 currentUserName來(lái)存儲(chǔ)當(dāng)前的輸入內(nèi)容,登陸成功后,跳轉(zhuǎn)到 首頁(yè), 服務(wù)端會(huì)返回當(dāng)前的用戶(hù)名。如果用戶(hù)輸入了惡意腳本內(nèi)容,則惡意腳本就會(huì)在瀏覽器端執(zhí)行。
在用戶(hù)名的輸入框輸入 <script>alert('存儲(chǔ)型 XSS 攻擊')</script>,執(zhí)行結(jié)果如下
存儲(chǔ)型XSS
基于DOM(DOM based XSS)
通過(guò)惡意腳本修改頁(yè)面的DOM節(jié)點(diǎn),是發(fā)生在前端的攻擊
基于DOM攻擊大致需要經(jīng)歷以下幾個(gè)步驟
攻擊者構(gòu)造出特殊的URL,其中包含惡意代碼
用戶(hù)打開(kāi)帶有惡意代碼的URL
用戶(hù)瀏覽器接受到響應(yīng)后執(zhí)行解析,前端JavaScript取出URL中的惡意代碼并執(zhí)行
惡意代碼竊取用戶(hù)數(shù)據(jù)并發(fā)送到攻擊者的網(wǎng)站,冒充用戶(hù)行為,調(diào)用目標(biāo)網(wǎng)站接口執(zhí)行攻擊者指定的操作。
舉個(gè)例子:
<body>
<div class="login-wrap">
<input type="text" placeholder="輸入url" class="url">
<br>
<br>
<button class="btn">提交</button>
<div class="content"></div>
</div>
</body>
<script>
var btn = document.querySelector('.btn');
var content = document.querySelector('.content');
btn.onclick = function () {
var url = document.querySelector('.url').value;
content.innerHTML = `<a href=${url}>跳轉(zhuǎn)到輸入的url</a>`
}
</script>
點(diǎn)擊提交按鈕,會(huì)在當(dāng)前頁(yè)面插入一個(gè)超鏈接,其地址為文本框的內(nèi)容。
在輸入框輸入 如下內(nèi)容
'' onclick=alert('哈哈,你被攻擊了')
執(zhí)行結(jié)果如下
基于DOM型XSS
首先用兩個(gè)單引號(hào)閉合調(diào) href屬性,然后插入一個(gè)onclick事件。點(diǎn)擊這個(gè)新生成的鏈接,腳本將被執(zhí)行。
上面的代碼是通過(guò)執(zhí)行 執(zhí)行 alert來(lái)演示的攻擊類(lèi)型,同樣你可以把上面的腳本代碼修改為任何你想執(zhí)行的代碼,比如獲取 用戶(hù)的 cookie等信息,<script>alert(document.cookie)</script>,同樣也是可以的.
防御XSS
HttpOnly
由于很多XSS攻擊都是來(lái)盜用Cookie的,因此可以通過(guò) 使用HttpOnly屬性來(lái)防止直接通過(guò) document.cookie 來(lái)獲取 cookie。
一個(gè)Cookie的使用過(guò)程如下
瀏覽器向服務(wù)器發(fā)起請(qǐng)求,這時(shí)候沒(méi)有 Cookie
服務(wù)器返回時(shí)設(shè)置 Set-Cookie 頭,向客戶(hù)端瀏覽器寫(xiě)入Cookie
在該 Cookie 到期前,瀏覽器訪問(wèn)該域下的所有頁(yè)面,都將發(fā)送該Cookie
HttpOnly是在 Set-Cookie時(shí)標(biāo)記的:
通常服務(wù)器可以將某些 Cookie 設(shè)置為 HttpOnly 標(biāo)志,HttpOnly 是服務(wù)器通過(guò) HTTP 響應(yīng)頭來(lái)設(shè)置的。
const login = ctx => {
// 簡(jiǎn)單設(shè)置一個(gè)cookie
ctx.cookies.set(
'cid',
'hello world',
{
domain: 'localhost', // 寫(xiě)cookie所在的域名
path: '/home', // 寫(xiě)cookie所在的路徑
maxAge: 10 * 60 * 1000, // cookie有效時(shí)長(zhǎng)
expires: new Date('2021-02-15'), // cookie失效時(shí)間
httpOnly: true, // 是否只用于http請(qǐng)求中獲取
overwrite: false // 是否允許重寫(xiě)
}
)
}
HttpOnly
需要注意的一點(diǎn)是:HttpOnly 并非阻止 XSS 攻擊,而是能阻止 XSS 攻擊后的 Cookie 劫持攻擊。
輸入和輸出的檢查
永遠(yuǎn)不要相信用戶(hù)的輸入。
輸入檢查一般是檢查用戶(hù)輸入的數(shù)據(jù)是都包含一些特殊字符,如 <、>, '及"等。如果發(fā)現(xiàn)特殊字符,則將這些字符過(guò)濾或編碼。這種可以稱(chēng)為 “XSS Filter”。
安全的編碼函數(shù)
針對(duì)HTML代碼的編碼方式是 HtmlEncode(是一種函數(shù)實(shí)現(xiàn),將字符串轉(zhuǎn)成 HTMLEntrities)
& --> &
< --> <
> --> >
" --> "
相應(yīng)的, JavaScript的編碼方式可以使用 JavascriptEncode。
假如說(shuō)用戶(hù)輸入了 <script>alert("你被攻擊了")</script>,我們要對(duì)用戶(hù)輸入的內(nèi)容進(jìn)行過(guò)濾(如果包含了 <script> 等敏感字符,就過(guò)濾掉)或者對(duì)其編碼,如果是惡意的腳本,則會(huì)變成下面這樣
<script>alert("你被攻擊了");</script>
經(jīng)過(guò)轉(zhuǎn)碼之后的內(nèi)容,如 <script>標(biāo)簽被轉(zhuǎn)換為 <script>,即使這段腳本返回給頁(yè)面,頁(yè)面也不會(huì)指向這段代碼。
防御 DOM Based XSS
我們可以回看一下上面的例子
btn.onclick = function () {
var url = document.querySelector('.url').value;
content.innerHTML = `<a href=${url}>跳轉(zhuǎn)到輸入的url</a>`
}
事實(shí)上,DOM Based XSS 是從 JavaScript中輸出數(shù)據(jù)到HTML頁(yè)面里。
用戶(hù)輸入 '' onclick=alert('哈哈,你被攻擊了'),然后通過(guò) innerHTML 修改DOM的內(nèi)容,就變成了 <a href='' onclick=alert('哈哈,你被攻擊了')>跳轉(zhuǎn)到輸入的url</a>, XSS因此產(chǎn)生。
那么正確的防御方法是什么呢?
從JavaScript輸出到HTML頁(yè)面,相當(dāng)于一次 XSS輸出的過(guò)程,需要根據(jù)不同場(chǎng)景進(jìn)行不同的編碼處理
變量輸出到 <script>,執(zhí)行一次 JavascriptEncode
通過(guò)JS輸出到HTML頁(yè)面
輸出事件或者腳本,做 JavascriptEncode 處理
輸出 HTML內(nèi)容或者屬性,做 HtmlEncode 處理
會(huì)觸發(fā) DOM Based XSS的地方有很多,比如
xxx.interHTML
xxx.outerHTML
document.write
頁(yè)面中所有的inputs框
XMLHttpRequest返回的數(shù)據(jù)
...
項(xiàng)目中如果用到,一定要避免在字符串中拼接不可信的數(shù)據(jù)。
利用CSP
CSP (Content Security Policy) 即內(nèi)容安全策略,是一種可信白名單機(jī)制,可以在服務(wù)端配置瀏覽器哪些外部資源可以加載和執(zhí)行。我們只需要配置規(guī)則,如何攔截是由瀏覽器自己實(shí)現(xiàn)的。我們可以通過(guò)這種方式來(lái)盡量減少 XSS 攻擊。
通??梢酝ㄟ^(guò)兩種方式來(lái)開(kāi)啟 CSP:
設(shè)置 HTTP Header 的 Content-Security-Policy
Content-Security-Policy: default-src 'self'; // 只允許加載本站資源
Content-Security-Policy: img-src https://* // 只允許加載 HTTPS 協(xié)議圖片
Content-Security-Policy: child-src 'none' // 允許加載任何來(lái)源框架
設(shè)置 meta 標(biāo)簽的方式
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
藍(lán)藍(lán)設(shè)計(jì)的小編 http://tweetduck.com