CARBONATO — Docker Botnet with an AI Agent Operator
Executive Summary
CARBONATO is a Linux Docker botnet first documented by ThreatDown (Malwarebytes) on 2026-09-22, with corroborating reporting from BleepingComputer, The Hacker News, DarkReading, and SC World. The attacker's objective is dual: build a persistent, self-propagating foothold across internet-exposed Docker hosts, and use that foothold to run the open-source Hermes Agent AI framework (Nous Research, MIT-licensed, installed unmodified) as an interactive, Telegram-operated post-exploitation tool under the persona GH0ST. The agent's persona file (SOUL.md) is overwritten with a 39-line malicious prompt whose stated top priority is harvesting AI provider API keys — ranked above SSH credentials, access tokens, and databases — because the operator's own LLM gateway runs on free-tier capacity and needs more keys to keep operating.
The highest-value defensive angle is the single exposure the entire chain depends on: an unauthenticated Docker daemon API reachable on tcp/2375. Every phase — privileged container creation, host filesystem access via bind mount, the nsenter escape to host PID 1, persistence, agent installation, and propagation to the next /24 — is downstream of that one misconfiguration. Closing it (bind the daemon to the Unix socket, never expose 2375, require TLS mutual auth for any remote API) removes the entire attack chain at its root; every other control in this pack is defense-in-depth for hosts that are already exposed or already compromised.
Operational evidence recovered from an unauthenticated CARBONATO Docker registry (itself exposed since May 2026) spans October 2024 through August 2026, and six of seven known registries plus the LLM gateway were confirmed live as of September 3, 2026 — this is an active, ongoing operation, not a historical curiosity. Attribution signals (voseo Spanish in deployment reports, UTC-06:00 timestamps, the Telegram handle "Carbo506", and a Costa Rica-based reverse-tunnel sink) point to a Costa Rica-based operator, though ThreatDown does not attribute this to a named group.
Defender priority: if you run Docker anywhere with a network-reachable daemon API, verify TODAY that tcp/2375 (and 2376 without mutual TLS) is not reachable from any network the daemon does not explicitly trust — including from other hosts inside your own environment. This single check, run in Section 7, is worth more than every CQL query in Section 8 combined.Source Review & Web Hunter Notes
Five sources were fetched during research and saved as plain-text snapshots to Carbonato-Botnet-Docker-Hunt-sources/ for provenance. Two (DarkReading, SC World) returned HTTP 403 on direct fetch and were captured via search-engine result synthesis instead of a raw page fetch — flagged explicitly below and never used as the sole source for any atomic indicator.
| # | Source | Tier | Fetch method | Contributed |
|---|---|---|---|---|
| 1 | ThreatDown / Malwarebytes — CARBONATO | T1 | Full fetch | Primary source: full attack chain, all IOCs, attribution signals, all file paths/env vars/secrets |
| 2 | BleepingComputer | T2 | Full fetch | Corroborates attack chain, GH0ST persona, AS262145 reverse-tunnel destination (2nd source for the ASN) |
| 3 | The Hacker News | T2 | Full fetch | Corroborates GH0ST/SOUL.md, Telegram C2, LLM gateway relay. Also names unrelated campaigns (knaithe/KnYuan, Thailand MoF, Strix/Cairn retailer breach, CLOSEDQUORUM) — explicitly excluded from this pack, see note below |
| 4 | DarkReading | T2 | Search synthesis (direct fetch 403) | Corroborates GH0ST/SOUL.md overwrite, AI-key collection priority |
| 5 | SC World | T2 | Search synthesis (direct fetch 403) | Corroborates attack chain and Costa Rica attribution |
Source-content safety note
All five sources were treated strictly as intelligence data. Source #1 contains a verbatim quotation of the threat actor's own malicious SOUL.md prompt (the GH0ST persona text) — that text was reproduced by ThreatDown for analysis and is quoted in the saved snapshot as evidence, never executed or treated as an instruction to this pipeline. No fetched page contained text attempting to redirect this pipeline's behavior, request a further fetch "for the real IOCs," or claim operator/admin authority; nothing was found that required dropping a source per the injection-safety rule.
Explicitly out of scope
Source #3 (The Hacker News) describes several other Hermes-Agent-abuse incidents (knaithe/KnYuan group using DeepSeek via Hermes Agent, a July 2026 Thailand Ministry of Finance incident in Hermes "YOLO" mode, a Chinese-speaking operator running Strix/Cairn/Hermes against 27+ retailers, and a separate Go-based Windows implant called CLOSEDQUORUM). These are different campaigns and actors that happen to share tooling (Hermes Agent) with CARBONATO. Per this pack's scope rule, none of their indicators, names, or TTPs are carried into this pack.
Hunt Brief & Attack Chain
Hypothesis: if CARBONATO is active in our environment, we should see (1) a Docker host with an unauthenticated API listener on 2375, (2) evidence of a privileged, host-bind-mounted container created via that API, (3) a host-PID-1 nsenter escape, (4) redundant persistence writes across cron/systemd-timers/rc.local/OpenRC with immutable file attributes, (5) a process masquerading as systemd-logind or exec'd with spoofed [kworker/u2:0] arguments, (6) an outbound reverse SSH tunnel toward Costa Rica, and (7) process or DNS activity referencing the Hermes Agent's SOUL.md persona file or the operation's Telegram/LLM-gateway infrastructure.
| # | Phase | Behavior | Primary artifact |
|---|---|---|---|
| 1 | Host Takeover | Unauthenticated Docker API on tcp/2375 used to create + start a privileged container with Binds:["/:/host"], PidMode:"host", NetworkMode:"host" | Docker API calls; container "netns-probe" / "net-setup" |
| 2 | Escape & Persistence | nsenter -t 1 -m -u -n -i into host PID 1; entry.sh (v5.3) installs cron/systemd-timer/rc.local/OpenRC hooks, marks them immutable (chattr +i), pairs a watchdog that re-pulls the implant | /opt/gh0st/entry.sh, auto-persist-host.sh, /usr/local/bin/.docker-network-monitor |
| 3 | Agent Installation | Unmodified Hermes Agent installed; SOUL.md overwritten with the 39-line GH0ST persona prompt prioritizing AI API key theft | /root/.hermes/SOUL.md (contains "GH0ST"), /root/.hermes/loot/ |
| 4 | Operator Interface | Operator tasks arrive via Telegram; agent forwards task + SOUL.md to the operator's LLM gateway, which writes terminal commands the agent executes and reports results back over Telegram | Telegram chat 750752697, handle Carbo506; LLM gateway 213.136.83.197 and carbonato-proxy-*.vercel.app |
| 5 | Worm Propagation | Every 5 minutes, enumerate attached networks + Docker bridges, sweep each /24 on tcp/2375, verify Docker, check infection marker, redeploy | Repos gh0st/, fsociety/, netd-svc, system/resolved, scrub-empty |
| — | Secondary payload | XMRig cryptominer, frequently masquerading as /usr/sbin/systemd-logind | fsociety/xmrig, xmrig-agent repos |
Consolidated IOC Table
Source-count note: every atomic network indicator below is single-sourced to the ThreatDown primary (source #1). No second independent source publishes the literal IP/domain values (BleepingComputer/THN/DarkReading/SC World corroborate the behavior, not the specific addresses). Per this pack's policy, single-sourced indicators ship at action = detect, never prevent, and never at severity = critical. The GH0ST persona name and SOUL.md filename ARE corroborated by 3–4 independent sources and are treated with higher confidence throughout.
| Type | Value | Confidence | Action | Context |
|---|---|---|---|---|
| IPv4 | 45.79.183.61 | Medium (single-sourced, T1) | Detect | C2 hub (Linode) |
| IPv4 | 91.99.195.164 | Medium (single-sourced, T1) | Detect | fsociety-era C2 (Hetzner) |
| IPv4 | 213.136.79.115 | Medium (single-sourced, T1) | Detect | Beacon / reverse shell (Contabo), ports 8080 & 4444 |
| IPv4 | 213.136.83.197 | Medium (single-sourced, T1) | Detect | LLM gateway (Contabo), reported live |
| IPv4 | 190.211.124.187 | Medium-High (IP single-sourced; the AS262145/Costa Rica destination is 2nd-sourced by BleepingComputer) | Detect | Reverse-tunnel sink, AS262145, Costa Rica |
| Domain | carbonato-proxy-drab.vercel.app | Medium (single-sourced, T1) | Detect | LLM proxy, suspended per source |
| Domain | carbonato-proxy-zeta.vercel.app | Medium (single-sourced, T1) | Detect | LLM proxy, suspended per source |
| Domain | carbonato-proxy-zeta-2.vercel.app | Medium (single-sourced, T1) | Detect | LLM proxy, suspended per source |
| File path | /opt/gh0st/entry.sh | High (T1) | Hunt | Implant entrypoint script, v5.3 |
| File path | /opt/gh0st/auto-persist-host.sh | High (T1) | Hunt | Persistence-install companion script |
| File path | /root/.hermes/SOUL.md (containing "GH0ST") | High (3–4 sources) | Hunt | Hermes Agent malicious persona file |
| File path | /root/.hermes/loot/ | High (T1) | Hunt | Plaintext credential/API-key storage directory |
| File path | /usr/local/bin/.docker-network-monitor | High (T1) | Hunt | Watchdog binary; re-pulls implant if removed |
| File path | /usr/sbin/systemd-logind | High (T1) | Hunt | XMRig masquerading as this service binary |
| Process arg | [kworker/u2:0] | High (T1) | Hunt | Spoofed process-argument masquerade; never legitimate in this telemetry |
| Env var | GH0ST_C2 | High (T1) | Hunt | Container environment variable |
| Env var | FSOCIETY_DISABLE_TUNNEL | High (T1) | Hunt | Container environment variable |
| Env var | GATEWAY_ALLOW_ALL_USERS | High (T1) | Hunt | Container environment variable |
| Env var | CARBONATO_API_KEY | High (T1) | Hunt | .env variable on infected hosts |
| Secret string | carbonato125 | High (T1) | Hunt | Shared password observed across the archive |
| Repo name | gh0st/, fsociety/, netd-svc, system/resolved, scrub-empty | High (T1) | Hunt | Implant container repository names |
| Telegram | Chat ID 750752697 / handle Carbo506 | High (T1) | Enrich | Deployment reports and operator C2 chat |
| File hash | (none published) | — | — | No file hashes were published by any source as of this build |
Affected Surface & Telemetry Matrix
| Surface | Telemetry available | Priority | Gap |
|---|---|---|---|
| Docker daemon API exposure (tcp/2375) | Process execution (dockerd flags), network listen state | Critical | Wire-level Docker API HTTP calls are not directly visible to endpoint EDR — see Coverage Map |
| Container creation / privileged flag | Falcon process telemetry sees the resulting container-init process, not the Docker API metadata that set Privileged/Binds | High | Cannot confirm "Privileged: true" from CQL alone — pair with docker inspect (Section 7) |
| Host filesystem (persistence, chattr, implant paths) | Process execution telemetry (full visibility for anything exec'd) | High | Generic arbitrary text-file writes (e.g. editing SOUL.md content itself) are not a distinct Falcon event type — proxied via process telemetry (Q15) |
| Outbound network (C2 IPs, reverse SSH, Telegram, LLM gateway) | NetworkConnectIP4, DnsRequest | High | Telegram channel content is invisible; only egress to Telegram's IP/domain space is observable |
| Inside-container process activity | Depends on sensor deployment model — see Coverage Map | High | Hosts without a Falcon Linux sensor, or containers running on infrastructure the sensor cannot see into, have no coverage at all |
| Docker registry authentication posture | Not visible to endpoint EDR | Medium | Registry exposure audit is an infrastructure/network control, not a CQL hunt — see Section 7 and 12 |
ATT&CK Mapping
| Tactic | Technique | Behavior | Query / Control |
|---|---|---|---|
| Initial Access | T1190 | Unauthenticated Docker daemon API on tcp/2375 | Q1, Q14 |
| Execution | T1609 | Docker API container create/start/exec used as a remote command channel | Q1, Q3 |
| Privilege Escalation / Defense Evasion | T1611 | Privileged, host-bind-mounted container + nsenter into host PID 1 | Q2 |
| Persistence | T1053.003, T1053.006 | Cron jobs and systemd timers install the implant redundantly | Q4 |
| Persistence | T1037.004 | rc.local and OpenRC init hooks | Q4 |
| Persistence / Defense Evasion | T1543 | Watchdog process re-installs implant if removed | Q3 (entrypoint), native check (Section 7) |
| Defense Evasion | T1222.002 | chattr +i marks persistence files/binaries immutable | Q5 |
| Defense Evasion | T1036.004 | Process arguments spoofed as kernel thread [kworker/u2:0] | Q6 |
| Defense Evasion | T1036.005 | Container presents fake "systemd-resolved" banner; XMRig masquerades as /usr/sbin/systemd-logind | Q7 |
| Command and Control | T1572, T1021.004 | Reverse SSH tunnel to a Costa Rica relay, port derived from MD5(victim IP) | Q8 |
| Command and Control | T1071.001 | Beaconing / LLM-gateway contact over known infrastructure | Q9, Q10 |
| Command and Control | T1102.002 (analyst inference — see note) | Telegram Bot API used as bidirectional operator channel | Native hunt (Section 7); no dedicated CQL field exists for Telegram Bot API traffic content |
| Command and Control | No ATT&CK sub-technique currently published (flagged gap) | Operator's LLM gateway interprets tasks and returns terminal commands — an AI-orchestrated command relay sitting between Telegram and execution | None — genuine coverage/taxonomy gap, see Coverage Map |
| Discovery / Lateral Movement | T1046 | Docker-API /24 subnet sweep every 5 minutes | Q11 |
| Lateral Movement | T1210 | Repeated exploitation of remote Docker services against each newly found host | Q1, Q11 |
| Credential Access / Collection | T1552.001, T1552 | AI provider API keys, SSH keys, tokens exfiltrated to /root/.hermes/loot/ | Q12, Q15 |
| Impact | T1496 | XMRig cryptomining | Q13 |
Native Audit-Log Hunts
These checks run directly on Docker hosts (or via fleet config-management) and cover ground CQL cannot reach — especially confirming the Docker API's own metadata (Privileged/Binds) and reading SOUL.md's actual content.
Docker daemon exposure & live privileged containers
cat /etc/docker/daemon.json 2>/dev/null; ps aux | grep -F dockerd
ss -tlnp | grep -E ':(2375|2376)\b'
docker ps -q | xargs -r docker inspect \
--format '{{.Id}} Privileged={{.HostConfig.Privileged}} Binds={{.HostConfig.Binds}} PidMode={{.HostConfig.PidMode}}' \
| awk -F'Privileged=' '$2 ~ /^true/'
Hermes Agent persona file — authoritative content check
test -f /root/.hermes/SOUL.md && grep -l "GH0ST" /root/.hermes/SOUL.md grep -RIl "CARBONATO_API_KEY" /root/*/.env /etc/environment 2>/dev/null
Immutable-attribute sweep on known implant paths
lsattr -d /opt/gh0st /usr/local/bin/.docker-network-monitor /usr/sbin/systemd-logind 2>/dev/null | grep -- '-i-'
Persistence surface review
crontab -l 2>/dev/null; ls -la /etc/cron.d/ systemctl list-timers --all cat /etc/rc.local 2>/dev/null rc-status 2>/dev/null # OpenRC
Suggested auditd rules (see Section 13 for the full playbook)
-w /var/run/docker.sock -p rwxa -k docker_socket_watch -a always,exit -F arch=b64 -S execve -F path=/usr/bin/nsenter -k container_escape -a always,exit -F arch=b64 -S execve -F path=/usr/bin/chattr -k immutable_attr_change
Telegram egress (firewall / proxy logs)
On server workloads with no legitimate reason to reach Telegram, review firewall/proxy logs for outbound connections to api.telegram.org — Falcon-side coverage of this is limited to DNS/IP contact (see Coverage Map); proxy/firewall logs are the more complete source for this specific channel.
CrowdStrike LogScale CQL Hunt Queries
Looks for: an infected host issuing its own curl/wget call against a target's Docker API to create a privileged, host-mounted container — the worm's propagation step, seen from the source side. FP: legitimate Docker orchestration/CI tooling calling the Remote API with Privileged:true against a real target.
// HUNT: CARBONATO - T1190 Exploit Public-Facing Application + T1610 Deploy Container (outbound propagation) // HYPOTHESIS: an infected host performing CARBONATO worm propagation issues its own // outbound HTTP calls against a target's Docker API on :2375 to create a privileged, // host-mounted container. If this fires, the ALERTING HOST is the attacker/source, // not (necessarily) the victim -- treat it as an active-infection indicator. // MITRE: T1190, T1610 | CONF: high FP: medium COST: low | REQUIRES: process execution telemetry // TUNING: exclude hosts running legitimate Docker orchestration/CI tooling (Portainer, // Rancher, GitLab/Jenkins Docker-in-Docker runners) that legitimately call // containers/create with Privileged:true against a REMOTE Docker API on 2375 -- // scope to hosts outside your CI/orchestration allowlist. // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^(curl|wget)$/i | CommandLine like "*2375/containers/create*" | CommandLine like "*Privileged*" | table([@timestamp, aid, ComputerName, UserName, FileName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: nsenter attaching to host PID 1's mount/UTS/net/IPC namespaces from inside a container. FP: legitimate container-debugging and security tooling (Falco, kube-bench, sysdig) or manual sysadmin troubleshooting.
// HUNT: CARBONATO - T1611 Escape to Host (nsenter into host PID 1) // HYPOTHESIS: after launching a privileged container with the host filesystem // bind-mounted and host PID/network namespaces, the implant runs nsenter to // attach to host PID 1 and execute commands as if running directly on the host. // MITRE: T1611, T1610 | CONF: high FP: medium COST: low | REQUIRES: process execution telemetry // TUNING: exclude known container-debugging / security-tooling hosts that use // nsenter legitimately (Falco, kube-bench, sysdig, manual sysadmin troubleshooting // sessions) -- scope to hosts NOT on the container-ops allowlist, or pair with // Q3/Q6/Q7 for corroborating implant evidence before escalating. // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^nsenter$/i | CommandLine like "*-t 1*" | CommandLine like "*-m*" | CommandLine like "*-u*" | CommandLine like "*-n*" | table([@timestamp, aid, ComputerName, UserName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: execution of the implant's named entrypoint scripts from their known drop path. FP: negligible — path and filenames are highly specific.
// HUNT: CARBONATO - T1609 Container Administration Command / Implant Entrypoint Execution // HYPOTHESIS: the deployed container runs entry.sh (v5.3) from /opt/gh0st/, or its // companion auto-persist-host.sh, to establish the reverse tunnel, persistence // hooks, and Hermes Agent install in one shot. // MITRE: T1609, T1543 | CONF: high FP: low COST: low | REQUIRES: process execution telemetry // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^(entry\.sh|auto-persist-host\.sh)$/i | ImageFileName like "*/opt/gh0st/*" | table([@timestamp, aid, ComputerName, UserName, ImageFileName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: a process writing into any of the four autostart locations, or enabling a suspiciously-named systemd unit. FP: configuration management (Ansible/Puppet/Chef/cloud-init/Salt) and package post-install scripts write to these same paths constantly — this is the noisiest query in the pack.
// HUNT: CARBONATO - T1053.003/T1053.006/T1037.004 Persistence via Cron, Systemd Timers, rc.local, OpenRC // HYPOTHESIS: entry.sh installs redundant persistence across four Linux autostart // mechanisms so removing any single one does not kill the implant. Look for a // process writing into any of the four locations, or enabling a suspicious // systemd unit/timer. // MITRE: T1053.003, T1053.006, T1037.004 | CONF: medium FP: high COST: medium | REQUIRES: process execution telemetry // TUNING: this is one of the noisiest queries in the pack -- configuration // management (Ansible, Puppet, Chef, cloud-init, Salt), package managers // (apt/dpkg/yum postinst scripts), and routine sysadmin cron edits all write // to these same paths constantly. Scope to hosts outside the CM/patch-window // allowlist, and correlate with Q3/Q6/Q7/Q12 before treating a hit as // confirmed CARBONATO activity rather than routine administration. // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | (FileName=/^(tee|cp|install|mv)$/i and (CommandLine like "*/etc/cron.d/*" or CommandLine like "*/etc/systemd/system/*.timer*" or CommandLine like "*/etc/rc.local*" or CommandLine like "*/etc/init.d/*" or CommandLine like "*/etc/conf.d/*")) or (FileName=/^systemctl$/i and CommandLine like "*enable*" and (CommandLine like "*netd-svc*" or CommandLine like "*gh0st*" or CommandLine like "*fsociety*" or CommandLine like "*resolved*")) | table([@timestamp, aid, ComputerName, UserName, FileName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: chattr +i, which entry.sh uses so persistence files and implant binaries survive a naive "delete and forget" response. FP: log-integrity tooling (auditd, OSSEC/Wazuh) and some hardening baselines legitimately use chattr +i on config/log files.
// HUNT: CARBONATO - T1222.002 File and Directory Permissions Modification (chattr +i) // HYPOTHESIS: entry.sh marks its persistence files and implant binaries immutable // with chattr +i so a defender's "delete and forget" response silently fails and // the paired watchdog can re-pull the implant if the container/file disappears. // MITRE: T1222.002 | CONF: medium FP: medium COST: low | REQUIRES: process execution telemetry // TUNING: chattr +i is also used legitimately by log-integrity tooling (auditd, // OSSEC/Wazuh agents protecting their own config) and some hardening baselines // that lock down /etc/passwd, /etc/shadow, or SSH host keys. Exclude known // security-agent parent processes and baseline-managed paths; a hit on // /opt/gh0st/, /root/.hermes/, /usr/local/bin/.docker-network-monitor, or // /usr/sbin/systemd-logind is high-confidence regardless. // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^chattr$/i | CommandLine like "*+i*" | table([@timestamp, aid, ComputerName, UserName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: any exec'd process whose FileName/CommandLine contains "kworker". FP: essentially none — real kworker kernel threads are created by kthreadd and never exec()'d as a user image, so they never generate this event type in the first place; any hit here is a spoof by definition.
// HUNT: CARBONATO - T1036.004 Masquerade Task or Service (fake [kworker] process arguments) // HYPOTHESIS: entry.sh disguises the implant's process arguments as the kernel // thread "[kworker/u2:0]". Genuine kworker threads are kernel threads created by // kthreadd; they are never exec()'d as a user-mode image and therefore never // generate this event type at all. Any ProcessRollup2/SyntheticProcessRollup2 // event whose FileName or CommandLine contains "kworker" is, by definition, // not a real kernel worker. // MITRE: T1036.004 | CONF: high FP: low COST: low | REQUIRES: process execution telemetry // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | CommandLine=/kworker/i | table([@timestamp, aid, ComputerName, UserName, ImageFileName, FileName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: a process named systemd-logind/systemd-resolved spawned by something other than the expected init chain. FP: minimal-container base images that legitimately reimplement these binaries, or non-systemd (OpenRC-only) hosts where the "expected parent" baseline differs.
// HUNT: CARBONATO - T1036.005 Match Legitimate Name or Location (XMRig disguised as systemd-logind / systemd-resolved) // HYPOTHESIS: the host-side XMRig miner is dropped and executed as // /usr/sbin/systemd-logind, and the deployed container presents a fake // "systemd-resolved" banner. A process using the real systemd service basename // but spawned by something other than the expected init chain (systemd/init as // PID 1, or containerd-shim for the container-side fake) is the tell. // MITRE: T1036.005 | CONF: medium FP: medium COST: low | REQUIRES: process execution telemetry // TUNING: on hosts using a non-systemd init (OpenRC-only, containers built // without systemd) the "expected parent" baseline differs -- validate the // normal parent for systemd-logind/systemd-resolved in your fleet before // enabling detect-to-prevent; false positives cluster on minimal-container // base images that legitimately reimplement these binaries. // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^(systemd-logind|systemd-resolved)$/i | !in(ParentBaseFileName, values=["systemd", "init", "containerd-shim", "containerd-shim-runc-v2"]) | table([@timestamp, aid, ComputerName, UserName, ImageFileName, ParentBaseFileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: the SSH client reverse-forward flags (-R/-N), since the actual remote port varies per host (derived from MD5 of the victim IP). FP: reverse SSH tunnels are a completely normal remote-support/bastion technique — this is a wide behavioral net, meant to be cross-referenced against Q9's known destination IPs.
// HUNT: CARBONATO - T1572 Protocol Tunneling (reverse SSH tunnel establishment) // HYPOTHESIS: entry.sh opens a reverse SSH tunnel (ssh -R ... -N) to a relay in // Costa Rica, with the remote port derived from an MD5 hash of the victim's own // IP. The exact port varies per host, so hunt the SSH CLIENT BEHAVIOR (-R/-N // reverse-forward flags to an external destination) rather than a fixed port. // MITRE: T1572, T1021.004 | CONF: medium FP: high COST: low | REQUIRES: process execution telemetry // TUNING: reverse SSH tunnels are a completely normal remote-support and // bastion-hopping technique. Exclude your known jump-hosts, support-tunnel // service accounts, and any destination inside your own ASNs/VPN ranges. // Escalate immediately if the destination matches Q9's known CARBONATO IPs or // sits in AS262145 (Costa Rica). // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^ssh$/i | CommandLine like "*-R*" | CommandLine like "*-N*" | table([@timestamp, aid, ComputerName, UserName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: outbound connections to any of the five published CARBONATO IPv4 addresses, joined back to the initiating process for triage context. FP: low — exact IP match; note these are single-sourced (ThreatDown only), so they ship at action=detect, not prevent.
// HUNT: CARBONATO - Network Connections to Known C2 / Beacon / Reverse-Tunnel Infrastructure // HYPOTHESIS: an infected host beacons to one of CARBONATO's five published IPv4 // addresses (C2 hub, fsociety-era C2, beacon/reverse-shell, LLM gateway, or the // Costa Rica reverse-tunnel sink). // SOURCE NOTE: all five IPs are single-sourced to the ThreatDown primary report // (no second independent source publishes the literal addresses) -- shipped at // action=detect, not prevent, per two-source policy for prevent/critical IOCs. // MITRE: T1071.001, T1572 | CONF: high FP: low COST: low | REQUIRES: network connection telemetry // LOOKBACK: 7d #event_simpleName=NetworkConnectIP4 | RemoteAddressIP4=/^(45\.79\.183\.61|91\.99\.195\.164|213\.136\.79\.115|213\.136\.83\.197|190\.211\.124\.187)$/ | rename(field=ContextProcessId_decimal, as=TargetProcessId_decimal) | join(query={#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/}, field=TargetProcessId_decimal, include=[ImageFileName, CommandLine]) | table([@timestamp, aid, ComputerName, UserName, RemoteAddressIP4, RemotePort, ImageFileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: DNS resolution of any of the three carbonato-proxy-*.vercel.app subdomains. FP: low — exact subdomain match. These proxies are reported suspended, so a hit is retrospective evidence of past contact, not necessarily live C2 today.
// HUNT: CARBONATO - T1071.001 DNS Resolution of LLM-Proxy C2 Domains // HYPOTHESIS: the operator's LLM gateway was additionally proxied through three // Vercel-hosted domains. ThreatDown reports all three as suspended at time of // publication; a hit is retrospective evidence of past C2 contact, not // necessarily live C2 today. // SOURCE NOTE: single-sourced to ThreatDown -- action=detect, not prevent. // MITRE: T1071.001 | CONF: high FP: low COST: low | REQUIRES: DNS request telemetry // LOOKBACK: 30d (proxies are suspended; widen the window to catch historical contact) #event_simpleName=DnsRequest | DomainName=/^carbonato-proxy-(drab|zeta|zeta-2)\.vercel\.app$/i | table([@timestamp, aid, ComputerName, UserName, DomainName]) | sort(@timestamp, order=desc)
Looks for: a single host generating many distinct port-2375 connection attempts in a short window — the worm's 5-minute /24 scan loop. FP: vulnerability scanners and Docker orchestration management hosts polling many nodes.
// HUNT: CARBONATO - T1046 Network Service Discovery + Worm Propagation Sweep // HYPOTHESIS: an infected host scans its attached /24 subnets for exposed Docker // daemons on tcp/2375 every 5 minutes. A single host generating many distinct // port-2375 connection attempts in a short window is the worm's scan loop, not // a person. // MITRE: T1046, T1210 (repeated exploitation against each newly found host) | CONF: medium FP: medium COST: medium | REQUIRES: network connection telemetry // TUNING: exclude known vulnerability-scanner / asset-inventory service accounts // and hosts (Nessus, Qualys, Rapid7 InsightVM, internal CMDB scan windows) and // legitimate Docker Swarm / Portainer / Rancher management hosts that poll many // node Docker APIs on a schedule. // LOOKBACK: 1h (the worm's scan interval is 5 minutes; a 1h window comfortably // catches a full sweep cycle without an in-query time filter) #event_simpleName=NetworkConnectIP4 | RemotePort=2375 | groupBy([aid, ComputerName], function=[count(as=Attempts), count(RemoteAddressIP4, distinct=true, as=DistinctTargets)], limit=1000) | test(DistinctTargets >= 20) | sort(DistinctTargets, order=desc)
Looks for: the known CARBONATO env-var/secret strings and any process referencing the loot directory. FP: negligible — these are campaign-specific literal strings.
// HUNT: CARBONATO - T1552.001 Unsecured Credentials (AI API key / secret loot collection) // HYPOTHESIS: the GH0ST persona's top-priority objective is exfiltrating AI // provider API keys, storing every credential in plaintext under // /root/.hermes/loot/. Hunt for the known CARBONATO env-var and secret strings, // and for process activity referencing the loot directory. // MITRE: T1552.001, T1552 | CONF: high FP: low COST: low | REQUIRES: process execution telemetry // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | (CommandLine like "*CARBONATO_API_KEY*" or CommandLine like "*GH0ST_C2*" or CommandLine like "*FSOCIETY_DISABLE_TUNNEL*" or CommandLine like "*GATEWAY_ALLOW_ALL_USERS*" or CommandLine like "*carbonato125*" or CommandLine like "*/root/.hermes/loot/*") | table([@timestamp, aid, ComputerName, UserName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: XMRig's own command-line flags, independent of the systemd-logind masquerade (Q7) since some deployments run it under its real name. FP: rare legitimate mining/benchmarking workloads.
// HUNT: CARBONATO - T1496 Resource Hijacking (XMRig miner execution) // HYPOTHESIS: XMRig is deployed as the secondary payload, most often disguised as // /usr/sbin/systemd-logind (see Q7). Hunt the miner's own command-line flags // independent of the masquerade, since some deployments run it under its real // name from a container repository (fsociety/xmrig, xmrig-agent). // MITRE: T1496 | CONF: high FP: medium COST: low | REQUIRES: process execution telemetry // TUNING: stratum-protocol flags and "--donate-level" are effectively unique to // XMRig and its forks, but a small number of organizations legitimately run // authorized mining/benchmarking workloads -- exclude any host on an approved // mining-research or benchmarking allowlist. // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | (CommandLine like "*stratum+tcp*" or CommandLine like "*stratum+ssl*" or CommandLine like "*--donate-level*" or FileName=/^xmrig$/i) | table([@timestamp, aid, ComputerName, UserName, ImageFileName, FileName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Looks for: dockerd started with an unauthenticated TCP listener on 2375 — this is NOT a compromise detection, it is an exposure audit. Run it on a schedule. FP: low.
// HUNT: CARBONATO - Proactive Exposure Audit: Docker Daemon Listening Unauthenticated on tcp/2375 // HYPOTHESIS: this is NOT a compromise detection -- it is an exposure audit. Any // host running dockerd with an unauthenticated TCP listener is a CARBONATO // initial-access target regardless of whether it has been hit yet. Run this on // a schedule, not just after an incident. // MITRE: T1190 (the exposure this technique exploits) | CONF: high FP: low COST: low | REQUIRES: process execution telemetry // LOOKBACK: 30d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^dockerd$/i | CommandLine like "*-H*tcp://*" | CommandLine like "*2375*" | table([@timestamp, aid, ComputerName, UserName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: process activity reading/writing the SOUL.md persona path, as a proxy for the file-content check CQL cannot perform natively. FP: unrelated developer or roleplay-bot repos reusing the same filename/string if the path qualifier is dropped.
// HUNT: CARBONATO - Hermes Agent SOUL.md Persona Tampering Signature (GH0ST) // HYPOTHESIS: ThreatDown's own top recommended abuse signature is the SOUL.md // persona file at /root/.hermes/SOUL.md containing the string "GH0ST". Falcon's // documented data model does not expose a generic file-write/read event for // arbitrary text files (typed events exist for PE/ELF/Zip/Jar, not persona // config text), so this is hunted via the process telemetry proxy: any process // reading, writing, or grepping that specific path. Treat a hit as // high-confidence and pair with a direct RTR file-content check for the // authoritative answer -- see Coverage Map (Section 15) for the honest gap. // MITRE: T1552.001 (loot/persona file), T1036 | CONF: medium FP: medium COST: low | REQUIRES: process execution telemetry (file-content confirmation requires RTR / a file-integrity tool, not CQL) // TUNING: scope strictly to the /root/.hermes/ path -- a bare match on "SOUL.md" // or "GH0ST" without the path qualifier will pick up unrelated developer or // roleplay-bot repos that happen to use the same filename/string. // LOOKBACK: 7d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^(cat|grep|less|head|tail|vi|vim|nano|find)$/i | CommandLine like "*/root/.hermes/*" | table([@timestamp, aid, ComputerName, UserName, FileName, CommandLine, ParentBaseFileName]) | sort(@timestamp, order=desc)
Custom IOA Recommendations
IOA-1 · Host-PID Namespace Escape via nsenter
Rule Type: Process Creation | Action: Detect | Severity: High | MITRE: T1611
Image Filename: .*/nsenter$ Command Line: .*-t\s+1.*-m.*-u.*-n.*
FP tuning: exclude container-security tooling hosts (Falco, sysdig, kube-bench) via a host-group exclusion in the rule scope, not the pattern itself.
IOA-2 · chattr Immutable-Bit Set on Known Implant Paths
Rule Type: Process Creation | Action: Detect | Severity: Medium | MITRE: T1222.002
Image Filename: .*/chattr$ Command Line: .*\+i.*(gh0st|hermes|docker-network-monitor|systemd-logind).*
FP tuning: exclude known log-integrity/security-agent parent processes before promoting beyond Detect.
IOA-3 · Spoofed Kernel-Worker Process Argument
Rule Type: Process Creation | Action: Detect | Severity: High | MITRE: T1036.004
Command Line: .*kworker.*
FP tuning: minimal — real kworker kernel threads never appear in this telemetry (see Q6). Still start in Detect for 14 days per standard promotion guidance before considering Block.
None of these are recommended for immediate Block promotion. Follow the standard 14-day Detect-mode / <5% FP-rate canary rollout before promoting any rule, per standing prevention-policy guidance.
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags ipv4,45.79.183.61,detect,high,2027-03-30,CARBONATO C2 hub (Linode),campaign:Carbonato ipv4,91.99.195.164,detect,high,2027-03-30,CARBONATO fsociety-era C2 (Hetzner),campaign:Carbonato ipv4,213.136.79.115,detect,high,2027-03-30,CARBONATO beacon/reverse shell Contabo ports 8080 4444,campaign:Carbonato ipv4,213.136.83.197,detect,high,2027-03-30,CARBONATO LLM gateway (Contabo),campaign:Carbonato ipv4,190.211.124.187,detect,high,2027-03-30,CARBONATO reverse-tunnel sink AS262145 Costa Rica,campaign:Carbonato domain,carbonato-proxy-drab.vercel.app,detect,medium,2026-12-29,CARBONATO LLM proxy suspended per source,campaign:Carbonato domain,carbonato-proxy-zeta.vercel.app,detect,medium,2026-12-29,CARBONATO LLM proxy suspended per source,campaign:Carbonato domain,carbonato-proxy-zeta-2.vercel.app,detect,medium,2026-12-29,CARBONATO LLM proxy suspended per source,campaign:Carbonato
Process args containing "kworker" in ProcessRollup2/SyntheticProcessRollup2 (never legitimate) nsenter -t 1 -m -u -n -i (host PID-1 namespace escape) chattr +i on /opt/gh0st/, /root/.hermes/, /usr/local/bin/.docker-network-monitor, /usr/sbin/systemd-logind ssh -R ... -N to an external, non-allowlisted destination dockerd -H tcp://0.0.0.0:2375 (or any 0.0.0.0/public bind without TLS) /root/.hermes/SOUL.md containing the string "GH0ST"
CARBONATO (botnet / campaign name) Hermes Agent (MIT-licensed AI framework, Nous Research -- installed UNMODIFIED, abused via persona overwrite) GH0ST (malicious persona name given to the Hermes Agent instance) fsociety (repository/container naming convention observed in the same operation) XMRig (secondary cryptomining payload)
[ ] No Docker daemon reachable on tcp/2375 or tcp/2376 without mutual TLS, from ANY network [ ] docker.sock is the only listener; "hosts" in daemon.json contains no tcp:// entry [ ] No running container has HostConfig.Privileged=true with a "/" host bind mount unless explicitly whitelisted [ ] Every private registry requires authentication (no anonymous pull/push) [ ] AI provider API keys are not stored as plaintext container env vars [ ] Fleet inventory includes every Docker host (no "shadow" hosts provisioned outside enrollment)
Detection Validation Gates
| Gate | Requirement |
|---|---|
| Telemetry ready | Falcon Linux sensor installed on the Docker host itself (not just inside a container) with process execution, network connection, and DNS telemetry enabled |
| Benign baseline | Run Q1–Q15 in Detect mode for 7–14 days before treating hit volume as actionable; Q4/Q8/Q11 need the longest baseline given their FP profile |
| Positive test | On an isolated test VM, expose a throwaway dockerd on 2375, issue a benign privileged-container create/start against it (alpine image, non-destructive command), run nsenter -t 1 with a benign command, and confirm Q1/Q2/Q3 fire against the test host only |
| Promotion criteria | Never auto-promote any of these to Falcon IOC action=prevent — the network IOCs are single-sourced per Section 4. Custom IOAs may move from Detect to Block only after 14+ days at <5% measured FP rate, on a canary group first |
Hardening — Tiered
- Bind the Docker daemon to the local Unix socket only; remove any
tcp://entry fromdaemon.json"hosts". If a remote API is genuinely required, mutual TLS is mandatory, never plaintext TCP. MITRE M1042; CIS Docker Benchmark 2.1, 2.6; Docker Engine security docs — "Protect the Docker daemon socket." - Default-deny inbound tcp/2375 and tcp/2376 (without mutual TLS) at the firewall/security-group layer from every network except an explicit management CIDR. MITRE M1030; CIS Docker Benchmark 2.6.
- Disable
--privilegedcontainer creation fleet-wide unless explicitly whitelisted per workload; immediately audit and terminate any running privileged container with a host filesystem bind mount. MITRE M1026; CIS Docker Benchmark 5.4 "Ensure privileged containers are not used", 5.5 "Ensure sensitive host system directories are not mounted on containers." - Require authentication on every private Docker registry — no anonymous pull or push. MITRE M1047; Docker Engine security docs — registry access control.
- Roll out seccomp and AppArmor/SELinux profiles fleet-wide; never run with
--security-opt seccomp=unconfined. MITRE M1022; CIS Docker Benchmark 5.21, 5.28. - Migrate eligible workloads to rootless Docker so a daemon compromise does not equal root-on-host. MITRE M1026; Docker Engine security docs — "Rootless mode."
- Deploy egress filtering / proxy allowlisting blocking direct outbound to
api.telegram.orgfrom server workloads with no legitimate need for it, and block outbound SSH from container hosts to non-approved destinations. MITRE M1037; CIS Docker Benchmark 2.1. - Alert on
chattr +ioutside an approved baseline (auditd rule in Section 13). MITRE M1022.
- Move AI provider API keys (OpenAI, Anthropic, Google/Gemini, OpenRouter, Together, Groq, Mistral, Cohere, self-hosted LocalAI/Ollama/vLLM/LiteLLM/OneAPI) out of container environment variables and into a secrets manager issuing short-lived tokens; alert on key usage from unexpected source IPs. MITRE M1041; NIST SP 800-53 IA-5.
- Run a recurring internal Docker-host exposure sweep (the same /24 logic the worm uses, pointed at your own estate) so a newly provisioned host can't sit exposed for months before enrollment. MITRE M1047.
- Adopt container image/registry provenance and signing (Docker Content Trust / Notary, or Sigstore cosign) so an implant repository cannot be silently pulled and trusted. MITRE M1047; CIS Docker Benchmark 4.5, 4.6.
Deployable Playbooks
1. Bind the daemon to the Unix socket only
# /etc/docker/daemon.json -- remove any "hosts" TCP listener; Unix socket only
{
"hosts": ["unix:///var/run/docker.sock"]
}
# Verify no TCP listener remains, then restart:
ss -tlnp | grep -E ':(2375|2376)\b' || echo "OK: no Docker API TCP listener"
systemctl restart docker
2. Default-deny 2375/2376 at the host firewall
# nftables example -- default-deny inbound 2375/2376 except a management CIDR
nft add rule inet filter input tcp dport { 2375, 2376 } ip saddr != 10.10.0.0/24 drop
3. Find and kill privileged / host-mounted containers
docker ps -q | xargs -r docker inspect \
--format '{{.Id}} {{.HostConfig.Privileged}} {{.HostConfig.Binds}}' \
| awk '$2=="true"'
# Review the output, then for confirmed-malicious containers:
docker kill <container_id> && docker rm <container_id>
4. Enforce seccomp/AppArmor and rootless Docker
# Confirm no container is running unconfined:
docker ps -q | xargs -r docker inspect --format '{{.Id}} {{.HostConfig.SecurityOpt}}' | grep -i unconfined
# Rootless Docker install (per-host, run as the target non-root user):
curl -fsSL https://get.docker.com/rootless | sh
5. auditd rules for escape / immutability tampering
# /etc/audit/rules.d/carbonato.rules -w /var/run/docker.sock -p rwxa -k docker_socket_watch -a always,exit -F arch=b64 -S execve -F path=/usr/bin/nsenter -k container_escape -a always,exit -F arch=b64 -S execve -F path=/usr/bin/chattr -k immutable_attr_change # Load and verify: augenrules --load auditctl -l | grep -E 'docker_socket_watch|container_escape|immutable_attr_change'
Deploying via your RMM
The snippets above are raw shell/config. To wrap them for fleet deployment (admin checks, error handling, compliance-tracking writeback), hand them to your RMM deployment tooling and ask for a deployable package.
Containment Runbook
| Phase | Actions | Owner | Evidence to preserve |
|---|---|---|---|
| 1. Isolate | Network-isolate the host (Falcon network containment or firewall block) before killing any process, to preserve forensic state | IR / SOC | Falcon containment record, timestamp |
| 2. Identify | Confirm the implant via Section 7 native checks: privileged/host-mounted containers, /opt/gh0st/, /root/.hermes/SOUL.md content, .docker-network-monitor, systemd-logind parent-process check | IR | Command output, docker inspect JSON |
| 3. Eradicate | Kill netd-svc / system/resolved containers; confirm and kill the masquerading systemd-logind PID; chattr -i every immutable persistence file before deleting; remove cron/systemd-timer/rc.local/OpenRC entries; remove operator-added SSH keys; remove implant images from local cache | IR | Before/after file listings, removed cron/unit contents |
| 4. Rotate credentials | Rotate every AI provider API key ever exposed on that host (assume all were harvested — loot priority #1 per the actor's own persona prompt); rotate SSH/service credentials; revoke and reissue registry credentials | IR + platform owners | Rotation timestamps, key-usage audit before revocation |
| 5. Rebuild | Rebuild the host from a known-good image/IaC definition rather than trusting a cleaned host — the watchdog + immutable-file design is built to survive partial cleanup | Platform / IR | New build provenance record |
| 6. Re-scan the /24 | This is a worm — assume lateral spread. Re-run Q11 and Q14 (and a direct port sweep) across the entire subnet before declaring the incident closed | SOC | Sweep results, any newly identified hosts |
| 7. Harden before return | Apply Section 12 Immediate-tier controls to the rebuilt host before returning it to production | Platform owners | Hardening checklist sign-off |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1190 | Unauthenticated Docker API on :2375 | Q1, Q14 | — | Partial |
| T1609/T1610 | Privileged container create/start via Docker API | Q1, Q3 | — | Partial |
| T1611 | nsenter host PID-1 escape | Q2 | IOA-1 | Good |
| T1053.003/.006, T1037.004 | Cron/systemd-timer/rc.local/OpenRC persistence | Q4 | — | Partial |
| T1222.002 | chattr +i immutability | Q5 | IOA-2 | Good |
| T1036.004 | [kworker] argument spoofing | Q6 | IOA-3 | Good |
| T1036.005 | systemd-logind/-resolved masquerade | Q7 | — | Partial |
| T1572, T1021.004 | Reverse SSH tunnel | Q8, Q9 | — | Good |
| T1071.001 | C2 IP / LLM-proxy domain contact | Q9, Q10 | — | Good |
| T1102.002 (Telegram Bot API C2) | Operator tasking over Telegram | Section 7 (egress only) | — | GAP — channel content invisible; only egress to Telegram IP/domain space is observable |
| AI-orchestrated command relay (no ATT&CK ID published) | LLM gateway interprets tasks, returns terminal commands | — | — | GAP — genuine taxonomy gap; no dedicated technique exists yet for this control-flow pattern |
| T1046, T1210 | /24 subnet sweep on :2375; repeated exploitation | Q11 | — | Partial |
| T1552.001, T1552 | AI API key / credential loot collection | Q12, Q15 | — | Partial — env-var/string hunt is strong; SOUL.md file-content confirmation requires RTR, not CQL |
| T1496 | XMRig cryptomining | Q13 | — | Good |
Honest coverage gaps
- Inside-container visibility: any activity occurring strictly inside a container on infrastructure where no Falcon sensor is deployed (a managed/serverless container platform, or a Docker host where only a sidecar-less minimal runtime is present) is invisible to every query in this pack. All 15 queries assume a Falcon Linux sensor on the underlying host.
- Unenrolled hosts: Docker hosts with no Falcon Linux sensor at all — including newly-provisioned cloud scratch VMs that are exactly the profile CARBONATO targets — have zero coverage until enrolled. Section 12's strategic "recurring exposure sweep" control exists specifically to find these before the worm does.
- Docker API wire traffic: Falcon endpoint telemetry sees the resulting process activity, not the Docker Engine API HTTP request itself (the "Privileged":true / "Binds" JSON body). Confirming a container's actual privilege/mount configuration requires the native
docker inspectcheck in Section 7, or network-layer (NDR/firewall) visibility. - Telegram C2 channel content: end-to-end within Telegram's own infrastructure. Falcon can only observe that a host contacted Telegram's IP/domain space generically — it cannot distinguish this operator's specific bot traffic from legitimate Telegram use on that host.
- Arbitrary text file writes: the Falcon data model exposes typed file-write events (PE/ELF/Zip/Jar) but not a confirmed generic event for arbitrary text config writes. SOUL.md content changes, and the exact moment persistence files are written by an editor or heredoc, are hunted via process-execution proxies (Q4, Q15) rather than a native file-write event.
All queries assume telemetry is flowing and hosts have passed the validation gates in Section 11 before being trusted operationally.
Hunt Summary Ticket
TITLE: CARBONATO Docker Botnet / AI-Agent (GH0ST) Hunt
SEVERITY: High
SCOPE: All Linux hosts running a Docker daemon, especially any with a
network-reachable Docker API (tcp/2375 or 2376)
HYPOTHESIS: An unauthenticated Docker API is being used to deploy a privileged,
host-escaping container that installs Hermes Agent under a
malicious "GH0ST" persona, tunnels out over reverse SSH to Costa
Rica, receives operator tasking via Telegram, mines XMRig, and
propagates to every exposed host on the local /24 every 5 minutes.
QUERIES: Q1-Q15 (Section 8); native checks in Section 7
DO FIRST: Run Q14 (exposure audit) and the Section 7 daemon.json / ss check
fleet-wide. Any host that fires is a live initial-access target
regardless of infection status -- remediate exposure before
chasing individual alerts.
FINDINGS: Queries must be run manually in Falcon LogScale; this ticket
describes what a positive result looks like, not a live result.
A true positive is any Q1-Q13/Q15 hit corroborated by a Section 7
native check (docker inspect Privileged=true, or SOUL.md
containing "GH0ST").
GAPS: Inside-container-only visibility, unenrolled hosts, Docker API
wire traffic, Telegram channel content, arbitrary text-file
writes -- see Section 15 for the full honest gap list.
ACTIONS: Immediate-tier hardening (Section 12) on every Docker host;
containment runbook (Section 14) on any confirmed host.
OWNER: SOC / IR on-call
VERSION: v0.1 -- 2026-09-30
Changelog
References
| Tier | Source | Used for | Access date |
|---|---|---|---|
| T1 | ThreatDown / Malwarebytes — CARBONATO | Primary intel, full attack chain, all IOCs | 2026-09-30 |
| T2 | BleepingComputer | Corroboration, AS262145 2nd source | 2026-09-30 |
| T2 | The Hacker News | Corroboration; related-campaign context (excluded from IOCs) | 2026-09-30 |
| T2 | DarkReading | Corroboration (search synthesis, direct fetch 403) | 2026-09-30 |
| T2 | SC World | Corroboration (search synthesis, direct fetch 403) | 2026-09-30 |
| T1 | MITRE ATT&CK | Technique IDs and names | 2026-09-30 |
| T1 | CIS Docker Benchmark | Hardening control citations | 2026-09-30 |
| T1 | Docker Engine security documentation | Vendor hardening authority (daemon socket, rootless mode, registry auth) | 2026-09-30 |