<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    實(shí)戰(zhàn):Express 模擬 CSRF 攻擊

    共 6232字,需瀏覽 13分鐘

     ·

    2021-02-08 15:29

    CSRF攻擊 是前端領(lǐng)域常見(jiàn)的安全問(wèn)題,概念方面不再贅述,可以參考維基百科。對(duì)于這些概念,包括名詞定義、攻擊方式、解決方案等估計(jì)大家都看過(guò)不少,但留下印象總是很模糊,要?jiǎng)邮植僮饕环拍芗由钣∠蟛⒛苷嬲斫?,所以我決定動(dòng)手實(shí)現(xiàn)一個(gè) CSRF 的攻擊場(chǎng)景,并通過(guò)演示的方式講解 CSRF 的防范手段。

    • CSRF 攻擊流程
    • CSRF 模擬攻擊
    • CSRF 防范方法

    CSRF 攻擊流程

    假設(shè)用戶(hù)先通過(guò) bank.com/auth 訪(fǎng)問(wèn)銀行網(wǎng)站A的授權(quán)接口,通過(guò)認(rèn)證后拿到A返回的 cookie: userId=ce032b305a9bc1ce0b0dd2a,接著攜帶 cookie 訪(fǎng)問(wèn) bank.com/transfer?number=15000&to=Bob 銀行A的轉(zhuǎn)賬接口轉(zhuǎn)給Bob 15000元,然后A返回 success 表示轉(zhuǎn)賬成功。

    釣魚(yú)網(wǎng)站B(hack.com)通過(guò)郵件或者廣告等方式引誘小明訪(fǎng)問(wèn),并返回給小明惡意的 HTML 攻擊代碼,HTML 中會(huì)包含發(fā)往銀行A的敏感操作:bank.com/transfer?number=150000&to=Jack ,此時(shí)瀏覽器會(huì)攜帶A的 cookie 發(fā)送請(qǐng)求,A拿到請(qǐng)求后,只通過(guò) cookie 判斷是個(gè)合法操作,于是在小明不知情的情況下,賬戶(hù)里150000元被轉(zhuǎn)給了Jack,即惡意攻擊者。

    這樣就完成了一次基本的 CSRF 攻擊。

    CSRF 攻擊流程圖如下:


    如果現(xiàn)在看不懂沒(méi)關(guān)系,可以看完演示再回頭看此圖就會(huì)恍然大悟了。

    CSRF 模擬攻擊

    首先通過(guò) express 搭建后端,以模擬 CSRF 攻擊。

    啟動(dòng)銀行 A 的服務(wù)器,端口 3001,包含 3 個(gè)接口:

    app.use('/',?indexRouter);
    app.use('/auth',?authRouter);
    app.use('/transfer',?transferRouter);

    authRouter:

    router.get('/',?function(req,?res,?next)?{
    ??res.cookie('userId',?'ce032b305a9bc1ce0b0dd2a',?{?expires:?new?Date(Date.now()?+?900000)?})
    ??res.end('ok')
    });

    transferRouter:

    router.get('/',?function(req,?res,?next)?{
    ??const?{?query?}?=?req;
    ??const?{?userId?}?=?req.cookies;
    ??if(userId){
    ????res.send({
    ??????status:?'transfer?success',
    ??????transfer:?query.number
    ????})
    ??}else{
    ????res.send({
    ??????status:?'error',
    ??????transfer:?''
    ????})
    ??}
    });

    使用 ejs 提供銀行轉(zhuǎn)賬頁(yè)面:

    html>
    <html?lang="en">

    <head>
    ??<meta?charset="UTF-8">
    ??<meta?name="viewport"?content="width=device-width,?initial-scale=1.0">
    ??<title>
    ????<%=?title?%>
    ??title>
    head>

    <body>
    ??<h2>
    ????轉(zhuǎn)賬
    ??h2>
    ??<script>
    ????const?h2?=?document.querySelector('h2');
    ????h2.addEventListener('click',?()?=>?{
    ??????fetch('/transfer?number=15000&to=Bob').then(res?=>?{
    ????????console.log(res.json());
    ??????})
    ????})
    ??
    script>
    body>

    html>

    假設(shè)釣魚(yú)網(wǎng)站 B 提供的惡意代碼為:

    html>
    <html?lang="en">

    <head>
    ??<meta?charset="UTF-8">
    ??<meta?name="viewport"?content="width=device-width,?initial-scale=1.0">
    ??<title>Documenttitle>
    head>

    <body>

    <div?class="wrapper">
    ??<iframe?src="http://bank.com/transfer?number=150000&to=Jack"?frameborder="0">iframe>
    div>
    ??<script>

    ??script>
    body>
    html>

    并將其啟動(dòng)在3002端口,再通過(guò) Whistle 進(jìn)行域名映射,因?yàn)閮烧叨际?Localhost 域名,而 Cookie 不區(qū)分端口,所以需要區(qū)分域名。


    首先打開(kāi) Firefox 瀏覽器(暫時(shí)不用 Chrome ),訪(fǎng)問(wèn)銀行 A 的 /auth獲得授權(quán):


    然后通過(guò)點(diǎn)擊轉(zhuǎn)賬按鈕發(fā)送請(qǐng)求 http://bank.com/transfer?number=15000&to=Bob 進(jìn)行轉(zhuǎn)賬操作:


    用戶(hù)收到誘惑進(jìn)入了 hack 網(wǎng)站,hack 網(wǎng)站首頁(yè)有一個(gè)發(fā)往銀行A的請(qǐng)求 http://bank.com/transfer?number=150000&to=Jack ,這個(gè)請(qǐng)求可以放在 iframe、img、script 等的 src 里面。


    可以看到請(qǐng)求攜帶 cookie,并成功轉(zhuǎn)賬,這樣一次 CSRF 攻擊就完成了。當(dāng)然這是一次簡(jiǎn)單的 GET 請(qǐng)求的攻擊,POST 請(qǐng)求攻擊可以通過(guò)自動(dòng)提交表單實(shí)現(xiàn),比如:

    <form?action="bank.com/transfer"?method=POST>
    ????<input?type="hidden"?name="number"?value="150000"?/>
    ????<input?type="hidden"?name="to"?value="Jack"?/>
    form>
    <script>?document.forms[0].submit();?script>

    從上面可以看出,CSRF 攻擊主要特點(diǎn)是:

    1. 發(fā)生在第三方域名(hack.com)。
    2. 攻擊者只能使用 cookie 而拿不到具體的 cookie。

    針對(duì)以上特點(diǎn),我們就能進(jìn)行對(duì)應(yīng)的防范了。

    CSRF 防范方法

    CSRF 防范方法通常有以下幾種:

    1. 阻止不同域的訪(fǎng)問(wèn)
      1. 同源檢測(cè)。
      2. Samesite Cookie。
    2. 提交時(shí)要求附加本域才能獲取的信息。
      1. 添加 CSRF Token。
      2. 雙重 Cookie驗(yàn)證。

    同源檢測(cè) - 通過(guò) Origin 和 Referer 確定來(lái)源域名

    針對(duì)第一個(gè)特點(diǎn)進(jìn)行域名檢查,HTTP 請(qǐng)求時(shí)會(huì)攜帶這兩個(gè) Header,用于標(biāo)記來(lái)源域名,如果請(qǐng)求來(lái)源不是本域,直接進(jìn)行攔截。


    但是這兩個(gè) Header 也是可以不攜帶的,所以我們的策略是校驗(yàn)如果兩個(gè) Header 不存在或者存在但不是本域則阻攔。

    修改 transferRouter 代碼如下:

    const?csrfGuard?=?require('../middleware/csrfGuard')
    /*?GET?users?listing.?*/
    router.get('/',?csrfGuard,?function(req,?res,?next)?{
    ??const?{?query?}?=?req;
    ??const?{?userId?}?=?req.cookies;
    ??if(userId){
    ????res.send({
    ??????status:?'transfer?success',
    ??????transfer:?query.number
    ????})
    ??}else{
    ????next()
    ??}
    });
    router.get('/',?function(req,?res,?next)?{
    ??res.send({
    ????status:?'error',
    ????transfer:?''
    ??})
    });

    csrfGuard.js:

    module.exports?=?function(req,?res,?next){
    ??const?[Referer,?Origin]?=?[req.get('Referer'),?req.get('Origin')]
    ??if(Referer?&&?Referer.indexOf('bank.com')?>?0){
    ????next();
    ??}
    ??else?if(Origin?&&?Origin.indexOf('bank.com')?>?0){
    ????next();
    ??}else{
    ????next('route')
    ??}
    }

    驗(yàn)證:


    Samesite Cookie

    在敏感 cookie 上攜帶屬性 Samesite:Strict 或 Lax,可以避免在第三方不同域網(wǎng)站上攜帶 cookie,具體原因可以參考阮一峰老師的Cookie 的 SameSite 屬性。

    //?authRouter.js
    router.get('/',?function(req,?res,?next)?{
    ??res
    ??.cookie('userId',?'ce032b305a9bc1ce0b0dd2a',?{?expires:?new?Date(Date.now()?+?900000),?sameSite:?'lax'?})
    ??res.end('ok')
    });

    查看 bank.com cookie:


    再次訪(fǎng)問(wèn) hack.com,發(fā)現(xiàn)轉(zhuǎn)賬鏈接并未攜帶 cookie:


    這樣就達(dá)到了防范的目的,兼容性 目前來(lái)看還可以,雖然沒(méi)有達(dá)到完美覆蓋,但大部分瀏覽器也都支持了

    PS: 前面之所以沒(méi)有使用 Chrome 瀏覽器做實(shí)驗(yàn),是因?yàn)閺?Chrome 80 版本起,Samesite 被默認(rèn)設(shè)置為了 Lax,而 Firefox 仍然為 None。

    添加 CSRF Token

    首先服務(wù)器生成一個(gè)動(dòng)態(tài)的 token,傳給用戶(hù),用戶(hù)再次提交或者請(qǐng)求敏感操作時(shí),攜帶此 token,服務(wù)端校驗(yàn)通過(guò)才返回正確結(jié)果。

    改寫(xiě) indexRouter,使其返回 token 給頁(yè)面:

    var?express?=?require("express");
    var?router?=?express.Router();
    const?jwt?=?require("jsonwebtoken");

    router.get("/",?function?(req,?res,?next)?{
    ????res.render("index",?{?title:?"Express",?token:?jwt.sign({
    ??????username:?'ming'
    ????},?'key',?{
    ??????expiresIn:?'1d'
    ????})?});
    });

    module.exports?=?router;

    前端頁(yè)面:

    //?index.ejs

    ??<h2>
    ????轉(zhuǎn)賬
    ??h2>

    ??<span?id='token'?data-token=<%=?token?%>>span>
    ??<script>
    ????const?h2?=?document.querySelector('h2');
    ????const?tokenElem?=?document.querySelector('#token');
    ????const?token?=?tokenElem.dataset.token;
    ????h2.addEventListener('click',?()?=>?{
    ??????fetch('/transfer?number=15000&to=Bob&token='?+?token).then(res=>{
    ????????console.log(res.json());
    ??????})
    ????})
    ??
    script>

    </body>

    將 transferRouter 的驗(yàn)證中間件改成 token 驗(yàn)證:

    const?tokenVerify?=?require('../middleware/tokenVerify')

    router.get('/',?tokenVerify,?function(req,?res,?next)?{
    ??const?{?query?}?=?req;
    ??const?{?userId?}?=?req.cookies;
    ??if(userId){
    ????res.send({
    ??????status:?'transfer?success',
    ??????transfer:?query.number
    ????})
    ??}else{
    ????next()
    ??}
    });

    JWT 驗(yàn)證:

    const?jwt?=?require("jsonwebtoken");

    module.exports?=?function(req,?res,?next){
    ??const?{?token?}?=?req.query;
    ??jwt.verify(token,'key',?(err,?decode)=>?{
    ????if(err){
    ??????next('route')
    ????}else{
    ??????console.log(decode);
    ??????next()
    ????}
    ??})
    }

    攜帶 token 正常訪(fǎng)問(wèn)成功:


    釣魚(yú)網(wǎng)站拿不到 token 所以攻擊失敗:


    以上為加深理解而寫(xiě)的代碼,而在生產(chǎn)環(huán)境中,node 可以使用 csurf中間件來(lái)防御 csrf 攻擊

    雙重Cookie驗(yàn)證

    設(shè)置一個(gè)專(zhuān)用 cookie,因?yàn)楣粽吣貌坏?cookie,所以將 cookie 種到域名的同時(shí),訪(fǎng)問(wèn)敏感操作也需要攜帶,攻擊者帶不上 cookie,就達(dá)到了防范的目的。

    //?authRouter.js
    const?randomString?=?require('random-string');
    /*?GET?users?listing.?*/
    router.get('/',?function(req,?res,?next)?{
    ??res
    ??.cookie('userId',?'ce032b305a9bc1ce0b0dd2a',?{?expires:?new?Date(Date.now()?+?900000)?})
    ??.cookie('csrfcookie',?randomString(),?{?expires:?new?Date(Date.now()?+?900000)?})
    ??res.end('ok')
    });

    bank.com 銀行轉(zhuǎn)賬頁(yè)面:

    ??<script>
    ????const?h2?=?document.querySelector('h2');
    ????const?csrfcookie?=?getCookie('csrfcookie')
    ????h2.addEventListener('click',?()?=>?{
    ??????fetch('/transfer?number=15000&to=Bob&csrfcookie='?+?csrfcookie).then(res?=>?{
    ????????console.log(res.json());
    ??????})
    ????})
    ??
    script>

    驗(yàn)證中間件:

    //?doubleCookie.js?
    module.exports?=?function(req,?res,?next){
    ??const?queryCsrfCookie?=?req.query.csrfcookie
    ??const?realCsrfCookie?=?req.cookies.csrfcookie;
    ??console.log(queryCsrfCookie,?realCsrfCookie);
    ??if(queryCsrfCookie?===?realCsrfCookie){
    ????next()
    ??}else{
    ????next('route')
    ??}
    }

    銀行 bank.com:


    而 hack.com 拿不到 csrfcookie 所以驗(yàn)證不通過(guò)。

    這個(gè)方法也是很有效的,比如請(qǐng)求庫(kù) axios 就是用的這種方式。

    總結(jié)

    到這里大家是不是已經(jīng)明白了 CSRF 攻擊的原因所在,并可以提出針對(duì)性的解決方案了呢,防范關(guān)鍵其實(shí)就是防止其他人冒充你去做只有你能做的敏感操作,與此同時(shí)希望大家對(duì)于這類(lèi)抽象性的問(wèn)題可以自己動(dòng)手敲一下,模擬一遍,用造重復(fù)輪子的方法去理解,動(dòng)手比動(dòng)眼管用的多。

    以上過(guò)程和代碼僅僅為幫助學(xué)習(xí)并做演示使用,如果用于生產(chǎn)力還是需要更成熟的解決方案。


    瀏覽 59
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    黄色视频免费收看黄色视频免费收看 | 亚洲AV无码精品色午夜红一片 | 人人看人人搂人人摸 | 日韩精品一区二区亚洲AV观看 | 欧美精品在线第一页 |