<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6232字,需瀏覽 13分鐘

           ·

          2021-02-08 15:29

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

          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 提供的惡意代碼為:

          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端口,再通過 Whistle 進(jìn)行域名映射,因?yàn)閮烧叨际?Localhost 域名,而 Cookie 不區(qū)分端口,所以需要區(qū)分域名。


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


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


          用戶收到誘惑進(jìn)入了 hack 網(wǎng)站,hack 網(wǎng)站首頁有一個(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)然這是一次簡單的 GET 請(qǐng)求的攻擊,POST 請(qǐng)求攻擊可以通過自動(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. 阻止不同域的訪問
            1. 同源檢測(cè)。
            2. Samesite Cookie。
          2. 提交時(shí)要求附加本域才能獲取的信息。
            1. 添加 CSRF Token。
            2. 雙重 Cookie驗(yàn)證。

          同源檢測(cè) - 通過 Origin 和 Referer 確定來源域名

          針對(duì)第一個(gè)特點(diǎn)進(jìn)行域名檢查,HTTP 請(qǐng)求時(shí)會(huì)攜帶這兩個(gè) Header,用于標(biāo)記來源域名,如果請(qǐng)求來源不是本域,直接進(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:


          再次訪問 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,傳給用戶,用戶再次提交或者請(qǐng)求敏感操作時(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

          ??<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 所以攻擊失敗:


          以上為加深理解而寫的代碼,而在生產(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',?{?expires:?new?Date(Date.now()?+?900000)?})
          ??.cookie('csrfcookie',?randomString(),?{?expires:?new?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è)方法也是很有效的,比如請(qǐng)求庫 axios 就是用的這種方式。

          總結(jié)

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

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


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲欧视频在线播放 | 伊人69| 久久婷婷综合网 | 影音先锋日韩资源站 | 九九九九九九九九精品 |