上一篇文章中介紹了XSS,這篇文章介紹CSRFjavascript
Cross-site request forgery, 跨站請求僞造。是指黑客引誘用戶打開黑客的網站,在黑客的網站中,利用用戶的登陸狀態發起跨站請求。html
我本身模擬了一個例子,java
在這個頁面內,我會自動向服務器發起一個get請求,服務器響應這個請求的同時會向瀏覽器發送cookieexpress
在這個頁面內,作了三個事情segmentfault
經過截圖能夠看到,三個請求都帶了www.abc.com下的cookie發送給了localhost:8899服務器,而且localhost:8899服務器也正常響應了。
流程圖以下:跨域
黑客發起CSRF攻擊的條件瀏覽器
CSRF攻擊與XSS攻擊不一樣,CSRF攻擊不會往頁面內注入惡意腳本,所以黑客是沒法經過CSRF攻擊來獲取用戶頁面數據的,因此主要由服務器來作預防。
主要有如下幾種方式:安全
SameSite選項一般由Strict、Lax和None三個值服務器
可是如今大部分的網站靜態資源都放在單獨的域名下,因此經過設置Cookie的SameSite爲Strict、Lax是不能正常運行的,因此這個方法只適用靜態資源跟服務器接口在同一個站點下的網站。
我測試了一下,以下圖:
localhost:8899向www.abc.com寫入SameSite值爲Lax的cookie,寫不進去cookie
locahost:8899向該站點下的localhost:8899/get寫入SameSite值爲Lax的cookie,成功寫入。
在服務器端驗證請求的來源站點。由於CSRF攻擊大多數都是來自第三方站點。
經過http請求頭中的Referer和Origin屬性
記錄了該http請求的來源地址,但有些場景不適合未來源URL暴露給服務器,因此能夠設置不用上傳,而且referer屬性是能夠修改的,因此在服務器端校驗referer屬性並無那麼可靠
經過XMLHttpRequest、Fetch發起的跨站請求或者Post方法發送請求時,都會帶上origin,因此服務器能夠優先判斷Origin屬性,再根據實際狀況判斷是否使用referer判斷。
除了上面兩個方法以外,還可使用CSRF Token來驗證。
代碼很簡單
<div> <h6>CSRF測試</h2> <a href="http://www.haha.com:9999">點我點我</a> <script> fetch('http://localhost:8899', { method: 'get', mode: 'cors', credentials: 'include' //很重要,容許跨域訪問傳輸cookie }).then((res) => { console.log(res) }) </script> </div>
const express = require('express') const app = express() const port = 8899 //allow custom header and CORS app.all('*',function (req, res, next) { // res.header('Access-Control-Allow-Origin', '*'); // res.header('Access-Control-Allow-Origin', req.hostname); res.header('Access-Control-Allow-Origin', req.headers.origin); res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); res.header('Access-Control-Allow-Credentials', true); // 很重要,容許跨域訪問傳輸cookie if (req.method == 'OPTIONS') { res.send(200); /讓options請求快速返回/ } else { next(); } }); app.get('/', (req, res) => { // res.cookie('name', 'hey', { domain: req.hostname, path: '/'}); res.cookie('name', 'hey', { domain: req.hostname, path: '/', sameSite: 'None'}); // res.cookie('name', 'hey', { secure: true }); res.send('Hello World!a') }) app.get('/get/test', (req, res) => { res.cookie('username', 'hahah', { domain: req.hostname, path: '/', sameSite: 'Lax'}); // res.cookie('name', 'hey', { secure: true }); res.send('Hello World!a') }) app.get('/get', (req, res) => { res.set('Content-Type', 'text/html') console.log(req.headers, 'header') const html = ` <div> <div>服務器同站點下的頁面</div> <script> function fetchUrl(url, method='get') { fetch(url, { method, mode: 'cors', credentials: 'include' //很重要,容許跨域訪問傳輸cookie }).then((res) => { console.log(res) }) } fetchUrl('http://localhost:8899/get/test') </script> </div> ` res.send(html) }) app.post('/post', (req, res) => { console.log(req.headers, 'header') // res.cookie('name', 'hey', { secure: true }); res.send('I am post') }) app.listen(port, () => console.log(`Example app listening on port ${port}!`))
const express = require('express') const app = express() const port = 9999 //allow custom header and CORS app.all('*',function (req, res, next) { // res.header('Access-Control-Allow-Origin', '*'); // res.header('Access-Control-Allow-Origin', req.hostname); console.log(req.headers.origin) res.header('Access-Control-Allow-Origin', req.headers.origin); res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); res.header('Access-Control-Allow-Credentials', true); // 很重要,容許跨域訪問傳輸cookie if (req.method == 'OPTIONS') { res.send(200); /讓options請求快速返回/ } else { next(); } }); app.get('/', (req, res) => { res.set('Content-Type', 'text/html') console.log(req.headers, 'header') const html = ` <div> <div>nihao</div> <img src="http://localhost:8899" /> <a href="javascript: fetchUrl('http://localhost:8899/post', 'post');">你好,交個朋友吧</a> <script> function fetchUrl(url, method='get') { fetch(url, { method, mode: 'cors', credentials: 'include' //很重要,容許跨域訪問傳輸cookie }).then((res) => { console.log(res) }) } fetchUrl('http://localhost:8899/post', 'post') </script> </div> ` res.send(html) }) app.listen(port, () => console.log(`Example app listening on port ${port}!`))
以前雖然知道CSRF的原理,可是沒有實際模擬過,實際模擬以後,感受,原來真的那麼簡單。也算是一個小小收穫
歡迎跟我一塊兒挖坑、填坑。