国产精品爱久久久久久久小说,女人扒开腿让男人桶到爽 ,亚洲欧美国产双大乳头,国产成人精品综合久久久久,国产精品制服丝袜无码,免费无码精品黄av电影,黑色丝袜无码中中文字幕,乱熟女高潮一区二区在线

            Web安全之CSRF實(shí)例解析

            2020-5-11    seo達(dá)人

            前言

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


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


            CSRF

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


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


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

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

            CSRF攻擊


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


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

            實(shí)現(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。


            平時(shí)自己寫例子中會(huì)用到下面這兩個(gè)工具,非常方便好用:

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

            nodemon: nodemon是一種工具,通過在檢測(cè)到目錄中的文件更改時(shí)自動(dòng)重新啟動(dòng)節(jié)點(diǎn)應(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">

                           聽說點(diǎn)擊這個(gè)鏈接的人都賺大錢了,你還不來看一下么

                       </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ìn)入判斷是否已經(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';

                   }

                   

               })

               // 點(diǎn)擊登陸

               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;

                   })

               }

               // 點(diǎn)擊支付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;

                   })

               }

               // 封裝的請(qǐng)求方法

               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>

            實(shí)現(xiàn)一個(gè)簡(jiǎn)單的支付功能:


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

            登陸完成之后,會(huì)展示轉(zhuǎn)賬信息,點(diǎn)擊支付,可以實(shí)現(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  使得前后端都在同一個(gè)服務(wù)下

            app.use(KoaStatic(__dirname));


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


            // 初始金額為 1000

            let money = 1000;


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

            const login = ctx => {

               const req = ctx.request.body;

               const userName = req.userName;

               currentUserName = userName;

               // 簡(jiǎn)單設(shè)置一個(gè)cookie

               ctx.cookies.set(

                   'name',

                   userName,

                   {

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

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

                     maxAge: 10 * 60 * 1000, // cookie有效時(shí)長(zhǎng)

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

                     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 來簡(jiǎn)單判斷是否登錄

               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 請(qǐng)求

            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('啟動(dòng)成功');

            });

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


            CSRF-demo


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


            第三方頁面 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>

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

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

                   <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 起一個(gè) 本地端口為 3100的服務(wù),就可以通過 http://127.0.0.1:3100/bad.html 這個(gè)鏈接來訪問,CSRF攻擊需要做的就是在正常的頁面上誘導(dǎo)用戶點(diǎn)擊鏈接進(jìn)入這個(gè)頁面

            CSRF-DEMO


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

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


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


            自動(dòng)發(fā)起Get請(qǐng)求

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


            <!DOCTYPE html>

            <html>

             <body>

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

             </body>

            </html>

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


            自動(dòng)發(fā)起POST請(qǐng)求

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


            <body>

               <div>

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

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

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


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

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


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

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


            如何防御CSRF

            利用cookie的SameSite

            SameSite有3個(gè)值: Strict, Lax和None


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

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

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

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


            驗(yàn)證請(qǐng)求的來源點(diǎn)

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

            可以通過HTTP請(qǐng)求頭中的 Referer和Origin屬性。


            HTTP請(qǐng)求頭


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


            CSRF Token

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

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

            其他知識(shí)點(diǎn)補(bǔ)充

            1. 第三方cookie

            Cookie是種在服務(wù)端的域名下的,比如客戶端域名是 a.com,服務(wù)端的域名是 b.com, Cookie是種在 b.com域名下的,在 Chrome的 Application下是看到的是 a.com下面的Cookie,是沒有的,之后,在a.com下發(fā)送b.com的接口請(qǐng)求會(huì)自動(dòng)帶上Cookie(因?yàn)镃ookie是種在b.com下的)


            2. 簡(jiǎn)單請(qǐng)求和復(fù)雜請(qǐng)求

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


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


            3. Fetch的 credentials 參數(shù)

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


            credential的參數(shù)如下


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

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

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

            平常寫一些簡(jiǎn)單的例子,從很多細(xì)節(jié)問題上也能補(bǔ)充自己的一些知識(shí)盲點(diǎn)。

            日歷

            鏈接

            個(gè)人資料

            存檔

            主站蜘蛛池模板: 久久人人爽人人爽人人av东京热| 精品国产av一区二区| 熟妇人妻无码xxx视频| 丰满熟女高潮毛茸茸欧洲视频| 91传媒理伦片在线观看| 人妻饥渴偷公乱中文字幕| 收集最新中文国产中文字幕| 久久久久久久久久久av| 无码高潮少妇毛多水多水| 久久精品国产99精品最新| 亚洲性生活| 国产精品亚洲二区亚瑟| 日本边添边摸边做边爱喷水| 性久久久久| 免费荫蒂添的好舒服视频| 内射爽无广熟女亚洲| 成人精品av一区二区三区网站| 午夜精品成人| 揉着我的奶从后面进去视频| 2018亚洲а∨天堂| 中文资源在线观看| 疯狂撞击丝袜人妻| 亚洲香蕉中文日韩v日本| 麻豆专区| 欧美中文字幕一区| 国产精品久久无码一区| 男人激烈吮乳吃奶视频免费| 污黄啪啪网| 欧美熟妇xxxxx欧美老妇不卡| 亚洲中文字幕aⅴ天堂自拍| 国产精品扒开腿做爽爽爽视频| 成人av影院在线观看| 毛片免费视频| 卡一卡二卡三免费视频| 热久久久久久久| 欧美伊人| 日本亚洲国产一区二区三区| 黄色裸体视频| 在线免费观看a级片| 中国熟妇人妻xxxxx| 国产精品乱子伦xxxx裸|