![challenge description](https://i.imgur.com/scC1klM.png)
Going to the homepage link in the description, we can see a page with a login/register form.
![home](https://i.imgur.com/okH0ixP.png)
After registering, we can see a textbox with a string and we can see that a cookie is set with a JWT ([Json Web Token](https://jwt.io/)).
![saved_cookie](https://i.imgur.com/wFdBvJ0.png)
We can get the content of the token by going on `https://jwt.io/#debugger-io`. Here we can see the that it contains the User-ID of the account that's logged in
![decoded_cookie](https://i.imgur.com/ozf8pYO.png)
Now, looking at the source of the challenge, we can see that the objective is to visit the endpoint `/vault` while being "unrestricted"
```jsapp.get("/vault", (req, res) => {    if (!res.locals.user) {        res.status(401).send("Log in first");        return;    }    const user = users.get(res.locals.user.uid);    res.type("text/plain").send(user.restricted ? user.vault : flag);});```Going back on the site while being logged in, the content of `user.vault` is none other than the content of the text area in the home:
![set_vault](https://i.imgur.com/dYtqCUn.png)
![vault](https://i.imgur.com/cDyAVna.png)
As we have seen before, after login or registration, the cookie will be set with the UID of the user and signed with a random key.
Since we already know the format of the secret key (`0.[0-9]+`), I've started to try to break the jwt token by using [jwtcrack](https://github.com/brendan-rius/c-jwt-cracker), to try to forge a valid token.This tool uses a brute-force attack (so it tries every possible combination of the specified charset) to retrieve the secret used to sign the token.
It can be lauched with: `docker run -it --rm  jwtcrack [token] [charset] [maxlen] [algorithm]`
So, in our case: `docker run -it --rm  jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIwLjM0Nzc5Mjc3MTY2MDg4MjIiLCJpYXQiOjE2NTE1NzcwNDh9.asR5mHIczZ-s1tZHCoCNoLKtPsPFu8S46adyRwOYa-U 0.123456789 20 sha256`
After a day without anything (and thanks to a nudge from VCT on Discord), I've turned back to the source code.
In express, every app has an `app.use` function that's used as middleware and is called on every request.
```jsapp.use((req, res, next) => {    try {        res.locals.user = jwt.verify(req.cookies.token, jwtKey, {            algorithms: ["HS256"],        });    } catch (err) {        if (req.cookies.token) {            res.clearCookie("token");        }    }    next();});```
We can notice that the only condition set the user variable, is to use a valid jwt as a cookie.
Also, the login function (as opposed to the register function) never verifies that the fields of the request are actually defined.```jsapp.post("/login", (req, res) => {    const user = users.get(users.lookup(req.body.username));    if (user && user.password === req.body.password) {        res.cookie(            "token",            jwt.sign({ uid: user.uid }, jwtKey, { algorithm: "HS256" })        );        res.redirect("/");    } else {        res.redirect("/?e=" + encodeURIComponent("Invalid username/password"));    }});```
We can use this to our advantage: in theory by making a login request without having a body, it will generate a valid token
Let's try our hypothesis: we can use a tool called "Burp" to intercept our login request and modify its content.
![alogin](https://i.imgur.com/X4GYI6x.png)
![login](https://i.imgur.com/TEiwsZO.png)
Here we can send the request to the Repeater page, where we can edit and replay the request without problems.
![9repeater](https://i.imgur.com/tG8wElY.png)
![login_no_creds](https://i.imgur.com/Sibdgnn.png)
Now we have a valid token, but that's not related to any user!
![decoded jwt](https://i.imgur.com/PTffsP5.png)
We can see why this worked by carefully reading the functions called in the login method:```jsconst user = users.get(users.lookup(req.body.username));
//~~~~
get(uid) {    return this.users[uid] ?? {};}lookup(username) {    return this.usernames[username];}```In javascript, some objects when used in a boolean statement are always evaluated to `true` and others to `false`. These objects are called [Truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and [Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) respectively.
Here we can see two examples of these kinds of objects:
In the `lookup` function, if we pass an undefined value, it will return `undefined`.
As for the `get(uid)`, il will try to evaluate `this.users[undefined]`, but since `undefined` is a [Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), it will return an empty object.
So, now we have```jsif (user && user.password === req.body.password)```where `user` is an empty object so a [Truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy).
The same goes for the password check: if we don't send a password in the request and since the empty object won't have a password attribute, both will be evaluated to `undefined` so `undefined === undefined` which is true and will be created a correct cookie.
Now, when accessing the `vault` endpoint, `user.restricted` will be undefined too, and since `undefined` is a [Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), instead of the vault content, we'll get the flag!
So everything we need to do is to set the cookie with the value obtained by making the empty `/login` request, and then we can read the flag successfully.
![set_cookie](https://i.imgur.com/iPdnqd2.png)
![flag](https://i.imgur.com/I6hfpD4.png)
---
Now, this isn't the supposed way to solve the CTF: there's a "[Use after free](https://cwe.mitre.org/data/definitions/416.html)" vulnerability present (as indicated in the flag text) that enables you to use the cookie of a deleted user
In this case, the exploit works mostly the same, it's also based on the possibility to use a valid signed token in the request, but instead of using an empty one, is of a deleted user.
This causes the script to retrieve an undefined `user` object and therefore the checks will fail in a similar way