← All campaigns

TeamPCP v21: Inside a 45-Second Docker Escape to Multi-Persistence Cryptojacking

Docker exposes a management interface, the Docker daemon API, that lets you create, start, and control containers. When it is accidentally published to the internet with no authentication, anyone who can reach that port can take over the host underneath it. We watched a fully automated toolkit do exactly that in about 45 seconds.

MITRE ATT&CK techniques T1190T1610T1609T1611T1068T1543.002T1053.003T1546.004T1098.004T1552.005T1552.001T1528T1562.001T1027T1046T1613T1021.004T1496

Docker exposes a management interface, the Docker daemon API, that lets you create, start, and control containers. On a developer’s laptop it listens only locally. But it is routinely, and accidentally, published straight to the internet with no authentication: a CI/CD runner opened to a build network, a “quick” remote-management setup, a cloud image shipped with the wrong default. Anyone who can reach that port can ask the daemon to run a container, and a container, asked the right way, is a foothold on the host underneath it.

We run internet-facing devices that present an exposed Docker daemon, and in June 2026 we watched a fully automated toolkit find one, walk through that foothold, and turn it into complete host takeover in about 45 seconds. The toolkit names itself in its own source: “TeamPCP v21.”

It is nominally a cryptominer. That is the least interesting thing about it. In the same automated run it harvests credentials from 18 categories, plants four independent persistence mechanisms, reaches for cluster-wide Kubernetes propagation, and, caught live, in the same session, rewrites the command-and-control (C2) addresses of miners it had already planted elsewhere, exposing its own previous infrastructure in the process. (Command-and-control, or C2, is the server an attacker’s malware calls home to for instructions and to report stolen data.)

We captured the whole chain verbatim, the recon, the escape, the persistence, and the credential sweep, and recovered the dropper, the miner configuration, and the miner binary into a one-way sandbox for analysis.

This is the step-by-step walkthrough.

Every command, payload string, URL, and credential quoted below is captured attacker input recorded on our own device, reproduced here as evidence so defenders can recognize it. This is not a HowTo for Evil.


Step 1: Recon, fingerprinting the exposed daemon

The toolkit opens by asking the daemon what it is and what it is running:

GET /version
GET /version
GET /v1.54/images/json

What the attacker is doing here:

  1. Two version probes, back to back. The duplicate is a tell about the tooling, not a mistake: the run uses two different client libraries, python-requests/2.34.2 and docker-sdk-python/7.1.0, and each one issues its own version handshake. The toolkit is a Python program driving the Docker API through both a raw HTTP client and the official Docker SDK.

  2. Enumerating images. GET /v1.54/images/json lists the container images already present on the host, so the toolkit can prefer an image that is already pulled and avoid the noise and delay of downloading one.

No exploit yet, this is reconnaissance against an API that answered without asking who was calling. That missing authentication is the entire vulnerability, and it is a total one. The Docker daemon API is not a read-only status page: it is the remote control for the whole container engine. Reaching it without a password means the toolkit can now tell the daemon to do anything it can do.

Two of those powers drive everything below:

  1. Run commands inside a container the host is already running (exec). The toolkit uses this first, in Step 2.
  2. Create brand-new containers, including ones wired to the host’s filesystem (create). The toolkit uses this next, in Step 3, and that is the actual escape to the host.

So nothing in Step 2 or Step 3 is a separate “hack.” Both are just the toolkit spending the control that the open API in Step 1 already handed it.


Step 2: The operator moves house, caught live

The toolkit spends that control on housekeeping before it deploys anything new. Using the first power listed above, exec, it runs commands inside a container already present on the host (no new container needed yet) and makes three find-and-replace edits to a miner source file at /opt/.cgroup-fs/miner.c. Captured verbatim (defanged):

sed -i 's/192.168.1.34/31.56.48[.]179/g'  /opt/.cgroup-fs/miner.c
sed -i 's/45.76.202[.]47/31.56.48[.]179/g'  /opt/.cgroup-fs/miner.c
sed -i 's/120.26.46[.]187/31.56.48[.]179/g' /opt/.cgroup-fs/miner.c

