前言
前端關(guān)于網(wǎng)絡(luò)安全看似高深莫測(cè),其實(shí)來(lái)來(lái)回回就那么點(diǎn)東西,我總結(jié)一下就是 3 + 1 ?= 4,3個(gè)用字母描述的【分別是 XSS、CSRF、CORS】 + 一個(gè)中間人攻擊。當(dāng)然 CORS 同源策略是為了防止攻擊的安全策略,其他的都是網(wǎng)絡(luò)攻擊。除了這 4 個(gè)前端相關(guān)的面試題,其他的都是一些不常用的小嘍啰。
我將會(huì)在我的《面試題一網(wǎng)打盡》專欄中先逐一詳細(xì)介紹,然后再來(lái)一篇文章總結(jié),預(yù)計(jì)一共5篇文章,歡迎大家關(guān)注~
本篇文章是前端網(wǎng)絡(luò)安全相關(guān)的第一篇文章,內(nèi)容就是 CSRF?攻擊。
一、準(zhǔn)備工作
CSRF(cross-site request forgery)跨站請(qǐng)求偽造,是利用 iframe、img、a 、link、form等標(biāo)簽src、href 等不存在跨域問(wèn)題的特點(diǎn)。CSRF 攻擊的本質(zhì)是利用 cookie 會(huì)在同源(同站)請(qǐng)求中攜帶發(fā)送給服務(wù)器的特點(diǎn),以此來(lái)實(shí)現(xiàn)用戶的冒充,偽造用戶的請(qǐng)求。
1.1 拉取倉(cāng)庫(kù)
所以本篇文章的基礎(chǔ)是需要一個(gè)服務(wù)端的項(xiàng)目,可以跟著我的這篇文章搭建自己的服務(wù)端項(xiàng)目?;蛘咧苯涌寺∥业膫}(cāng)庫(kù)代碼在這個(gè)提交上拉一個(gè)新分支,本篇文章所有的代碼都是在這個(gè)提交基礎(chǔ)上進(jìn)行的。
在本篇文章之前,我已經(jīng)寫了xss 攻擊的文章,所以在你拉取我的 git 最新代碼的時(shí)候,已經(jīng)有很多更新的提交了,不過(guò),無(wú)論是從上面我說(shuō)的那個(gè)提交拉取新分支,還是拉取最新的代碼都可以,我的倉(cāng)庫(kù)的所有的合并都是相互獨(dú)立的。
不論你先看 XSS 教程還是先看 CSRF?教程都可以。
1.2 新增 CSRF 文件夾
二、攻擊條件
- 目標(biāo)站點(diǎn)一定要有 CSRF 漏洞
- 用戶要登錄過(guò)目標(biāo)站點(diǎn),并且在瀏覽器上保持有該站點(diǎn)的登錄狀態(tài)
- 需要用戶打開一個(gè)第三方站點(diǎn),可以是黑客的站點(diǎn),也可以是一些論壇
所以說(shuō)這個(gè)攻擊,有兩個(gè)網(wǎng)站!
三、攻擊類型
3.1 get 攻擊:
利用 iframe、img、a?等標(biāo)簽發(fā)起 get 請(qǐng)求時(shí),不存在跨域的問(wèn)題,利用 cookie 發(fā)起跨站請(qǐng)求,注意是跨站,不是跨域【協(xié)議+域名+端口號(hào)都相同才不跨域、有效頂級(jí)域名 + 1 相同才不跨站】
在當(dāng)前訪問(wèn)的網(wǎng)站和請(qǐng)求服務(wù)的網(wǎng)站跨站的情況下,第三方服務(wù)設(shè)置的 cookie 就稱之為第三方 cookie,比如當(dāng)前網(wǎng)站A,有一個(gè) iframe 地址為 B,B 請(qǐng)求服務(wù),B 的 cookie 對(duì)于網(wǎng)站 A 就是第三方 cookie,跨站的概念和 cookie 的 domain 屬性設(shè)置是差不多的,
還可以利用 a 標(biāo)簽的 href 屬性,這個(gè)本質(zhì)上也是 get 請(qǐng)求的攻擊。
跨站和跨域的關(guān)系與區(qū)別
- 跨站 not samesite:兩個(gè)域名不屬于同站(域名-主機(jī)名/IP相同,協(xié)議相同)。
- 跨域:兩個(gè)域名不屬于同源(域名-主機(jī)名/IP相同,端口號(hào)相同,協(xié)議相同)。
Cookie 的同源主要用于防止 CRSF,Cookie 的校驗(yàn)較寬松,Cookie 只關(guān)注域名,忽略協(xié)議和端口,只要兩個(gè) URL 的 eTLD+1 相同即可【頂級(jí)域名+1】,eTLD 表示有效頂級(jí)域名,注冊(cè)于 Mozilla 維護(hù)的公共后綴列表(Public Suffix List)中,例如,.com、.co、.uk、.github.io 等。而 eTLD+1 則表示,有效頂級(jí)域名+二級(jí)域名,例如 taobao.com 等。
- www.taobao.com 和 www.baidu.com 是跨站
- www.a.taobao.com 和 www.b.taobao.com 是同站
- a.github.io和b.github.io是跨站
所以跨站即:兩個(gè) URL 頂級(jí)域名和二級(jí)域名不同就是跨站(也稱第三方(Third-party)),相同則是同站本方(First-party)
跨站就一定跨域
跨域不一定跨站
3.2 post 攻擊:
構(gòu)建一個(gè)表單 html 中的 <form action="xxx" method="get/post"></form>,隱藏它,當(dāng)用戶進(jìn)入頁(yè)面時(shí),自動(dòng)提交這個(gè)表單,一個(gè) form 表單可以實(shí)現(xiàn) post 請(qǐng)求或者get 請(qǐng)求
好久不用原生的 form 表單了,都忘了怎么用了,在提交 form?表單之后頁(yè)面會(huì)自動(dòng)跳轉(zhuǎn),想要阻止自動(dòng)跳轉(zhuǎn),就不能用 form 自帶的 submit 功能了,需要手動(dòng)使用 js 提交表單的內(nèi)容,但是那樣就不會(huì)發(fā)起 csrf 攻擊了。
四、get 類型 CSRF 攻擊
get 類型 CSRF 攻擊是利用 iframe、img 等標(biāo)簽發(fā)起 get 請(qǐng)求時(shí),不存在跨域的問(wèn)題,利用 cookie 發(fā)起跨站請(qǐng)求。
4.1 攻擊的步驟
- 有一個(gè)安全的網(wǎng)站 A(https://safe.com),用戶登錄的身份驗(yàn)證存在 cookie 中,使用 get 請(qǐng)求保存敏感信息(密碼),如:https://safe.com/savePwd?pwd=456,在請(qǐng)求時(shí)會(huì)攜帶cookie的身份信息
- 用戶在 A網(wǎng)站正常登錄,useId 保存在 cookie 中,A 的服務(wù)器接收到請(qǐng)求后,驗(yàn)證 cookie 信息后,就開始更改密碼。
- 有一個(gè)黑客,偽造了一個(gè)惡意網(wǎng)站B(https://danger.com),此網(wǎng)站有一個(gè)有一個(gè) iframe或者img,src 的值是?https://safe.com/savePwd?pwd=456 ,黑客誘導(dǎo)用戶打開這個(gè)網(wǎng)站,并且A網(wǎng)站還沒(méi)有退出登錄?!疽部梢杂?a 標(biāo)簽】
- 用戶打開 B 網(wǎng)站就會(huì)自動(dòng)發(fā)送 更改密碼的請(qǐng)求【因?yàn)闀?huì)自動(dòng)加載 iframe 里面的內(nèi)容】,并且攜帶了A網(wǎng)站的 cookie?!灸軘y帶是瀏覽器的功能SameSite=none的功能】
- cookie 不區(qū)分端口號(hào),但是區(qū)分 path
- B 網(wǎng)站只能利用 cookie,但是不能通過(guò) document.cookie 獲取 A 的 cookie,因?yàn)樵贐網(wǎng)站上獲取的只能是 B 的 cookie
4.2 代碼實(shí)現(xiàn)
根據(jù) 4.1 的流程實(shí)現(xiàn)代碼即可。
4.2.1 安裝 cookie-parser
我們主要的目的是盜用 cookie,所以要學(xué)會(huì)在 express 服務(wù)端代碼中設(shè)置、獲取 cookie,我們使用?cookie-parser 這個(gè)安裝包
pnpm i cookie-parser
4.2.2 安裝?cors
因?yàn)?cookie 是不區(qū)分端口號(hào)的,我們有一個(gè)不安全的頁(yè)面 2,并且頁(yè)面 2 和頁(yè)面 1 是跨站的,而且跨站一定會(huì)跨域,所以我們要保證我們的服務(wù)支持跨域請(qǐng)求,express 中設(shè)置支持跨域的方法很簡(jiǎn)單就是使用 cors 這個(gè) npm 包。
pnpm i cors
4.2.3 新增 csrf/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.button {
cursor: pointer;
}
</style>
</head>
<body>
<div class="button" onclick="login()">點(diǎn)擊用戶1登錄,設(shè)置cookie</div>
<div class="button" onclick="savePwd()">點(diǎn)擊修改密碼</div>
<script>
function login() {
fetch('/getInfo').then((res) => {
console.log('登錄成功', res);
});
}
function savePwd() {
fetch('/savePwd?pwd=456').then((data) => {
return data.json()
}).then((res) => {
console.log('修改結(jié)果', res)
})
}
</script>
</body>
</html>
4.2.4 新增 csrf/index2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>惡意網(wǎng)站</h1>
<a >點(diǎn)擊修改密碼</a>
<!--<img src="https://www.safe.com/savePwd?pwd=789"/>-->
<!--<iframe src="https://www.safe.com/savePwd?pwd=789">點(diǎn)擊修改密碼</iframe>-->
</body>
</html>
4.2.5 新增 csrf/index.js
const express = require('express');
const path = require('path');
const cors = require('cors');
const cookieParser = require('cookie-parser');
// 第一個(gè)服務(wù)
const app = express();
// 允許跨域請(qǐng)求
app.use(cors());
// 解析 cookie
app.use(cookieParser());
// 安全的網(wǎng)頁(yè),用于用戶登錄
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, '/index.html'));
});
// 安全的網(wǎng)頁(yè)的登錄接口,給網(wǎng)站設(shè)置 cookie
app.get('/getInfo', function (req, res) {
res.cookie('userId', '111111', {
expires: new Date(Date.now() + 900000),
sameSite: 'None', // 這個(gè)是必須設(shè)置的
secure: 'true', //只要是 truthy 值就可以
path: '/',
});
res.send('end');
});
// 修改密碼的接口
app.get('/savePwd', function (req, res, next) {
const { query, cookies } = req;
// 能獲取 cookie 才返回修改成功
if (cookies.userId) {
res.json({
pwd: query.pwd,
message: '修改成功',
});
} else {
res.json({
pwd: query.pwd,
message: '修改失敗',
});
}
});
app.listen(3000)
// 第二個(gè)服務(wù)
// 不安全的網(wǎng)頁(yè),用于用 iframe/ a 標(biāo)簽 等 嵌入安全的網(wǎng)頁(yè),盜用 cookie
const app2 = express();
app2.get('/', function (req, res) {
res.sendFile(path.join(__dirname, '/index2.html'));
});
app2.listen(3001)
4.2.6 運(yùn)行代碼
npm run dev csrf
4.2.7?使用 whistle
按照官網(wǎng)安裝即可,我們使用它的目的是
- 把 localhost:3000 映射到 https://www.safe.com
- 把 localhost:3001?映射到 https://www.danger.com
這樣我們就有 2 個(gè)跨站的域名了。
安裝之后 在命令行運(yùn)行
w2 start
然后訪問(wèn)http://127.0.0.1:8899/ 設(shè)置域名映射
4.2.8 結(jié)果
這里面有一個(gè)關(guān)鍵點(diǎn)是需要設(shè)置 sameSite = none 、?secure = true
(1)chrome 瀏覽器默認(rèn)的 sameSite = lax,所以要手動(dòng)設(shè)置 sameSite = none
(2)要設(shè)置 sameSite=none,那么就必須要設(shè)置 secure =true,而設(shè)置后 secure=true之后,?只能通過(guò)https 請(qǐng)求攜帶cookie了,這也是我們要使用域名代理的原因。
參考這篇文章
?4.2.9 提交代碼
五、防御攻擊
5.1 進(jìn)行同源檢測(cè)
服務(wù)端進(jìn)行同源檢測(cè)【類似非簡(jiǎn)單請(qǐng)求的同源策略,get請(qǐng)求不會(huì)跨域】,在接收到請(qǐng)求之后,判斷 origin 或 referer 的值是否是安全的【如果是在惡意網(wǎng)站發(fā)起的請(qǐng)求,origin、referer 會(huì)顯示惡意網(wǎng)站,如下圖】【優(yōu)先判斷 origin】
Origin 或 Referer 的關(guān)系和區(qū)別
- Origin 和 Referer 都可以服務(wù)端用來(lái)做來(lái)源驗(yàn)證,來(lái)防止 csrf 攻擊,都是瀏覽器自動(dòng)帶在請(qǐng)求頭的
- 但是,可以通過(guò) Referrer Policy 來(lái)禁止請(qǐng)求攜帶 referer,【請(qǐng)求頭增加字段 Referrer-Policy: no-referrer/origin等】所以服務(wù)器驗(yàn)證請(qǐng)求中的 referer 不太可靠
- 因此標(biāo)準(zhǔn)委員會(huì)又制定了 origin 屬性,在一些重要的場(chǎng)合,比如通過(guò) XMLHttpRequest 、fetch 發(fā)起跨站請(qǐng)求時(shí),都會(huì)帶上 origin
- Origin 包含協(xié)議、主機(jī)和端口,不包含路徑
- Referer 只包含了源頁(yè)面的 URI,包含路徑,而且該字段可能在用戶隱私方面存在一些敏感性問(wèn)題。
- Origin 主要用于跨站請(qǐng)求的安全性檢查,而 Referer 則是提供請(qǐng)求來(lái)源信息的通用手段,可用于日志記錄、統(tǒng)計(jì)分析等。
- 所以對(duì)于 csrf 來(lái)源驗(yàn)證,服務(wù)器的策略是有優(yōu)先判斷 origin;如果請(qǐng)求頭中沒(méi)有包含 origin 屬性 ,再根據(jù)實(shí)際情況判斷是否使用 referer?
Origin:
- 用途: Origin 請(qǐng)求頭用于表示一個(gè) URI 的起源,包括協(xié)議、主機(jī)和端口。
- 格式: Origin: <scheme>://<host>:<port>
- 安全性: Origin 是由瀏覽器自動(dòng)設(shè)置的,它通常用于跨站請(qǐng)求的安全性檢查。在跨域請(qǐng)求時(shí),瀏覽器會(huì)檢查目標(biāo)服務(wù)器是否允許來(lái)自特定 Origin 的請(qǐng)求,如果不允許,瀏覽器會(huì)阻止此類請(qǐng)求。
- origin的值不能手動(dòng)修改,不能手動(dòng)設(shè)置都是瀏覽器自動(dòng)的
Referer:
- 用途: Referer 請(qǐng)求頭用于表示請(qǐng)求的來(lái)源頁(yè)面的 URI。它指明了用戶是從哪個(gè)頁(yè)面跳轉(zhuǎn)或提交請(qǐng)求的。
- 格式: Referer: <origin>
- 安全性: Referer 是由瀏覽器設(shè)置的,但它不像 Origin 那樣用于強(qiáng)制安全性檢查。雖然 Referer 也可以在某些場(chǎng)景下用于防范 CSRF 攻擊,但它的值可由用戶和服務(wù)端控制,不是可靠的安全控制手段。
- 可以手動(dòng)修改document.write('<meta name="referrer" content="http://example.com">')但是會(huì)收到安全限制,不建議
?5.1.1 修改 index.js
現(xiàn)在我們來(lái)根據(jù)請(qǐng)求的 referer 來(lái)進(jìn)行同源檢測(cè),我們的例子中使用的是a 標(biāo)簽,瀏覽器不會(huì)自動(dòng)攜帶 origin , 所以我們只能根據(jù) referer 判斷了。
這里面有一個(gè)關(guān)于 express 的知識(shí),就是使用 req.get 獲取http的請(qǐng)求頭?
5.1.2 運(yùn)行
這回我們?cè)冱c(diǎn)擊惡意網(wǎng)站的鏈接跳轉(zhuǎn),就會(huì)顯示失敗
?
5.2 使用 CSRF token 驗(yàn)證
其實(shí)本質(zhì)就是每一個(gè)請(qǐng)求都加一個(gè) token 驗(yàn)證,在該密碼的接口中帶上這個(gè) token,由服務(wù)器進(jìn)行驗(yàn)證,驗(yàn)證通過(guò)才進(jìn)行下一步操作。token 的設(shè)置主要有兩種方式
- 頁(yè)面加載時(shí)硬編碼在 html 中【要用服務(wù)端渲染,服務(wù)端生成token,嵌在html中,比如form表單的隱藏字段中】【這種方法的問(wèn)題是,每一次請(qǐng)求都需要加上這個(gè) token,并且如果加上了 cdn 負(fù)載均衡服務(wù)器(分布式服務(wù)器),每個(gè)服務(wù)器的session 無(wú)法同步,那就保證token的正確了】。很多教程中都是說(shuō)的用這種服務(wù)端渲染的硬編碼!但是我們常用的vue等框架不是ssr的,所以看的云里霧里,如果是用的nuxt等框架就可以實(shí)現(xiàn)這種邏輯了
- 前端動(dòng)態(tài)獲取 CSRF 令牌,用新的接口獲取 token【要保證這個(gè)token獲取接口是安全且身份驗(yàn)證和授權(quán),不容易受到 CSRF 攻擊】這種方式通常被稱為 "CSRF Token Refresh",其中前端可以通過(guò)新接口請(qǐng)求一個(gè)最新的 CSRF 令牌,并將其嵌入到后續(xù)的請(qǐng)求中。這可以提高安全性,特別是對(duì)于那些用戶會(huì)話可能變得很長(zhǎng)時(shí)間的應(yīng)用程序,因?yàn)?CSRF 令牌的有效性通常是有限的,但是這樣的話,邏輯循環(huán)了,要怎么保證獲取 token 的接口不會(huì)受到 CSRF攻擊呢。
5.3?對(duì)cookie進(jìn)行雙重驗(yàn)證
cookie 進(jìn)行雙重驗(yàn)證是指,在第一次訪問(wèn)服務(wù)器時(shí),設(shè)置一個(gè) cookie,后續(xù)的請(qǐng)求都需要把這個(gè)cookie 取出來(lái)添加到參數(shù) URL 中,然后服務(wù)器需要對(duì) cookie 的數(shù)據(jù)和 URL 上的參數(shù)進(jìn)行比較。惡意網(wǎng)站無(wú)法獲取 cookie 自然就無(wú)法拼出正確的 URL 了。這種方法不涉及分布式訪問(wèn)的問(wèn)題,但是如果存在 XSS 漏洞,就會(huì)失效,這種方法不能做到子域名的隔離。
5.3.1?修改 index.js
5.3.2?修改 index.html
這樣修改之后,在惡意網(wǎng)站就無(wú)法請(qǐng)求成功了,因?yàn)閻阂饩W(wǎng)站請(qǐng)求的地址沒(méi)變!因?yàn)槲覀儧](méi)有修改 index2.html
5.3.3 提交代碼?
5.4?cookie 設(shè)置 SameSite
在服務(wù)端設(shè)置 cookie 屬性的時(shí)候設(shè)置 SameSite 限制cookie不能被第三方使用,這個(gè)是服務(wù)端設(shè)置的。samesite 取值 none/Strict/Lax,默認(rèn)為 Lax,chrome 瀏覽器的開發(fā)者工具中顯示的空,其實(shí)就是Lax。
嚴(yán)格模式 Strict,cookie 在任何情況下都不可能作為第三方cookie使用;
寬松模式下 Lax,cookie可以被提交get表單、會(huì)發(fā)生頁(yè)面跳轉(zhuǎn)的請(qǐng)求使用a 標(biāo)簽/link標(biāo)簽,但是如果在第三方中使用post方法或者使用 img/iframe 等標(biāo)簽加載 URL 這些場(chǎng)景不會(huì)攜帶 cookie。
將 Samesite 設(shè)為 Lax ,這種模式稱為寬松模式,也是目前瀏覽器中的默認(rèn)值。如果這個(gè)請(qǐng)求是個(gè) GET 請(qǐng)求,并且這個(gè)請(qǐng)求改變了當(dāng)前頁(yè)面或者打開了新的頁(yè)面,那么這個(gè) cookie 可以作為第三方 cookie【和本篇文章 4.2 中的例子一樣】,其余情況下都不能作為第三方 cookie。使用這種方法的缺點(diǎn)是,因?yàn)樗恢С肿佑?,所以子域沒(méi)有辦法與主域共享登錄信息,每次轉(zhuǎn)入子域的網(wǎng)站,都需要重新登錄。還有一個(gè)問(wèn)題就是它的兼容性不夠好。
5.4.1 設(shè)置 sameSite
因?yàn)閏hrome瀏覽器默認(rèn)的 cookie 的 samesite 值是 lax,雖然在開發(fā)者工具中顯示的是空,所以如果我們模擬的 demo 中使用的是 firame 或者 src 就不會(huì)跨站攜帶 cookie.。要想我們的demo生效需要在服務(wù)端設(shè)置 cookie的時(shí)候同時(shí)設(shè)置 samesite=none & secure=true。但是 srcure 只有在https請(qǐng)求中可以設(shè)置成功,所以我們需要使用代理工具,將本地的ip地址映射成https:的域名,使用whistle工具很方便 w2 start即可
經(jīng)過(guò)這樣設(shè)置,基本上可以杜絕所有的 CSRF?攻擊了。?
總結(jié)
模擬 CSRF 攻擊的代碼已經(jīng)完成,我的倉(cāng)庫(kù)地址如下,歡迎查看
yangjihong2113/learn-express
內(nèi)容較多,難免疏漏,如有問(wèn)題,歡迎指正。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-773596.html
這是一系列的文章,我已經(jīng)總結(jié)完關(guān)于 XSS 攻擊的內(nèi)容,關(guān)于網(wǎng)絡(luò)安全的內(nèi)容還有 CORS 和中間人攻擊的內(nèi)容沒(méi)有總結(jié),持續(xù)更新中,歡迎關(guān)注。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-773596.html
到了這里,關(guān)于徹底理解前端安全面試題(2)—— CSRF 攻擊,跨站請(qǐng)求偽造攻擊詳解,建議收藏(含源碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!