粗大的内捧猛烈进出小视频,日本成人精品视频一区,在线播放亚洲成人av,精品人妻少妇嫩草av专区,亚洲AV永久久久久久久浪潮,性导航app精品视频,九九热精品免费视频,一本一本大道香蕉久在线播放

        Web安全之CSRF實例解析

        2020-5-11    seo達人

        前言

        文章首次發(fā)表在 個人博客


        之前寫過一篇 web安全之XSS實例解析,是通過舉的幾個簡單例子講解的,同樣通過簡單得例子來理解和學(xué)習(xí)CSRF,有小伙伴問實際開發(fā)中有沒有遇到過XSS和CSRF,答案是有遇到過,不過被測試同學(xué)發(fā)現(xiàn)了,還有安全掃描發(fā)現(xiàn)了可能的問題,這兩篇文章就是簡化了一下當(dāng)時實際遇到的問題。


        CSRF

        跨站請求偽造(Cross Site Request Forgery),是指黑客誘導(dǎo)用戶打開黑客的網(wǎng)站,在黑客的網(wǎng)站中,利用用戶的登陸狀態(tài)發(fā)起的跨站請求。CSRF攻擊就是利用了用戶的登陸狀態(tài),并通過第三方的站點來做一個壞事。


        要完成一次CSRF攻擊,受害者依次完成兩個步驟:


        登錄受信任網(wǎng)站A,并在本地生成Cookie

        在不登出A的情況,訪問危險網(wǎng)站B

        CSRF攻擊


        在a.com登陸后種下cookie, 然后有個支付的頁面,支付頁面有個誘導(dǎo)點擊的按鈕或者圖片,第三方網(wǎng)站域名為 b.com,中的頁面請求 a.com的接口,b.com 其實拿不到cookie,請求 a.com會把Cookie自動帶上(因為Cookie種在 a.com域下)。這就是為什么在服務(wù)端要判斷請求的來源,及限制跨域(只允許信任的域名訪問),然后除了這些還有一些方法來防止 CSRF 攻擊,下面會通過幾個簡單的例子來詳細介紹 CSRF 攻擊的表現(xiàn)及如何防御。


        下面會通過一個例子來講解 CSRF 攻擊的表現(xiàn)是什么樣子的。

        實現(xiàn)的例子:

        在前后端同域的情況下,前后端的域名都為 http://127.0.0.1:3200, 第三方網(wǎng)站的域名為 http://127.0.0.1:3100,釣魚網(wǎng)站頁面為 http://127.0.0.1:3100/bad.html。


        平時自己寫例子中會用到下面這兩個工具,非常方便好用:

        http-server: 是基于node.js的HTTP 服務(wù)器,它最大的好處就是:可以使用任意一個目錄成為服務(wù)器的目錄,完全拋開后端的沉重工程,直接運行想要的js代碼;

        nodemon: nodemon是一種工具,通過在檢測到目錄中的文件更改時自動重新啟動節(jié)點應(yīng)用程序來幫助開發(fā)基于node.js的應(yīng)用程序

        前端頁面: client.html


        <!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>CSRF-demo</title>

           <style>

               .wrap {

                   height: 500px;

                   width: 300px;

                   border: 1px solid #ccc;

                   padding: 20px;

                   margin-bottom: 20px;

               }

               input {

                   width: 300px;

               }

               .payInfo {

                   display: none;

               }

               .money {

                   font-size: 16px;

               }

           </style>

        </head>


        <body>

           <div class="wrap">

               <div class="loginInfo">

                   <h3>登陸</h3>

                   <input type="text" placeholder="用戶名" class="userName">

                   <br>

                   <input type="password" placeholder="密碼" class="password">

                   <br>

                   <br>

                   <button class="btn">登陸</button>

               </div>

               

               

               <div class="payInfo">

                   <h3>轉(zhuǎn)賬信息</h3>

                   <p >當(dāng)前賬戶余額為 <span class="money">0</span>元</p>

                   <!-- <input type="text" placeholder="收款方" class="account"> -->

                   <button class="pay">支付10元</button>

                   <br>

                   <br>

                   <a target="_blank">

                       聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么

                   </a>

               </div>

           </div>

        </body>

        <script>

           const btn = document.querySelector('.btn');

           const loginInfo = document.querySelector('.loginInfo');

           const payInfo = document.querySelector('.payInfo');

           const money = document.querySelector('.money');

           let currentName = '';

           // 第一次進入判斷是否已經(jīng)登陸

           Fetch('http://127.0.0.1:3200/isLogin', 'POST', {})

           .then((res) => {

               if(res.data) {

                   payInfo.style.display = "block"

                   loginInfo.style.display = 'none';

                   Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 0})

                   .then((res) => {

                       money.innerHTML = res.data.money;

                   })

               } else {

                   payInfo.style.display = "none"

                   loginInfo.style.display = 'block';

               }

               

           })

           // 點擊登陸

           btn.onclick = function () {

               var userName = document.querySelector('.userName').value;

               currentName = userName;

               var password = document.querySelector('.password').value;

               Fetch('http://127.0.0.1:3200/login', 'POST', {userName, password})

               .then((res) => {

                   payInfo.style.display = "block";

                   loginInfo.style.display = 'none';

                   money.innerHTML = res.data.money;

               })

           }

           // 點擊支付10元

           const pay = document.querySelector('.pay');

           pay.onclick = function () {

               Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 10})

               .then((res) => {

                   console.log(res);

                   money.innerHTML = res.data.money;

               })

           }

           // 封裝的請求方法

           function Fetch(url, method = 'POST', data) {

               return new Promise((resolve, reject) => {

                   let options = {};

                   if (method !== 'GET') {

                       options = {

                           headers: {

                               'Content-Type': 'application/json',

                           },

                           body: JSON.stringify(data),

                       }

                   }

                   fetch(url, {

                       mode: 'cors', // no-cors, cors, *same-origin

                       method,

                       ...options,

                       credentials: 'include',

                   }).then((res) => {

                       return res.json();

                   }).then(res => {

                       resolve(res);

                   }).catch(err => {

                       reject(err);

                   });

               })

           }

           

        </script>


        </html>

        實現(xiàn)一個簡單的支付功能:


        會首先判斷有沒有登錄,如果已經(jīng)登陸過,就直接展示轉(zhuǎn)賬信息,未登錄,展示登陸信息

        登陸完成之后,會展示轉(zhuǎn)賬信息,點擊支付,可以實現(xiàn)金額的扣減

        后端服務(wù): server.js


        const Koa = require("koa");

        const app = new Koa();

        const route = require('koa-route');

        const bodyParser = require('koa-bodyparser');

        const cors = require('@koa/cors');

        const KoaStatic = require('koa-static');


        let currentUserName = '';


        // 使用  koa-static  使得前后端都在同一個服務(wù)下

        app.use(KoaStatic(__dirname));


        app.use(bodyParser()); // 處理post請求的參數(shù)


        // 初始金額為 1000

        let money = 1000;


        // 調(diào)用登陸的接口

        const login = ctx => {

           const req = ctx.request.body;

           const userName = req.userName;

           currentUserName = userName;

           // 簡單設(shè)置一個cookie

           ctx.cookies.set(

               'name',

               userName,

               {

                 domain: '127.0.0.1', // 寫cookie所在的域名

                 path: '/',       // 寫cookie所在的路徑

                 maxAge: 10 * 60 * 1000, // cookie有效時長

                 expires: new Date('2021-02-15'),  // cookie失效時間

                 overwrite: false,  // 是否允許重寫

                 SameSite: 'None',

               }

             )

           ctx.response.body = {

               data: {

                   money,

               },

               msg: '登陸成功'

           };

        }

        // 調(diào)用支付的接口

        const pay = ctx => {

           if(ctx.method === 'GET') {

               money = money - Number(ctx.request.query.money);

           } else {

               money = money - Number(ctx.request.body.money);

           }

           ctx.set('Access-Control-Allow-Credentials', 'true');

           // 根據(jù)有沒有 cookie 來簡單判斷是否登錄

           if(ctx.cookies.get('name')){

               ctx.response.body = {

                   data: {

                       money: money,

                   },

                   msg: '支付成功'

               };

           }else{

               ctx.body = '未登錄';

           }

        }


        // 判斷是否登陸

        const isLogin = ctx => {

           ctx.set('Access-Control-Allow-Credentials', 'true');


           if(ctx.cookies.get('name')){

               ctx.response.body = {

                   data: true,

                   msg: '登陸成功'

               };


           }else{

               ctx.response.body = {

                   data: false,

                   msg: '未登錄'

               };

           }

        }

        // 處理 options 請求

        app.use((ctx, next)=> {

           const headers = ctx.request.headers;

           if(ctx.method === 'OPTIONS') {

               ctx.set('Access-Control-Allow-Origin', headers.origin);

               ctx.set('Access-Control-Allow-Headers', 'Content-Type');

               ctx.set('Access-Control-Allow-Credentials', 'true');

               ctx.status = 204;

           } else {

               next();

           }

        })


        app.use(cors());

        app.use(route.post('/login', login));

        app.use(route.post('/pay', pay));

        app.use(route.get('/pay', pay));

        app.use(route.post('/isLogin', isLogin));


        app.listen(3200, () => {

           console.log('啟動成功');

        });

        執(zhí)行 nodemon server.js,訪問頁面 http://127.0.0.1:3200/client.html


        CSRF-demo


        登陸完成之后,可以看到Cookie是種到 http://127.0.0.1:3200 這個域下面的。


        第三方頁面 bad.html


        <!DOCTYPE html>

        <html lang="en">

        <head>

           <meta charset="UTF-8">

           <meta name="viewport" content="width=device-width, initial-scale=1.0">

           <title>第三方網(wǎng)站</title>

        </head>

        <body>

           <div>

               哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!

               <!-- form 表單的提交會伴隨著跳轉(zhuǎn)到action中指定 的url 鏈接,為了阻止這一行為,可以通過設(shè)置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當(dāng)前頁面iframe則不會刷新頁面 -->

               <form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr" style="display: none">

                   <input type="text" name="userName" value="xiaoming">

                   <input type="text" name="money" value="100">

               </form>

               <iframe name="targetIfr" style="display:none"></iframe>

           </div>

        </body>

        <script>

           document.querySelector('.form').submit();

        </script>

        </html>

        使用 HTTP-server 起一個 本地端口為 3100的服務(wù),就可以通過 http://127.0.0.1:3100/bad.html 這個鏈接來訪問,CSRF攻擊需要做的就是在正常的頁面上誘導(dǎo)用戶點擊鏈接進入這個頁面

        CSRF-DEMO


        點擊誘導(dǎo)鏈接,跳轉(zhuǎn)到第三方的頁面,第三方頁面自動發(fā)了一個扣款的請求,所以在回到正常頁面的時候,刷新,發(fā)現(xiàn)錢變少了。

        我們可以看到在第三方頁面調(diào)用 http://127.0.0.1:3200/pay 這個接口的時候,Cookie自動加在了請求頭上,這就是為什么 http://127.0.0.1:3100/bad.html 這個頁面拿不到 Cookie,但是卻能正常請求 http://127.0.0.1:3200/pay 這個接口的原因。


        CSRF攻擊大致可以分為三種情況,自動發(fā)起Get請求, 自動發(fā)起POST請求,引導(dǎo)用戶點擊鏈接。下面會分別對上面例子進行簡單的改造來說明這三種情況


        自動發(fā)起Get請求

        在上面的 bad.html中,我們把代碼改成下面這樣


        <!DOCTYPE html>

        <html>

         <body>

           <img src="http://127.0.0.1:3200/payMoney?money=1000">

         </body>

        </html>

        當(dāng)用戶訪問含有這個img的頁面后,瀏覽器會自動向自動發(fā)起 img 的資源請求,如果服務(wù)器沒有對該請求做判斷的話,那么會認為這是一個正常的鏈接。


        自動發(fā)起POST請求

        上面例子中演示的就是這種情況。


        <body>

           <div>

               哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!

               <!-- form 表單的提交會伴隨著跳轉(zhuǎn)到action中指定 的url 鏈接,為了阻止這一行為,可以通過設(shè)置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當(dāng)前頁面iframe則不會刷新頁面 -->

               <form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr">

                   <input type="text" name="userName" value="xiaoming">

                   <input type="text" name="money" value="100">

               </form>

               <iframe name="targetIfr" style="display:none"></iframe>

           </div>

        </body>

        <script>

           document.querySelector('.form').submit();

        </script>

        上面這段代碼中構(gòu)建了一個隱藏的表單,表單的內(nèi)容就是自動發(fā)起支付的接口請求。當(dāng)用戶打開該頁面時,這個表單會被自動執(zhí)行提交。當(dāng)表單被提交之后,服務(wù)器就會執(zhí)行轉(zhuǎn)賬操作。因此使用構(gòu)建自動提交表單這種方式,就可以自動實現(xiàn)跨站點 POST 數(shù)據(jù)提交。


        引導(dǎo)用戶點擊鏈接

        誘惑用戶點擊鏈接跳轉(zhuǎn)到黑客自己的網(wǎng)站,示例代碼如圖所示


        <a >聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么</a>

        用戶點擊這個地址就會跳到黑客的網(wǎng)站,黑客的網(wǎng)站可能會自動發(fā)送一些請求,比如上面提到的自動發(fā)起Get或Post請求。


        如何防御CSRF

        利用cookie的SameSite

        SameSite有3個值: Strict, Lax和None


        Strict。瀏覽器會完全禁止第三方cookie。比如a.com的頁面中訪問 b.com 的資源,那么a.com中的cookie不會被發(fā)送到 b.com服務(wù)器,只有從b.com的站點去請求b.com的資源,才會帶上這些Cookie

        Lax。相對寬松一些,在跨站點的情況下,從第三方站點鏈接打開和從第三方站點提交 Get方式的表單這兩種方式都會攜帶Cookie。但如果在第三方站點中使用POST方法或者通過 img、Iframe等標(biāo)簽加載的URL,這些場景都不會攜帶Cookie。

        None。任何情況下都會發(fā)送 Cookie數(shù)據(jù)

        我們可以根據(jù)實際情況將一些關(guān)鍵的Cookie設(shè)置 Stirct或者 Lax模式,這樣在跨站點請求的時候,這些關(guān)鍵的Cookie就不會被發(fā)送到服務(wù)器,從而使得CSRF攻擊失敗。


        驗證請求的來源點

        由于CSRF攻擊大多來自第三方站點,可以在服務(wù)器端驗證請求來源的站點,禁止第三方站點的請求。

        可以通過HTTP請求頭中的 Referer和Origin屬性。


        HTTP請求頭


        但是這種 Referer和Origin屬性是可以被偽造的,碰上黑客高手,這種判斷就是不安全的了。


        CSRF Token

        最開始瀏覽器向服務(wù)器發(fā)起請求時,服務(wù)器生成一個CSRF Token。CSRF Token其實就是服務(wù)器生成的字符串,然后將該字符串種植到返回的頁面中(可以通過Cookie)

        瀏覽器之后再發(fā)起請求的時候,需要帶上頁面中的 CSRF Token(在request中要帶上之前獲取到的Token,比如 x-csrf-token:xxxx), 然后服務(wù)器會驗證該Token是否合法。第三方網(wǎng)站發(fā)出去的請求是無法獲取到 CSRF Token的值的。

        其他知識點補充

        1. 第三方cookie

        Cookie是種在服務(wù)端的域名下的,比如客戶端域名是 a.com,服務(wù)端的域名是 b.com, Cookie是種在 b.com域名下的,在 Chrome的 Application下是看到的是 a.com下面的Cookie,是沒有的,之后,在a.com下發(fā)送b.com的接口請求會自動帶上Cookie(因為Cookie是種在b.com下的)


        2. 簡單請求和復(fù)雜請求

        復(fù)雜請求需要處理option請求。


        之前寫過一篇特別詳細的文章 CORS原理及@koa/cors源碼解析,有空可以看一下。


        3. Fetch的 credentials 參數(shù)

        如果沒有配置credential 這個參數(shù),fetch是不會發(fā)送Cookie的


        credential的參數(shù)如下


        include:不論是不是跨域的請求,總是發(fā)送請求資源域在本地的Cookies、HTTP Basic anthentication等驗證信息

        same-origin:只有當(dāng)URL與響應(yīng)腳本同源才發(fā)送 cookies、 HTTP Basic authentication 等驗證信息

        omit: 從不發(fā)送cookies.

        平常寫一些簡單的例子,從很多細節(jié)問題上也能補充自己的一些知識盲點。

        日歷

        鏈接

        個人資料

        藍藍設(shè)計的小編 http://www.xintaizi.com

        存檔