Analyzing a Multi-Actor Container Intrusion: Memory Analysis, Dead-Drop C2s, and a DDoS Payload
TL;DR During an investigation into a self-hosted compromised Budibase container, I pulled core dumps from two suspicious processes sharing a parent process tree. Inside, I found two completely separate threat actors operating side by side: Actor 1 (PANOPTICON): A dead-drop Node.js implant running out of /tmp/.syshealthd.js. It pulled commands from ntfy.sh topics and self-hosted Forgejo repository issues, authenticating tasks with daily HMAC-SHA256 keys. It tried to capture internal Redis Bull queues, but failed because the Alpine container had no Python runtime. Actor 2 (Stressify Botnet): A commercial DDoS worker that resolved its active C2 address by parsing a public Telegram account bio. It deployed a 193 MB custom ELF binary (browser2_all) containing headless Chromium, an Xvfb virtual frame buffer, and a BoringSSL HTTP/2 flooder built to bypass Cloudflare Turnstile challenges. Forensic Reality: With minimal tools installed and disk state wiped, traditional filesystem triage came up empty. Process memory dumps (gcore) recovered the full unminified JavaScript source, active C2 infrastructure, and every production credential leaked through container environment variables. I packaged and open-sourced the custom Python scripts built for this analysis in eln1x/node-memory-forensics-toolkit. What Happened Minimal Alpine containers feel safe right up until they aren’t. Strip out the package manager, drop Python, pull curl, kill systemd. None of it matters if the app running inside still holds the keys to your infrastructure. When attackers get remote code execution, they don’t pack up because there’s no bash prompt. They just adapt to the runtime . ...