Kill Chain Reconstruction: CVE-2026-39987
The vulnerability itself is textbook — the /terminal/ws WebSocket endpoint in Marimo simply forgot to call validate_auth(). Every other WebSocket endpoint does it. This one doesn't. That's not a sophisticated bug; that's a configuration oversight that gives unauthenticated attackers a full PTY shell.
Here's the kill chain as I see it:
Initial Access: Pre-auth WebSocket RCE via /terminal/ws — no credentials, no user interaction, just connect and get shell.
Credential Enumeration: AWS credentials harvested from environment variables. According to Sysdig, the complete credential theft operation from initial connection to data exfiltration was executed in under 3 minutes.
Privilege Escalation: SSH private keys pulled from AWS Secrets Manager — this is where it gets interesting. The attacker didn't just dump creds; they pivoted to Secrets Manager, implying automated enumeration across common secret paths.
Lateral Movement: SSH across bastion servers using those keys.
Exfiltration: PostgreSQL data extraction.
On Your Evasion Pattern Question:
According to Sysdig's May 2026 observation, the attacker used Cloudflare Workers as a per-request egress pool: 12 cloud API calls fanned across eleven distinct IPs in 22 seconds, defeating per-source-IP detection. Eight short SSH sessions were driven against a downstream SSH bastion server.
The Cloudflare Workers distribution tells me they're using serverless compute to rotate source IPs dynamically. This isn't "sophisticated" in the sense of novel technique — it's commodity cloud abuse. Anyone with a Cloudflare account and a script can do this. What is notable is the timing: 22 seconds suggests pre-staged infrastructure, not on-the-fly improvisation.
The eight concurrent SSH sessions from the Cloudflare pool is parallelization for speed, not stealth. A truly sophisticated actor would throttle this to avoid triggering rate limits or anomaly detection. This looks like automated post-exploitation tooling — think cloud-credential-stealer.py running on a cron, not a human operator making tactical decisions.
Sysdig's assessment of this specific incident: "This was not a pre-scripted attack." The LLM-driven pivoting and the adaptive command sequence suggests an AI agent at the wheel, not a static playbook.
On the C2 IP 216.126.225.129:8443:
My data shows this IP tied to a distinct campaign — the UAT-10608 credential harvesting operation exploiting CVE-2025-55182 (React2Shell) in Next.js applications. @Nu11Sector's tweet about this C2 mentions exfiltration of AWS/GCP/Azure creds, K8s configs, Vault secrets — the pattern matches, but the initial access vector is different.
According to Cisco Talos reporting on UAT-10608, that campaign compromised at least 766 hosts. The Marimo campaign (CVE-2026-39987) and the UAT-10608 React2Shell campaign are likely separate operations with similar post-exploitation objectives. Both target cloud credentials, but:
- Marimo: WebSocket RCE in Python notebook environments, weaponized in 9 hours 41 minutes per Sysdig
- UAT-10608: Next.js RCE (CVE-2025-55182), 766+ hosts compromised, "NEXUS Listener" C2 framework
The C2 at 216.126.225.129:8443 is associated with UAT-10608 per Talos reporting. Unless @Nu11Sector has additional telemetry linking it to Marimo exploitation, I'd treat these as parallel campaigns with convergent TTPs, not a single actor.
That said — if you're seeing both in the same environment, that's worth investigating as a potential supply chain or shared infrastructure angle. Could be the same hosting provider, could be a reseller, could be coincidence. But the exploit paths are different enough that I'd want to see session logs or payload samples before calling it one campaign.
Bottom line: The Marimo evasion pattern shows LLM-driven automation with distributed infrastructure, not manual operator tradecraft. The C2 IP belongs to a different vulnerability campaign. If you're defending against Marimo, patch to 0.23.0 immediately — this is genuinely weaponizable, and the 9-hour 41-minute disclosure-to-exploitation timeline means your window was already tight.