This step is baked into the toolkit’s standard playbook, it runs everywhere, patching any pre-existing miner it finds to point at the operator’s current C2. Because it runs everywhere, it ran on our device too, and in doing so it leaked the operator’s own infrastructure history.

What each line tells us:

  1. 192.168.1.34 → new C2. That first address is a private, non-routable one (the kind a machine has on a home or office LAN, never on the public internet). It is the operator’s own development/test box, a hardcoded leftover from where the toolkit was built and tried out.

  2. 45.76.202[.]47 → new C2. A prior C2 the operator has now abandoned (a Vultr server in Japan). Every host still pointing at it is being migrated forward.

  3. 120.26.46[.]187 → new C2. This is the scanning server that exploited us in Step 1. Rewriting it here confirms the operator’s scanner had been doing double duty as an early miner callback, and is now being separated from the dedicated C2.

Why this matters. Scan data shows you where an attacker is now. Catching an infrastructure migration mid-flight shows you where they were, a dev box, a retired C2, and a scanner-that-was-also-a-C2. Each is a retrospective hunting pivot a defender would otherwise never see.


Step 3: Four privileged containers, the escape

Now the toolkit uses the second power, create. It makes four containers in a row, each one a complete container escape. Every one carries the same two settings, this is the entry signature for the whole campaign:

POST /v1.54/containers/create
{
  ...
  "HostConfig": {
    "Privileged": true,
    "Binds": ["/:/host:rw"]
  }
}

What these two settings do. Privileged: true strips away the isolation that normally keeps a container fenced off from the host. Binds: ["/:/host:rw"] mounts the entire host filesystem, read-write, inside the container at /host. Together they mean the container can read and write any file on the machine underneath it, the container wall is now a formality. This combination has almost no legitimate use; seeing it on a containers/create call is the single highest-value thing to alert on.

Each of the four containers does one job:

  1. Shell dropper. Fetches the main payload (/w) from the C2 using a five-method download fallback, bash’s built-in /dev/tcp, busybox wget, curl, wget, and Python 3’s urllib, each tried twice, so the download succeeds on almost any Linux host regardless of what tools are installed. It then runs the fetched script. (The five-way fallback is the same “build for scale” pattern seen across commodity Linux cryptojacking.)

  2. SSH backdoor. Writes an attacker-controlled SSH public key into the host’s root account (/host/root/.ssh/authorized_keys), giving the operator direct remote login as root that survives reboots and password changes. The key carries the comment 41832@CAI, a stable fingerprint that lets a defender hunt for the same operator across many victims. It then runs the dropper inside the host’s own process space (via chroot/nsenter), so the payload executes as if it had never been in a container at all.

  3. Systemd persistence. Installs a system service named cgroup-fs.service, labeled “Control Group FS” to blend in with legitimate Linux plumbing, fetches the miner binary (/.real_mnd) and its configuration (/.mconf), and sets the service to run as root and restart automatically forever (Restart=always).

  4. Kubernetes lateral movement. Reads the host’s Kubernetes configuration (/host/etc/kubernetes/) and its service-account tokens, the credentials a node uses to talk to the rest of a cluster, then attempts kubectl get nodes and a kubectl apply of a DaemonSet. A DaemonSet is a Kubernetes object that runs a copy of a chosen container on every node in the cluster; applied here it would schedule the miner cluster-wide from this one foothold. (The manifest body itself was not captured.)

The point of four containers. This is not redundancy. Delivery, remote access, host persistence, and cluster spread are split into four independent operations so that if a defender catches and kills one, the other three still land. It is infrastructure-aware: the toolkit knows the difference between a lone Docker host and a Kubernetes node, and acts accordingly.


Step 4: Host takeover, what the dropper does once it lands

