CTFHub_2020-数字中国创新大赛虎符网络安全赛道-Web-easy_login(源码泄露、JWT-None攻击)
场景描述:最近正在开始学习nodejs开发,不如先写个登陆界面练练手。什么,大佬说我的程序有bug?我写的代码逻辑完美顺利运行怎么可能出错?!错的一定是我的依赖库!!
进入场景,显示是一个登录框
注册账号,登录,发现get flag按钮,点击提示permission denied
,无权限,那么此题的方向应该是伪造成一个高权限账户。
截取登录包,发现可疑authorization校验字段
解码显示为jwt
通过查看源码,发现/static/js/app.js 页面存在提示
/** * 或许该用 koa-static 来处理静态文件 * 路径该怎么配置?不管了先填个根目录XD */
koa-static 错误配置的源码泄露
说明 app.js 是直接静态映射到程序根目录的,直接访问根目录的该文件可直接看到源码
继续分析根目录的app.js
,发现代码引用了两个当前目录的文件
const rest = require('./rest');
const controller = require('./controller');
说明存在rest.js和controller.js文件
访问rest.js
发现同样一个路径前缀 api
const pathPrefix = '/api/';
访问controller.js
看到下面的代码
遍历在controllers文件夹下的以.js结尾的文件,并且引入文件添加在router中,推断controllers文件夹下存在一个api.js文件
function addControllers(router, dir) { fs.readdirSync(__dirname + '/' + dir).filter(f => { return f.endsWith('.js'); }).forEach(f => { const mapping = require(__dirname + '/' + dir + '/' + f); addMapping(router, mapping); }); } module.exports = (dir) => { const controllers_dir = dir || 'controllers'; const router = require('koa-router')(); addControllers(router, controllers_dir); return router.routes(); };
访问/controllers/api.js,
前端几个能看到的功能接口逻辑都在了,分析登录和注册接口
注册: const secret = crypto.randomBytes(18).toString('hex'); const secretid = global.secrets.length; global.secrets.push(secret) const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'}); 登录: const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization; const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid; console.log(sid) if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) { throw new APIError('login error', 'no such secret id'); } const secret = global.secrets[sid]; const user = jwt.verify(token, secret, {algorithm: 'HS256'});
1、确认用户身份的技术用的是jwt (Json WEB Token)
在注册时候生成一个token 由下面三部分组成const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
登陆时再以相同的方式生成token对比。
JWT 存在几种攻击手段,这个题利用的是 将加密方式改为’none’ 的那种
签名算法确保恶意用户在传输过程中不会修改JWT。但是标题中的alg字段可以更改为none。一些JWT库支持无算法,即没有签名算法。当alg为none时,后端将不执行签名验证。将alg更改为none后,从JWT中删除签名数据(仅标题+’.'+ payload +’.')并将其提交给服务器。
2、secretid值校验
要求 sid 不能为 undefined,null,并且必须在全局变量 secrets 数组的长度和 0 之间。
JavaScript 是一门弱类型语言,可以通过空数组与数字比较永远为真或是小数来绕过
python有处理jwt的模块,根据上面分析,secretid 赋值小数,algorithm赋值none,key值是一个函数必需字段给空值就行,生成token就是authorization值的内容
python 安装jwt pip install pyjwt
import jwt token = jwt.encode({"secretid":0.1,"username":"admin","password":"admin"},algorithm="none",key="").decode('utf-8') print(token)
把生成的值替换authorization的值就通过验证了
登入之后点击get flag,权限足够,获得flag。
参考:https://www.jianshu.com/p/0f76e1c69e33