# cr0wnair -- Union CTF 2021

**Points**: 100 (27 solves)
**Given**: source.zip

## TL;DR

- we are given a web app with outdated packages installed (jpv and jwt-simple)
- jwt token signing is blocked by a pattern verification but it is bypassable if the constructor of the object has the same
`name`

as`[].constructor`

- recover the public key from 2 jwt tokens
- sign a token using
`HS256`

(symmetric) instead of`RS256`

(asymmetric) with the public key

## The web app

We are given a node.js application with 2 routes: `checkin`

and `upgrades`

.
In the `checkin`

route there’s a function called `createToken`

and a endpoint `/checkin`

from where we can call that function if some requirements are met.
In order to call `createToken`

the request body must be a valid json that meets the following pattern requirements and the extras array must have a object with `sssr`

as the key and `FQTU`

as the value.

```
const pattern = {
firstName: /^\w{1,30}$/,
lastName: /^\w{1,30}$/,
passport: /^[0-9]{9}$/,
ffp: /^(|CA[0-9]{8})$/,
extras: [
{sssr: /^(BULK|UMNR|VGML)$/},
],
};
```

## JPV vulnerability

By looking at the pattern above it’s obvious that the extras array can’t have a object with `FQTU`

as a value. That’s where I checked if the JPV library that was verifying the pattern was up to date.
Surprise, surprise, it wasn’t.
By googling a bit I found out that the way the library verifies if the object is an array was totally broken. Looking at this issue (https://github.com/manvel-khnkoyan/jpv/issues/6) it’s obvious that all it is needed to bypass the array verification is to include an object with the same constructor name as an array and it magically lets us put `FQTU`

inside a valid object with the `sssr`

key and therefore sign jwt tokens.

```
const payload = {
"firstName":"s3np41",
"lastName":"k1r1t0",
"passport":"123456789",
"ffp":"CA12345678",
"extras":{
"payload":{
"sssr":"FQTU"
},
"constructor":{
"name":"Array"
}
}
};
```

## JWT-simple vulnerability

Analysing the upgrades route it becomes obvious where the flag is stored.

```
router.post('/flag', [getLoyaltyStatus], function(req, res, next) {
if (res.locals.token && res.locals.token.status == "gold") {
var response = {msg: config.flag};
} else {
var response = {msg: "You do not qualify for this upgrade at this time. Please fly with us more."};
}
res.json(response);
});
```

Looking at the middleware `getLoyaltyStatus`

the token is being decoded with the public key. However no algorithm is being specified.

```
function getLoyaltyStatus(req, res, next) {
if (req.headers.authorization) {
let token = req.headers.authorization.split(" ")[1];
try {
var decoded = jwt.decode(token, config.pubkey);
} catch {
return res.json({ msg: 'Token is not valid.' });
}
res.locals.token = decoded;
}
next()
}
```

Looking at the version of the `jwt-simple`

library, the decode function is known to be vulnerable to a very popular jwt attack that consists in changing the value of the `alg`

in the header of the jwt to a symmetric algorithm (`HS256`

) rather than the asymmetric `RS256`

algorithm used for signing.
By changing the algorithm used to sign/decode from an asymmetric algorithm to a symmetric one it means that instead of using the private key to sign and the public key to decode it changes to using the same key to sign and decode the jwt.
Bingo!
If it uses the public key to decode, it means that a token signed with the public key and the `HS256`

algorithm is a valid token.
Unfortunately, the public key is redacted from the source.

## Recovering the public key

To recover the public key, one first needs to understand a simple overview of the `RSA with SHA256`

(or `RS256`

for short) algorithm.

The steps for signing the jwt are the following:

- Produce the digest of the base64 encoded header and the base64 encoded payload ->
`dig = sha256(base64(header)+'.'+base64(payload))`

- Pad the digest using
`PKCS1 v1.5`

to the byte size of the modulus (n) ->`pt = PKCS1_v1.5_ENCODE(dig,byte_size(n))`

- Modular exponentiation using the padded digest, the private exponent (d) and the modulus (n) ->
`sig = pt ** d % n`

The last equation can be rewritten as `sig ** e = pt + k*n`

. Since the padding is deterministic and the plaintext is known we can rewrite it once again as `sig ** e - pt = k*n`

.
By signing 2 tokens, 2 values for `k*n`

are retrievable and to obtain `n`

the only thing needed is the greatest common divisor between `k1*n`

and `k2*n`

.
Assuming `e = 65537`

: `n = gcd(sig1**65537 - pt1, sig2**65537 - pt2)`

From now on, it is only needed to sign a token with the PEM version of the public key (as a literal string) and with the `HS256`

algorithm and send it to the server.

Et voilá: `union{I_<3_JS0N_4nD_th1ngs_wr4pp3d_in_JS0N}`