The shell dropper from container #1 is the workhorse. Once running on the host it executes a long, automated post-compromise routine. Here is the sequence, in order:

  1. Privilege escalation, if needed. If the toolkit did not already land as root, it exploits CVE-2026-31431, a Linux kernel local privilege-escalation flaw in the algif_aead cryptographic interface, flagged for container-breakout use and added to CISA’s Known Exploited Vulnerabilities catalog in May 2026. On-thesis for a Docker-escape chain: it is the exact bug that turns “code in a container” into “root on the host” when the easy path is blocked. (Defenders should confirm the CVE against NVD and the vendor advisory before acting on it.)

  2. Blinding the cloud. It removes the monitoring and security agents of the major cloud providers, Alibaba, Tencent, AWS, Google Cloud, and Azure, so the host stops reporting its own compromise to the cloud console the owner watches.

  3. Evicting the competition. It terminates rival miners already on the box and locks the Docker management ports shut behind it (iptables DROP on 2375–2377), denying the same door it just walked through to any other attacker. Stolen compute is a finite resource; the operator wants it all.

  4. Mining. It launches XMRig 6.22.2, a well-known open-source Monero miner, under a watchdog “guardian” process that relaunches it if it is killed.

  5. Four-layer persistence. It installs itself four independent ways, so removing any one leaves the other three:

    • a cron job (/etc/cron.d/net-check),
    • the systemd service from Step 3 (cgroup-fs.service),
    • a login-profile hijack (/etc/profile.d/fs-cgroup.sh, which runs every time someone logs in),
    • and the SSH key from Step 3.
  6. Worming outward. It reads SSH keys harvested from the host and uses masscan (a high-speed internet port scanner) to find more SSH servers, then tries to spread to them using the stolen keys, turning each victim into the next attacker.

  7. The credential sweep, the real payload. It enumerates and reads credentials from 18 categories and POSTs them to a dedicated exfil port (667), kept separate from the mining traffic on other ports. The categories include: SSH private keys; cloud-metadata tokens for AWS, Google Cloud, Azure, Alibaba, and Tencent; Kubernetes service-account tokens; Docker registry logins; cryptocurrency wallets; database credentials; CI/CD publishing tokens (npm, PyPI); and, new for 2026, AI coding-tool configurations, reading files such as ~/.claude/settings.json and the contents of ~/.cursor/.

That last category is the evolution to watch. As developers wire AI assistants into their workflows, the API keys and configs those tools store on disk become just another secret worth stealing, and this toolkit already knows to look for them. This is a credential harvester that happens to also mine.


Step 5: The mining relays, laundering stolen compute

The miner does not connect straight to a public mining pool. The C2 host (31.56.48[.]179) also runs the operator’s own stratum proxies on ports 80, 443, 3333, and 8080 (stratum is the protocol miners use to talk to a pool). Every infected machine points at these proxies, which aggregate all the stolen hashpower and forward it upstream to a public pool (gulf.moneroocean[.]stream) under a single Monero wallet.

Why bother with the middle layer? Two reasons. It means the public pool only ever sees the operator’s proxies, not the thousands of victim IPs, so a pool-side abuse report or ban hits the proxy, not the botnet. And it gives the operator a single switch: change where the proxies forward, and the entire fleet follows, without touching a single victim. It is the same resilience logic as the live C2 migration in Step 2, applied to the payout path.


The operator

