论node.js的express中csrf的随机验证失败的bug
来源:互联网 发布:js ajax 跨域访问 编辑:程序博客网 时间:2024/05/26 09:56
背景:
在node.js中直接使用express搭建文档结构,如果需要防护CSRF,需要在app.js中,也就是Middleware配置中,需要加上:
app.use(express.csrf());
问题:
当我加载,ejs的view的时候,如果使用在前端使用ajax再次请求,会获取到新的_csrf值,这原本是没有什么问题的,但是确会随机出现:
Error: Forbidden at Object.exports.error (/Users/apple/Desktop/groupbuy/node_modules/express/node_modules/connect/lib/utils.js:62:13) at createToken (/Users/apple/Desktop/groupbuy/node_modules/express/node_modules/connect/lib/middleware/csrf.js:82:55) at Object.handle (/Users/apple/Desktop/groupbuy/node_modules/express/node_modules/connect/lib/middleware/csrf.js:48:24) at next (/Users/apple/Desktop/groupbuy/node_modules/express/node_modules/connect/lib/proto.js:190:15) at next (/Users/apple/Desktop/groupbuy/node_modules/express/node_modules/connect/lib/middleware/session.js:312:9) at /Users/apple/Desktop/groupbuy/node_modules/express/node_modules/connect/lib/middleware/session.js:336:9 at /Users/apple/Desktop/groupbuy/node_modules/express/node_modules/connect/lib/middleware/session/memory.js:50:9 at process._tickCallback (node.js:415:13)
这明显是说因为csrf防护被拦截了。这简直就是奇葩情况。。。
原因:
前端通过不断的访问,发现一个问题,如图:
上图说明,express提供的csrf模块有漏洞,“6gidhoxMfXsLCuzLxAH+zd6+di9pFr6CxQ9MU=”这个字符被判断为匹配不上。
解决过程:
首先为了确认具体情况,深入模块代码(位于:/express/node_modules/connect/lib/middleware/csrf.js)进行研究,发现这个csrf模块是通过checkToken(token,secret)方法并使用createToken(salt,secret)来进行比对,代码块如下:
function createToken(salt, secret) { return salt + crypto .createHash('sha1') .update(salt + secret) .digest('base64'); }function checkToken(token, secret) { return token === createToken(token.slice(0, 10), secret); }
为了验证问题,修改这个方法进行调试:
function checkToken(token, secret) { if ('string' != typeof token) return false; console.log("token:"+token); console.log("createToken(token.slice(0,10),secret):"+createToken(token.slice(0,10),secret)); console.log(token===createToken(token.slice(0,10),secret)); return token === createToken(token.slice(0, 10), secret); }
出现异常的结果为:
token:RLaNpX4nMPvsv2 gjZ Rhe911/dDo0VA4Sa4A=secret:BHNfrSL2QRTiK-DgylWwCYUbcreateToken(token.slice(0,10),secret):RLaNpX4nMPvsv2+gjZ+Rhe911/dDo0VA4Sa4A=false再次测试,正常为:
token:dyqRPL8L0ikahAeEMP2Wr/cNcD/hjPRUV0KlM=secret:BHNfrSL2QRTiK-DgylWwCYUbcreateToken(token.slice(0,10),secret):dyqRPL8L0ikahAeEMP2Wr/cNcD/hjPRUV0KlM=true
可以清楚的看到,应该是”+“号的位置变成了“ ”
问题原因找到了,那为什么会发生这种情况?
真正的原因:
继续观察模块源代码,csrf.js是通过defaultValue(req)方法来获取到请求中的_csrf值,那修改这部分代码进行验证:
function defaultValue(req) { console.log("body._csrf:"+req.body._csrf); console.log("query._csrf:"+req.query._csrf); return (req.body && req.body._csrf) || (req.query && req.query._csrf) || (req.headers['x-csrf-token']) || (req.headers['x-xsrf-token']); }
会出现两种明显的结果:
body._csrf:undefinedquery._csrf:5c8lTNnuTB85FF dVQFYsTpVA MmUgjhH7Yk=
body._csrf:7Ap38aDkBap+1kEvDguMM79/61ytKJYkDZVAY=query._csrf:undefined
这就证明,不知道是express还是node.js在对get方式的值里面的"+"字符解析不正常。
接着,继续寻找原因:
既然知道是query的原因,就到query.js里面去找原因,修改代码:
module.exports = function query(options){ return function query(req, res, next){ if (!req.query) { req.query = ~req.url.indexOf('?') ? qs.parse(parse(req).query, options) : {}; console.log("parse(req).query:"+parse(req).query); console.log("options:"+JSON.stringify(options)); console.log("qs.parse(parse(req).query,options):"+JSON.stringify(qs.parse(parse(req).query,options))); } console.log("req.query:"+JSON.stringify(req.query)); next(); }; };
运行结果为:
parse(req).query:_csrf=0MppzaqUwEvM1x6S+G8WFnMl96oQ99f+vVthM=options:undefinedqs.parse(parse(req).query,options):{"_csrf":"0MppzaqUwEvM1x6S G8WFnMl96oQ99f vVthM="}req.query:{"_csrf":"0MppzaqUwEvM1x6S G8WFnMl96oQ99f vVthM="}
最后发现,原来是一个叫qs模块(/express/node_modules/connect/node_modules/qs)的问题:
查看qu的源代码,由于传入的明显是string,所以应该是parseString(str)的问题:
进一步发现,这个方法内部调用了一个decode(str)方法:
function decode(str) { try { return decodeURIComponent(str.replace(/\+/g, ' ')); } catch (err) { return str; } }
终于发现了。。。奇怪,他为什么要换掉+号?难道是避免碰到拼接字符串?
解决方法:
我采取的方法是修改saltedToken(secret)方法:
function saltedToken(secret) { var token = createToken(generateSalt(10), secret); var re = new RegExp("\\+",["i"]); var m = re.exec(token); while(m!=null){ token = createToken(generateSalt(10), secret); m = re.exec(token); } return token; //return createToken(generateSalt(10), secret); }
注:如果在客户端把+号改为空格,通过get方式也会变成%字符,而且这个decode方法也不能屏蔽,毕竟这还是很危险的。
- 论node.js的express中csrf的随机验证失败的bug
- node.js+express验证码的实现
- node.js中express的应用
- Node.js的express框架
- node js 中express 所遇到的问题
- node.js中express模块使用的疑难问题解决
- Node.js的Express运行问题处理
- node.js-express框架的初步使用。
- [Node.js] express 安装的问题
- Node.js + express的安装,配置
- node.js安装express遇到的问题
- node.js+express+socket的聊天室示例
- node.js 安装express 遇到的问题
- node.js的express实现jsonp
- node.js express工程的结构
- node.js的express框架搭建
- node.js的Express框架起步
- Node.js 封装仿照 express 的路由
- Vim基础用法_初学篇
- 难得发现个好的免费空间,分享给大家了
- haoop rpc服务端读取数据包源码解析注释
- C#自己编写的Window服务安装成功后启动出现错误
- WF4.0 基础篇 (十二) CancellationScope 取消容器
- 论node.js的express中csrf的随机验证失败的bug
- ios 常用宏
- android有序广播和无序广播的区别
- Unity3D脚本中Start()和Awake()的区别
- POJ 2352数状数组(一维)
- 如何在mysql中实现update字段
- 在一个程序里调用另一个程序——关于一些控制台命令
- Linux环境下openocd调试s5pv210方法
- mysql怎么查看端口号