1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 11036字,需瀏覽 23分鐘

         ·

        2021-04-29 09:34

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

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

        CSRF 攻擊流程

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

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

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

        CSRF 攻擊流程圖如下:


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

        CSRF 模擬攻擊

        首先通過 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', { expiresnew 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)賬頁面:

        <!DOCTYPE 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è)釣魚網(wǎng)站 B 提供的惡意代碼為:

        <!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>

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

          </script>
        </body>
        </html>

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


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


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


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


        可以看到請求攜帶 cookie,并成功轉(zhuǎn)賬,這樣一次 CSRF 攻擊就完成了。當(dāng)然這是一次簡單的 GET 請求的攻擊,POST 請求攻擊可以通過自動(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。

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

        CSRF 防范方法

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

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

        同源檢測 - 通過 Origin 和 Referer 確定來源域名

        針對第一個(gè)特點(diǎn)進(jìn)行域名檢查,HTTP 請求時(shí)會(huì)攜帶這兩個(gè) Header,用于標(biāo)記來源域名,如果請求來源不是本域,直接進(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', { expiresnew Date(Date.now() + 900000), sameSite'lax' })
          res.end('ok')
        });

        查看 bank.com cookie:


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


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

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

        添加 CSRF Token

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

        改寫 indexRouter,使其返回 token 給頁面:

        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;

        前端頁面:

        // index.ejs
        <body>
          <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 正常訪問成功:


        釣魚網(wǎng)站拿不到 token 所以攻擊失?。?/p>


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

        雙重Cookie驗(yàn)證

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

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

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

          <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)證不通過。

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

        總結(jié)

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

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

        最后



        如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個(gè)小忙:

        1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

        2. 歡迎加我微信「 sherlocked_93 」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

        3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。


        點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            乱肉合集乱500小说怀孕 | xxxx天堂网上xxxx | 修仙高肉h爽文bl | 女生张开腿让男生捅视频 | 无码专区视频 | 特黄毛片| 让人看了下面流水的视频 | 色偷偷av | 日韩淫荡视频 | 欧美男同又粗又长又大 |