Keypoints
- Exposed Docker Remote API servers were abused to create privileged containers and deploy the perfctl malware.
- Attackers used the ubuntu:mantic-20240405 image to run a container named “kube-edagent” in privileged mode with pid=host to enable host interaction.
- Payload delivery relied on Docker Exec to run nsenter (to escape the container) and a Base64-encoded shell script that performs environment setup and downloads a malicious binary.
- Evasion measures included checks for duplicate processes, process and TCP connection verification, and masquerading file names to avoid detection.
- Persistence was achieved by creating systemd services or cron jobs and by replacing /bin/sh with modified binaries to maintain a backdoor.
- Network traffic was observed routed through Tor (a captured relay at 192.121.108.237), and indicators include IPs, URLs, and file hashes tied to the campaign.
MITRE Techniques
- [T1133] External Remote Services – The attackers initially probed and pinged exposed Docker Remote API endpoints to gain access (‘begins with a ping to the Docker Remote API server’).
- [T1610] Deploy Container – The threat actor created a privileged container using the ubuntu:mantic-20240405 image to stage and run the payload (‘a container named “kube-edagent” was created from the ubuntu:mantic-20240405 base image’).
- [T1059.004] Command and Scripting Interpreter: Unix Shell – A Base64-encoded shell script was executed inside the container to perform setup, checks and downloads (‘The second part contains a Base64 encoded shell script’).
- [T1611] Escape to Host – The attackers used nsenter with multiple namespace flags to escape the container and operate on the host (‘using the “nsenter” command to enter the specific namespaces of the target process with PID 1’).
- [T1543.002] Create or Modify System Process: Systemd Service – The malware creates a systemd service (multi-user.target) when systemd is available to persist across reboots (‘it creates a systemd service using “multi-user.target”‘).
- [T1053.003] Scheduled Task/Job: Cron – When systemd is not available, the actor falls back to creating a cron job to maintain persistence (‘Otherwise, it resorts to creating a cron job’).
- [T1036.005] Masquerading: Match Legitimate Name or Location – Files and processes are named to resemble legitimate components (e.g., creating “kube-edagent” and downloading a binary named “httpd”) (‘creates a container named “kube-edagent,” deliberately giving it a name similar to a legitimate container’).
- [T1082] System Information Discovery – The payload checks system architecture and process state (e.g., verifying /proc and PID files) before proceeding (‘The script will terminate if the architecture is not “x86_64.” … It confirms the presence of a malicious process by checking “/tmp/.xdiag/p,” which should contain the PID’).
- [T1132.001] Data Encoding: Standard Encoding – The main payload is Base64-encoded and decoded on the target to execute commands (‘The second part contains a Base64 encoded shell script’).
- [T1105] Ingress Tool Transfer – The actor downloads a malicious binary (disguised as a PHP extension and saved as “httpd”) to the compromised host using a custom downloader when curl/wget are unavailable (‘It downloads the file in the “/tmp” directory with the “httpd” name’).
Indicators of Compromise
- [IP Address] network infrastructure seen in the campaign – 46.101.139.173, 194.169.175.107 (and other related hosting IPs observed).
- [URLs] locations used to host payloads – http://46.101.139[.]173/main/dist/avatar.php, http://46.101.139[.]173/main/dist/viewstate.php (and other endpoints on the same host).
- [File Hashes] payload identification – 9fb8a70406d0c44a98ce8db9240661a85e0f3f09a6db4c3e0d6affb91c11d4b0, 22e4a57ac560ebe1eff8957906589f4dd5934ee555ebcc0f7ba613b07fad2c13 (the latter detected as Trojan.Linux.PERFCTL.A).
- [File Names / Paths] artifacts and script locations – /tmp/kubeupd, /tmp/.xdiag/vei, /tmp/.xdiag/p, and a downloaded binary named “httpd” in /tmp.
- [Network Relay] anonymization node observed in network capture – Tor relay 192.121.108.237 used to reroute traffic.
Recent activity shows an unknown actor exploiting unprotected Docker Remote API servers to install the perfctl malware. The campaign starts by probing Docker endpoints and, when a vulnerable host is found, creating a container named “kube-edagent” from the ubuntu:mantic-20240405 image. Attackers configure that container to run in privileged mode with the host PID namespace shared, often specifying a long-running command such as “sleep 9955” so the container process is easily identifiable. If the base image is not present locally, the actor pulls it from Docker Hub before creating the container.
After the container is running, the adversary uses Docker’s Exec API to run a two-part payload. The first component uses nsenter to join the target’s mount, UTS, IPC, network, and PID namespaces, effectively escaping the container and acquiring host-level capabilities. The second component is a Base64-encoded shell script that the attacker decodes and runs. That script begins by checking for duplicate processes (looking for patterns like “nsenter.*bash.*base64”) to avoid simultaneous installs, and it proceeds only if the environment matches expected criteria such as an x86_64 architecture.
The decoded payload writes a bash script called “kubeupd” to /tmp and sets an environment variable VEI to a value based on the Docker API public IP (e.g., “dck_”). The script ensures /tmp exists and is mounted with exec permissions, creates support directories such as /tmp/.perfc and /tmp/.xdiag, and copies VEI into /tmp/.xdiag/vei. It implements a custom __curl function so files can be fetched even if curl or wget are not available. Before downloading a binary, the payload verifies whether a malicious process is already running by checking the PID stored in /tmp/.xdiag/p and cross-referencing /proc/, and it also looks for active TCP connections on ports 44870 or 63582 as secondary confirmation.
If the checks show the payload is not already present, the script downloads a binary disguised as a PHP extension and saves it to /tmp/httpd to blend in with legitimate files. The download uses the custom __curl function when standard download tools are missing. The script then validates the downloaded file size (for example, 9,301,499 bytes in observed samples) before performing post-download actions, which include killing any running perfctl processes, setting execute permissions, updating PATH, and launching the binary in the background with an environment token (e.g., KRI=kr httpd). After successful installation steps, it removes marker files such as /tmp/.install.pid33 to tidy up traces.
To maintain persistence, the malware chooses between two approaches depending on the environment. If systemd is available and active, the actor creates a systemd service targeted at multi-user.target to restart the malicious component. If systemd is unavailable or unsuitable, the payload resorts to creating a cron job, a fallback that is difficult to remove if overlooked. In addition to service/cron persistence, the Base64 payload includes routines that directly modify system shells: a Restore_sh function replaces /bin/sh with a modified binary called /bin/kkbush and duplicates the original shell under names like kkbush and kbush to preserve functionality while enabling a backdoor. These modifications allow the malware to elevate privileges and execute arbitrary commands, with the Wait_run and Kill_container functions managing timing and cleanups (for example, killing the “sleep 9955” process used to mark the container).
Four main functions are embedded in the decoded payload: Kill_container, which terminates the placeholder “sleep 9955” process; Wait_run, which pauses until a run file like /tmp/k8s.run42 appears or a timeout occurs; Restore_sh, which swaps /bin/sh with a modified shell to evade detection; and Fallback, which establishes and maintains the backdoor by duplicating and restoring shell binaries and running the kubeupd background process. Network captures also show the attacker routing traffic through the Tor network, with a Tor relay IP (192.121.108.237) observed in packet traces.
Detection and mitigation should focus on preventing unauthorized access to Docker Remote API endpoints and on monitoring for the specific behaviors described above. Hardening measures include enforcing authentication and strong access controls for the Docker API, avoiding privileged containers and host PID namespace sharing unless absolutely necessary, and reviewing images and container configurations before deployment. Organizations should also monitor for unusual container creation requests, Docker Exec calls that run nsenter or decode Base64 payloads, unexpected binaries placed in /tmp (such as files named httpd), and the creation of suspicious systemd units or cron entries. Regularly applying Docker and OS security updates, educating operations and DevOps teams about secure Docker configuration, and performing routine audits will reduce the risk of successful exploitation.
Exposed Docker Remote API servers present a clear and avoidable risk: by understanding the attack chain—from probing to container creation, nsenter escapes, Base64 payload execution, bespoke download routines, and persistence through system modifications—defenders can prioritize effective controls to block and detect similar campaigns. Implementing access controls, monitoring, and patching will significantly reduce exposure to perfctl and related threats.