The toolkit is unusually talkative about itself. Its own source comments name the group (# TeamPCP v21) and even credit the campaigns whose techniques it borrowed: # Pattern: 0cl/boatnet(ELLIO)+TeamTNT+PCPJack+RedTail+Perfctl+Kiss-a-dog. That places it squarely in the TeamTNT lineage of container-cryptojacking, borrowing container escape from PCPJack, persistence from RedTail, process hiding from Perfctl, and credential harvesting from Kiss-a-dog. The “v21” revision and the structured, modular code indicate an actively maintained toolkit, not a one-off script.

The operator made several mistakes that hand defenders durable leverage:

  • It named itself and its sources. The comments above identify the group, the build revision, and every technique family reused.
  • The migration leaked prior infrastructure (Step 2): a dev/test box, a retired Vultr C2, and a scanner that doubled as an early C2.
  • The miner binary leaks its build pipeline. It was compiled from a CI/CD path /home/buildbot/xmrig/ dated 2024-11-03, a build-environment anchor that links any other binary from the same pipeline.
  • Stable cross-victim fingerprints. The SSH key comment 41832@CAI and the Monero wallet are fixed across victims, usable to track the operator across many compromises (the wallet’s pool-side earnings are noted under Monero wallet below).

Command-and-control infrastructure

All characterization is from passive sources only, we did not probe the operator’s infrastructure.

Indicator Role Note
31.56.48[.]179 (ports 666, 667, 80/443/3333/8080) Primary C2, dropper delivery (666), credential exfil (667), self-hosted stratum proxies Capture-exclusive
95.182.96[.]193:8888 Fallback C2 (miner config) Already public (ThreatFox)
120.26.46[.]187 Scanner / exploiter (Alibaba Cloud, CN) Already public (GreyNoise)
45.76.202[.]47 Prior C2, rotated out (Vultr, JP) Retrospective-hunting value
192.168.1.34 Operator’s own dev/test box (private address) Leaked in the migration
gulf.moneroocean[.]stream Upstream public mining pool Commodity

The primary C2 sits on AS56971 (CGI GLOBAL LIMITED), an abuse-prone reseller; the scanning leg sits on Alibaba Cloud in China. These are different networks in different jurisdictions: the cloud-scanning leg is disposable compute, while the load-bearing anchor is the shared C2 autonomous system. The geolocation of that C2 network is contested across data sources, so we do not assert a single country for it, the verified anchor is the network (ASN), not a flag.

Payload URLs (defanged): hxxp://31.56.48[.]179:666/w (dropper) · hxxp://31.56.48[.]179:666/.real_mnd (miner) · hxxp://31.56.48[.]179:666/.mconf (config)


Sample hashes

Artifact SHA-256 Size Note
Dropper /w 0f63bea320d768fb12bb53a287f210b8b9ccec563ac66dc80b7967628e455566 21,060 B Capture-exclusive
Miner config .mconf 27cc6cf232ba7ed8dc92dcb0795bdb7185197928ec3061a8d6de097f9efc5440 1,035 B Capture-exclusive
XMRig .real_mnd b0e1ae6d73d656b203514f498b59cbcf29f067edf6fbd3803a3de7d21960848d 8,297,712 B Reused commodity (in MalwareBazaar since 2024-12, tagged Mirai)

The dropper and config hashes are exclusive to this capture and absent from public malware repositories. The XMRig binary is reused commodity, useful as a known-bad, but its presence alone does not identify this campaign.


Monero wallet

85BVSXMzxy74NTecwdiNbZZ83gxibFSszZhwCHBwcCCfW2xdrTWzMT14EgGihhQWC9fi3m5ZNfFAcaR4u5QYFP19ArnSAsK

As of publication (June 2026) this wallet was still actively mining on the MoneroOcean pool and had earned roughly 0.018 XMR to date, a small sum consistent with a limited number of infected hosts rather than a large botnet. Monero is a privacy coin, so the address itself yields no on-chain balance or transaction history; the figure above is the mining pool’s own published per-wallet statistic.


Timeline

When Event
Nov 2024 XMRig payload compiled (/home/buildbot/xmrig/, build date 2024-11-03)
May 2026 CVE-2026-31431 added to CISA Known Exploited Vulnerabilities catalog
June 2026 Full chain captured, including the live C2 migration; dropper, config, and miner recovered

The full automated chain, recon to host takeover, runs in about 45 seconds.


What defenders should look for

Behavioral rules first (durable), then host artifacts, then atomic indicators (cheap for the operator to change), then what will not work.

Behavioral (most durable)

  1. The entry signature: any Docker containers/create with "Privileged": true and a bind mount of the host root ("Binds": ["/:/host..."]). This combination is the single highest-value alert; it has almost no legitimate use.
  2. In-container sed -i edits to files under /opt/.cgroup-fs/ (the live C2-rewrite behavior).
  3. A systemd unit named cgroup-fs* whose start command fetches from a raw IP address.
  4. A login script under /etc/profile.d/ that launches a binary from /dev/shm/ (a memory-backed temp directory attackers favor).
  5. Cloud security/monitoring agents being stopped or removed on a host that should be running them.
  6. A burst of outbound SSH connection attempts preceded by a masscan-style port sweep (worm spreading).
  7. Outbound HTTP POSTs to an unusual high port (here, 667) carrying credential-shaped data, distinct from any mining traffic.

Host artifacts

/etc/systemd/system/cgroup-fs.service · /etc/cron.d/net-check · /etc/profile.d/fs-cgroup.sh · /opt/.cgroup-fs/miner.c · /dev/shm/.x · an SSH authorized_keys entry with the comment 41832@CAI.

Atomic indicators (see IOC CSV)

C2 IP and ports, payload URLs, sample hashes, the Monero wallet, the client User-Agent strings (python-requests/2.34.2, docker-sdk-python/7.1.0).

What will NOT detect this

  • Cloud-console alerts alone, the toolkit removes the cloud monitoring agents early (Step 4.2).
  • Hash-matching the miner, the XMRig binary is reused commodity shared with unrelated campaigns; matching it tells you little.
  • IP reputation alone, the primary C2 is absent from commodity feeds at publication.

Detection content

Shipped alongside this brief:

  • A YARA rule for the dropper.
  • Sigma rules for the systemd-persistence unit and for the privileged- container-with-host-bind Docker API call.
  • A machine-ingestible IOC CSV (defanged).

Recommendations

Close the door. Never expose the Docker daemon API to the internet. Bind it to localhost or a protected management network, require TLS client authentication, and confirm ports 2375–2377 are not reachable from outside. The same applies to the Kubernetes API.

Patch. CVE-2026-31431 is a CISA KEV entry used here for the host escalation step. Patch Linux kernels on internet-facing and container hosts.

Hunt. Run the behavioral checks above across your container and Linux fleet, and search for the host artifacts, especially the cgroup-fs* service, the 41832@CAI SSH key, and sed edits under /opt/.cgroup-fs/. The retired Vultr C2 (45.76.202[.]47) is worth a retrospective sweep of historical logs.

Block. Block the primary C2 (31.56.48[.]179) at egress and deploy the Sigma and YARA rules.


Closing

The mining is the noise; the credential harvest is the signal. In one automated 45-second run, a misconfigured Docker port became root on the host, four independent footholds, a reach into the Kubernetes cluster above it, and 18 categories of stolen secrets, now including the configs of developers’ AI coding tools. And because we caught the operator rewriting their own infrastructure mid-migration, the campaign handed us a map of where it had been: a dev box, a retired C2, and a scanner that used to double as a callback.

The lesson for defenders is the oldest one in container security, sharpened: an exposed daemon API is not a configuration nit, it is host-level remote code execution waiting for the first scanner to find it, and in 2026, the scanner that finds it is automated, infrastructure-aware, and after far more than your CPU cycles.


State of the Attack is a threat-research program publishing curated technical findings for defenders. Indicators are defanged; locations are given by region only.

Indicators of Compromise

  • 31[.]56[.]48[.]179
  • 95[.]182[.]96[.]193
  • 120[.]26[.]46[.]187
  • 45[.]76[.]202[.]47
  • gulf[.]moneroocean[.]stream
  • hxxp://31[.]56[.]48[.]179:666/w
  • hxxp://31[.]56[.]48[.]179:666/.real_mnd
  • hxxp://31[.]56[.]48[.]179:666/.mconf
  • 666
  • 667
  • 8888
  • 0f63bea320d768fb12bb53a287f210b8b9ccec563ac66dc80b7967628e455566
  • 27cc6cf232ba7ed8dc92dcb0795bdb7185197928ec3061a8d6de097f9efc5440
  • b0e1ae6d73d656b203514f498b59cbcf29f067edf6fbd3803a3de7d21960848d
  • /etc/systemd/system/cgroup-fs.service
  • /etc/cron.d/net-check
  • /etc/profile.d/fs-cgroup.sh
  • /opt/.cgroup-fs/miner.c
  • /dev/shm/.x
  • 41832@CAI
  • 85BVSXMzxy74NTecwdiNbZZ83gxibFSszZhwCHBwcCCfW2xdrTWzMT14EgGihhQWC9fi3m5ZNfFAcaR4u5QYFP19ArnSAsK
  • python-requests/2.34.2
  • docker-sdk-python/7.1.0
  • CVE-2026-31431
  • /v1.54/images/json
  • Docker containers/create with Privileged true and Binds containing /:/host:rw

Detection Rules & IOCs

Four rules across three detection tiers. A SOC analyst imports all four: together they cover the campaign at initial access (Docker API abuse), host persistence (systemd + cron + profile.d + SSH key), and the dropper payload itself.

Sigma: Docker Privileged Container with Host Root Bind Mount
docker-privileged-host-mount-create.yml
DetectsThe container-escape entry signature — a Docker containers/create call with Privileged:true and host root bind mount
DeployDocker daemon audit logs
When it firesWhen any Docker API call creates a privileged container with /:/host bind mount — the single highest-value alert for this campaign
Sigma: TeamPCP Host Persistence and SSH Backdoor
cgroup-fs-persistence-and-backdoor.yml
DetectsHost-level persistence artifacts: cgroup-fs.service, net-check cron, profile.d hijack, 41832@CAI SSH key
DeployLinux file-event logs (auditd, Sysmon for Linux, osquery)
When it firesWhen any of the four persistence paths or the SSH key marker appear on a host
YARA: TeamPCP v21 Dropper
scattershell-teampcp-v21-yara.yar
DetectsThe TeamPCP v21 dropper script by source-code markers and persistence-path strings
DeployFile scanning (YARA on endpoints, sandbox, or network capture)
When it firesWhen a file matches the TeamPCP v21 self-identification strings or the persistence + backdoor artifact cluster
IOC CSV
scattershell-teampcp-v21-iocs.csv
DetectsMachine-ingestible indicator list (defanged IPs, hashes, URLs, file paths, behaviors)
DeploySIEM, TIP, EDR blocklist import
When it firesImport into your threat intelligence platform for automated matching

In plain terms

  • Sigma (Docker) — "is something creating a privileged container with host root access?" (daemon level, inbound)
  • Sigma (host) — "has TeamPCP persistence landed on this Linux host?" (file-event level)
  • YARA — "is this file the TeamPCP v21 dropper?" (file level)
  • IOC CSV — machine-ingestible indicator list for SIEM/TIP import

Frequently asked

What is TeamPCP v21?

Docker exposes a management interface, the Docker daemon API, that lets you create, start, and control containers. When it is accidentally published to the internet with no authentication, anyone who can reach that port can take over the host underneath it. We watched a fully automated toolkit do exactly that in about 45 seconds.

What vulnerability does TeamPCP v21 exploit?

TeamPCP v21 is delivered by exploiting cve-2026-31431. The full exploitation chain is documented in the analysis above.

What are the indicators of compromise (IOCs) for TeamPCP v21?

Key indicators include 31[.]56[.]48[.]179, 95[.]182[.]96[.]193, 120[.]26[.]46[.]187, 45[.]76[.]202[.]47, gulf[.]moneroocean[.]stream, hxxp://31[.]56[.]48[.]179:666/w, and more. The full list and a downloadable IOC CSV are in the Detection Rules & IOCs section.

How do I detect TeamPCP v21?

TeamPCP v21 can be detected with Sigma: Docker Privileged Container with Host Root Bind Mount, Sigma: TeamPCP Host Persistence and SSH Backdoor, YARA: TeamPCP v21 Dropper, IOC CSV — all downloadable on this page. Four rules across three detection tiers. A SOC analyst imports all four: together they cover the campaign at initial access (Docker API abuse), host persistence (systemd + cron + profile.d + SSH key), and the dropper payload itself.

What MITRE ATT&CK techniques does TeamPCP v21 use?

TeamPCP v21 maps to T1190, T1610, T1609, T1611, T1068, T1543.002, T1053.003, T1546.004, T1098.004, T1552.005, T1552.001, T1528, T1562.001, T1027, T1046, T1613, T1021.004, T1496.

External references

These indicators are published to the threat-intelligence community. Verify or pull them from:

Browse by
CVE-2026-31431TeamPCP v21 toolkitXMRig 6.22.2masscanLive C2 rotation via sed patch of /opt/.cgroup-fs/miner.c on pre-compromised hostsFive-method fetch fallback (bash /dev/tcp, busybox wget, curl, wget, python3 urllib; each x2)Credential harvesting + Monero cryptojacking via Docker API container escape, with Kubernetes cluster-wide propagation
dockercontainer-escapecryptojackingcredential-harvestkubernetesteampcpteamtnt-lineage
Subscribe for new analysis Compare with another campaign