示例代码仓库地址
示例代码是通过nodejs express框架完成
模拟实现csrf(文件目录 csrf-example)
创建app 用于提供表单提交服务
mkdir app
cd app
npm init -y
npm install express pug --save
mkdir views
touch app.js views/layout.pug views/list.pug views/post.pug
app.js
const path = require('path');
const express = require('express');
const pug = require('pug');
const app = express();
let list = [
{title: '文章一'},
{title: '文章二'},
];
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', function(req, res, next) {
res.render('list', {
title: '文章列表',
list
})
});
app.get('/post', function(req, res, next) {
res.render('post', {
title: '新增文章'
});
});
app.post('/post', function(req, res, next) {
const title = req.body.title
if(title) {
list.push({title})
}
return res.redirect('/');
});
app.get('*', function (req, res) {
res.send('hello world!')
})
app.listen(4001, () => {
console.log('http://localhost:4001/')
})
views/layout.pug
doctype html
html
head
title= title
body
block content
views/list.pug
extends layout
block content
a(href="/post") 新增文章
each article in list
h1 #{article.title}
views/post.pug
extends layout
block content
form(action="/post" method="POST")
input(type="text" name="title" required placeholder="请输入文章标题")
button(type="submit") 提交
执行 node app.js 访问http://localhost:4001/ 效果如下
创建模拟攻击的服务
touch mockCsrf.js views/mockCsrf.pug
mockCsrf.js
const path = require('path');
const express = require('express');
const pug = require('pug');
const app = express();
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
app.get('*', function (req, res) {
res.render('mockCsrf', {title: 'mock csrf'});
})
app.listen(4002, () => {
console.log('mock csrf http://localhost:4002/')
})
views/mockCsrf.pug
extends layout
block content
h1 mock csrf
form#mock-form(action="http://localhost:4001/post" method="POST")
input(type="text" name="title" required placeholder="请输入文章标题" value="这是一篇通过跨站请求伪造发布的文章")
button(type="submit") 提交
script.
document.getElementById("mock-form").submit();
执行 node mockCsrf.js 访问http://localhost:4002/ 效果如下
通过检查Referer字段防范csrf(@authentication/csrf-protection)
在csrf-example基础上,安装@authentication/csrf-protection
npm install @authentication/csrf-protection --save
app.js
const path = require('path');
const express = require('express');
const pug = require('pug');
const csrfProtection = require('@authentication/csrf-protection')
const app = express();
let list = [
{title: '文章一'},
{title: '文章二'},
];
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
app.use(csrfProtection());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', function(req, res, next) {
res.render('list', {
title: '文章列表',
list
})
});
app.get('/post', function(req, res, next) {
res.render('post', {
title: '新增文章'
});
});
app.post('/post', function(req, res, next) {
const title = req.body.title
if(title) {
list.push({title})
}
return res.redirect('/');
});
app.get('*', function (req, res) {
res.send('hello world!')
})
app.listen(4001, () => {
console.log('http://localhost:4001/')
})
重启服务
npm start
npm run start-mock-csrf
再次访问csrf攻击服务 http://localhost:4002/ 效果如下:
至此通过检查Referer字段的防范csrf实现完成。
通过令牌模式防范csrf
在csrf-example基础上,安装依赖
npm i csurf csrf cookie-parser --save
在实现令牌同步模式之前,先了解下package csrf,因为express中间件csurf使用了此库。应用至少保证每个用户的csrfSecret不同。
const Tokens = require('csrf');
const tokens = new Tokens();
// 创建密钥,异步生成的方式:tokens.secret(callback)
const csrfSecret = tokens.secretSync();
// 创建令牌
const some_user_token = tokens.create(csrfSecret);
// 验证令牌
if (!tokens.verify(csrfSecret, some_user_token)) {
throw new Error('invalid token!')
} else {
console.log('验证通过');
}
修改views/post.pug内容如下
extends layout
block content
form(action="/post" method="POST")
input(type="hidden" name="_csrf" value= csrfToken)
input(type="text" name="title" required placeholder="请输入文章标题")
button(type="submit") 提交
修改app.js内容如下
const path = require('path');
const express = require('express');
const pug = require('pug');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true })
const app = express();
let list = [
{title: '文章一'},
{title: '文章二'},
];
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
app.use(cookieParser())
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', function(req, res, next) {
res.render('list', {
title: '文章列表',
list
})
});
app.get('/post', csrfProtection, function(req, res, next) {
res.render('post', {
title: '新增文章',
csrfToken: req.csrfToken()
});
});
app.post('/post', csrfProtection, function(req, res, next) {
const title = req.body.title
if(title) {
list.push({title})
}
return res.redirect('/');
});
app.get('*', function (req, res) {
res.send('hello world!')
})
app.listen(4001, () => {
console.log('http://localhost:4001/')
})
再次运行 node app.js 和 node mockCsrf.js
访问 http://localhost:4001 可以正常添加文章
但是再访问http://localhost:4002 试图跨站伪造攻击时的结果如下
ForbiddenError: invalid csrf token
其它比如ajax,或者单页APP传token的用法等请参考:github.com/expressjs/c…