我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
废话写在前面
最近在写一个新项目,主要做管理后台的。本来按照以往的项目架构设计,后台必须得是java大佬去撑场,我们前端做做页面,接接口渲染下数据就完事了。但是作为一个有“理想”的前端,耐不住自己要作死的心,打包票,直接前端一手包揽了。本次项目后台采用了nodejs+koa2+mongoose 框架结构进行设计开发。
在愉快敲了一个月代码后,给领导演示后,领导觉得可以先上线,把项目跑起来,后续再继续迭代。激动的心,颤抖的手,我的项目要上线了🎉 🎉🎉。提交测试,提交安全部门做渗透,上线时间都安排好了,晴天霹雳来了,安全报告给我一个大大的巴掌👋。有一个高危和一个中危漏洞,安全部门只给了两天修复时间,过时不侯,不允许上线。😮💨哎,冷静下,还是想想怎么解决吧。这次主要重点讲讲高危漏洞的解决。
安全漏洞分析
本次渗透测试出现的高危漏洞是存在越权漏洞,这类漏洞是指应用在检查授权(Authorization)时存在纰漏,使得攻击者在获得低权限用户帐后后,可以利用一些方式绕过权限检查,访问或者操作到原本无权访问的高权限功能。这次漏洞就是安全人员通过先登陆管理员账户,拿到账户uid,然后再登陆测试账户,将发送包里的uid改为管理员uid,从而获取到管理员的权限。在项目中,鉴权只做了token登陆态健全,没有做统一的权限鉴权。虽然在post接口内做了单独的权限校验,但是忽略了有些get请求的权限鉴权。这块的忽视,引发了这个漏洞。自己挖坑自己填。
原先的jwt鉴权
项目中登陆态鉴权 我们使用jsonwebtoken中间件。JWT(Json Web Token)是实现token技术的一种解决方案,JWT由三部分组成: header(头)、payload(载体)、signature(签名)。使用方式很简单:
npm i jsonwebtoken安装jwt- 需要指定一个密钥(secret),可以保存在服务端配置文件里
- 引入并注册一个token签名
const jwt = require("jsonwebtoken")
const token = jwt.sign({
uid: uid,
...
}, SECRET, { expiresIn: "2h" })
这样子就生成一个token登陆态了。在登陆后下发给前端,在需要验证的接口头部里带上token
验证我们使用koa-jwt中间件验证,在app.js文件里,使用这个中间件app.use(koajwt({ secret: JWT_SECRET }) 他会自动校验header里authorization的字段。这样子就完成了登录态的校验。
现在的jwt与用户鉴权
现在为了防止越权访问,我们需要将jwt的token利用起来,在app.js里我们写了个中间件,计划拦截所有带有token的接口。
app.use(async (ctx, next) => {
if(ctx.request.get("token")){
const verifydata = ctx.method=="POST" ? ctx.request.body : ctx.request.query
const uid = verifydata.uid || ctx.request.get("uid")
const tokenstatus = await tokenVerfiy(ctx.request.get("token"),uid)
if(!tokenstatus){
ctx.body = '';
ctx.status = 403;
return
}
})
接下来编写tokenVerfiy校验函数
const jwt = require('jsonwebtoken');
const {JWT_SECRET} = require('../config')
const tokenVerfiy = async (token,uid)=>{
if(!uid){return false}
try {
// 解密token,提出内容体
const decoded = jwt.verify(token,JWT_SECRET)
// 后台接口设计默认uid字段是当前用户id字段,固定用户字段
if(uid && uid != decoded.uid){ // 如果存在用户参数id
console.error("token鉴权失败,用户ID与token内ID不匹配")
return false
}
if(xx){ // 一些其他的敏感校验
// do something
}
return true
} catch (error) {
console.log("token鉴权失败",error)
return false
}
}
module.exports = tokenVerfiy
这样,每一个带有token的请求,都会执行以上的函数。通过获取到接口body里的uid或者header头部里uid的字段,然后与在登陆后jwt生成token后解密出来的内容体中信息进行匹配。这样子初步可以进行登陆鉴权与用户鉴权相互结合使用,让这个漏洞打上了个补丁。注意:在实际项目中必须要对jwt.verify进行try catch捕捉错误,因为如果token过期或者无效会直接抛出错误。
写在最后
可以交差了😄。在解决完所有的漏洞后,怀着忐忑的心,再次提交安全测试,幸运的是这次没问题,如期上线。这次漏洞也让我警醒,安全无小事,不要因为懒而不做安全防护。毕竟,你现在不做,后面就得花更多的时间去补救。