Rating:

![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"

```js
app.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.

```js
app.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.
```js
app.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:
```js
const 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
```js
if (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

Original writeup (https://gist.github.com/SalScotto/a4580f132148804dce058af18b6e9a2f).