By Alex Delamotte, with Ian Ahl (Permiso) and Daniel Bohannon (Permiso)
In December 2022, the threat research team at Permiso Security reported about a cloud credential stealer campaign that primarily targeted Amazon Web Services (AWS) credentials from public-facing Jupyter Notebooks services. The actors likely accessed these impacted services through unpatched web application vulnerabilities.
From June 14, 2023 through the end of the month, we worked with the Permiso team to track and analyze files related to a new incarnation of this campaign targeting exposed Docker services. The hallmark shell scripts remain the core of these campaigns, though we also identified an Executable and Linkable Format (ELF) binary written in Golang. The research team at Aqua also recently reported elements they observed from these actors’ abuse of Docker images.
SentinelLabs thanks the Permiso Security research team for their collaboration on the research in this report. The Permiso team released a blog about this campaign, which can be found here.
Since the December campaign, the actor has made several updates to how their tooling works.
The December campaign targeted AWS credentials; the most recent campaigns added functions that target credentials from Azure and GCP. The actor actively modified these features as the campaigns evolved throughout June: Initially, a script aws.sh
contained references to Azure credentials, but the relevant function was not called. A week later, samples emerged where the Azure credential functions were called.
The actor stored the generic credentials in an array labeled CRED_FILE_NAMES
. The AWS-specific array from the original script ACF has been replaced with AWS_CREDS_FILES
. We dive into this in more detail in the next section. There are also two new cloud service provider (CSP)-specific credentials variables: GCLOUD_CREDS_FILES
and AZURE_CREDS_FILES
.
The actor made the script more modular as it grew larger and more complex. The AWS functionality is now split into three smaller functions that are driven by the run_aws_grabber
function only if the system is identified as AWS. This increases the efficiency of the script by running AWS commands only on AWS systems, which also enhances the script’s stealth.
The actor no longer hosts files in an open directory, which complicates efforts to track and analyze these campaigns. Instead, C2 activity relies on a hardcoded username and password combination that are passed as arguments to the curl
command.
The older campaign infrastructure was hosted on a Netherlands-based IP associated with Nice IT Services. The attacker has since moved infrastructure to AnonDns, a dynamic domain name service (DDNS) provider. The campaigns through June 2023 use one of several AnonDNS subdomains:
everlost.anondns.net silentbob.anondns.net ap-northeast-1.compute.internal.anondns.net
The newer versions target credentials in newly added arrays GCLOUD_CREDS_FILES
and AZURE_CREDS_FILES
. The versions emerging the week of 6/26/2023 added .env
and docker-compose.yaml
; the version from 6/15/2023 has env without the period, so the actor is apparently updating the tool to be more effective in the newest campaign. The newest campaign also has a new variable, MIXED_CREDFILES which contains only redis.conf
.
The newer versions omitted the following credentials files that were present in the December campaign’s ACF:
cloud .npmrc credentials.gpg
The credentials collection logic in the new campaign’s samples targets the following services & technologies:
Technology | Targeted File |
Amazon Web Services | .boto, .passwd-s3fs, .s3b_config, .s3backer_passwd, .s3cfg, credentials, s3proxy.conf |
Azure | azure.json |
Google Cloud Platform | .feature_flags_config.yaml, .last_opt_in_prompt.yaml, .last_survey_prompt.yaml, .last_update_check.json, access_tokens.db, active_config, adc.json, config_default, config_sentinel, credentials.db, gce |
Censys | censys.cfg |
Docker | docker-compose.yaml |
Filezilla | filezilla.xml, recentservers.xml, queue.sqlite3 |
Git | .git-credentials |
Grafana | grafana.ini |
Kubernetes | clusters.conf, kubeconfig, secrets |
Linux OS | .netrc, netrc |
Ngrok | ngrok.yml |
PostgresQL | .pgpass, postgresUser.txt, postgresPassword.txt |
Redis | redis.conf |
S3QL | authinfo2 |
Server Message Block (SMB) | .smbclient.conf, .smbcredentials, .samba_credentials |
Uncategorized | .env, accounts.xml, api_key, resource.cache, servlist.conf |
There is considerable overlap in the targeted files between these credential stealer campaigns and the TeamTNT Kubelet-targeting campaign reported by Sysdig in October 2022.
The script uses the cred_files
function to search for credentials files on the system, write them to a temporary file $EDIS
, copy the new file to a master credential-holding file $CSOF
, then delete the temporary file. The $EDIS
and $CSOF
variable file names and paths are randomly generated via the special use Bash variable $RANDOM
, meaning the value is an integer between 0 and 32767 that changes each time $RANDOM
is accessed.
The new scripts show more attention to making the features modular, a natural evolution as a script becomes more complex. The AWS-specific functionality is driven by a function named run_aws_grabber
. Most AWS-centric features from the December campaign have been rolled into one of four functions driven by run_aws_grabber
:
get_aws_infos
: Queries the AWS instance metadata service (IMDS) for IAM configuration and sets the output to $AWS_INFO
, as well as security credential configuration from EC2 and IAM resources, which are set to $AWS_1_EC2
and $AWS_1_IAM_NAME
, respectively.get_aws_meta
: Writes the values from each of the variables generated in get_aws_infos
then parses the data for specific values via grep and extracts them using sed, writing the output to the $CSOF variable.get_aws_env
: Checks for values in AWS credential related variables, writes them to $CSOF
when present. When the $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
is found, the function calls curl
against the URL, then modifies the response using sed to format specific values into an aws configure set command. For example, the string AccessKeyId
in the response is transformed to aws configure set aws_access_key_id
. The actor likely chose to format the values as a command so that the output feeds into additional automated actions.get_awscli_data
: This function is only implemented in the two most recent versions: the function exists in the 6/15/2023 version of aws.sh
, but it is not called. The function invokes aws sts get-caller-identity
to collect the 12-digit AWS account identifier and writes the result to $CSOF
.A notable recent addition is logic specific to the Azure and Google Cloud platforms. The get_azure
and get_google
functions are implemented in the newest versions seen on 06/26/2023; the logic was present in the 6/15 campaign, but the functions were not called. These changes indicate that these features are being actively developed, so we expect more changes as the actors roll out and test these features.
The attackers now perform system profiling through the aws.sh
scripts as well as other scripts delivered under certain conditions. Another new feature is the get_docker
function, which checks if the environment is a Docker container. When it is, the function runs docker inspect
against each running container and saves the result to $CSOF
. The output will not necessarily have credentials and this likely serves as a mechanism for system profiling.
Additionally, the new version added the function get_prov_vars
, which calls cat /proc/*/env*
to collect environment variable details from each running process and writes the result to $CSOF
. The actor likely does this to enumerate other valuable services running on the system for manual targeting.
We also observed profiling activity from Data.sh
, a post-exploitation script that collects details from the system and sends it to the attacker’s server. The script uses Bash to craft a web request to download the curl
binary from the attacker’s server through the bashload
function. This is notable because attacks against minimal systems–such as containers–can be limited by the absence of ubiquitous binaries like curl
.
The attacker sets variables for a lockfile and datafile in /var/tmp
. The result of the following reconnaissance commands is written to the datafile:
whoami | Current user |
ls -al | Lists all files in the current directory |
who | List of users with active terminal sessions |
lastlog | Log of user login history |
cat /var/spool/cron/* | Contents of configured cron jobs |
ps aux | Details about all running processes |
netstat -anop | Network connection and socket details |
docker ps | List of Docker containers, including stopped containers |
The script then sends the results collected in the datafile to the C2 using curl with a provided username and password.
After collecting and processing the credentials, the credentials stealing scripts use curl
to exfiltrate the contents of the $CSOF
file to an AnonDNS-hosted server. The script contains hardcoded credentials that are used to authenticate the request. The June 2023 campaigns use the following username, password, and server URL combinations:
SHA1 | 5611cb5676556410981eefab70d0e2aced01dbc5 |
Name | aws.sh |
Username | jegjrlgjhdsgjh |
Password | oeireopüigreigroei |
Exfil URL | http[:]//everlost.anondns.net/upload.php |
SHA1 | 61da5d358df2e99ee174b22c4899dbbf903c76f0 |
Name | aws.sh (newer) |
Username | 1234 |
Password | 5678 |
Exfil URL | http[:]//silentbob.anondns.net/insert/keys.php |
SHA1 | ac78d5c763e460db2137999b67b921e471a55e11 |
Name | g.aws.sh |
Username | 1234 |
Password | 5678 |
Exfil URL | http[:]//ap-northeast-1.compute.internal.anondns.net/insert/keys.php |
SHA1 | dba0dcb8378d84abc8f7bf897825dd4f23e20e04 |
Name | data.sh |
Username | 8765 |
Password | 4321 |
Exfil URL | http[:]//everlost.anondns.net/data.php |
In addition to the usual shell scripts, we observed the actor delivering a UPX-packed, Golang-based ELF binary. The binary ultimately drops and executes another shell script that scans an actor-specified range and attempts to propagate to vulnerable targets. We believe the reason the actor used this binary to deliver yet another script is due to the relatively noisy nature of the scanning activity. The scanner is hidden as an embedded base64 object within the packed Golang binary, adding more stealth than a standalone shell script. Additionally, the binary drops Zgrab–a Golang network scanning tool–which depends on Golang environment variables that are set by running the parent Go binary.
The implemented code enables the binary to read a command from a string and execute it using os_exec
.
The main_main
function decodes an embedded base64 blob, resulting in a Bash script that is written and then executed by the main_runCommand
function. In the embedded script, the setupsomething
function downloads the following packages on systems using the Yum package manager:
gcc make git jq
libpcap libpcap-devel curl
This function also downloads the following packages on systems that use the Apt package manager:
gcc make git jq
libpcap0.8 libpcap0.8-dev
masscan curl
Next, setupsomething
checks if masscan
, docker
, and zgrab
are installed. If not, the script downloads the dependencies from the attacker’s server, hosted at the URI: /bin/[bin_name]
.
The dAPIpwn
function takes the following arguments:
/gr.php
The function passes these arguments to masscan
, which scans the specified IP ranges then passes the results to zgrab
, which looks for http responses from the remote endpoint /v1.16/version
. The output is filtered using grep to search for lines containing the strings 'ApiVersion'
or 'client version 1.16'
. Aqua also detailed a step in the attack chain that looks for misconfigured Docker daemons running version 1.16. Interestingly, a Shodan search revealed only apparent honeypot systems responding with these strings on the specified ports.
When a system is deemed vulnerable, the script calls back to the C2 using curl with the vulnerable IP address and port added to the request URI.
This campaign demonstrates the evolution of a seasoned cloud actor with familiarity across many technologies. The meticulous attention to detail indicates the actor has clearly experienced plenty of trial and error, shown in choices like serving the curl binary to systems that do not already have it. The actor has also improved the tool’s data formatting to enable more autonomous activity, which demonstrates a certain level of maturity and skill.
While AWS has long been in the crosshairs of many cloud-focused actors, the expansion to Azure and GCP credentials indicates there are other major contenders holding valuable data.
We believe this actor is actively tuning and improving their tools. Based on the tweaks observed across the past several weeks, the actor is likely preparing for larger scale campaigns. The lack of threats explicitly targeting Azure and GCP credentials up to this point means there are likely many fresh targets. The current focus on Docker is ultimately arbitrary: this actor has previously targeted other technologies and there are many other oft-forgotten vulnerable applications.
Organizations can prepare against these attacks by ensuring that applications are configured properly and patched as security fixes become available. Docker access should be restricted to suit your organization’s needs while reducing exposure from outside connections.
SHA1 | Description |
0e1805fd9efa6a1c3fe9adb3f34373a9dcc7fe19 | run.sh |
18d28ac44c5501f1768f0fc155ad38aa56610881 | chattr ELF binary |
27414df2f9a687db65d2bc5fed011a1f0f550417 | aws.sh v3 |
2ed9517159b89af2518cf65a93f3377dea737138 | UPX-packed Golang ELF binary that drops scanner script |
37cb34a044c70d1acea5a3a91580b7bfc2a8e687 | ELF binary, potentially Tsunami |
3d6aaed47135090326780727fef57ce1c1573aa2 | tmate.sh |
5611cb5676556410981eefab70d0e2aced01dbc5 | aws.sh v2 |
6123bbca11385f9a02f888b21a59155242a96aba | user.sh |
61da5d358df2e99ee174b22c4899dbbf903c76f0 | aws.sh v5 |
63fe964140907470427e035bdba5230f6a302056 | b.sh (Install script) |
654be7302f4a3638929fe5e67f6f2739a1801b07 | clean.sh |
828960576e182ec3206f457a263f25ee0531edbb | curl.full |
863bf9617f82c9c595cc9b09e84a346a306060c2 | Embedded script from binary with dAPIpwn function capability |
8802f1bf8f83e354f14686fe79b5018cd36eb77f | aws.sh v6 |
ac78d5c763e460db2137999b67b921e471a55e11 | aws.sh v4 |
b13d62f15868900ab22c9429effdfb7939563926 | aws.sh v7 |
c9edc82bc3ac344981231965bedec300fec31b1f | xc3.sh |
d79970f66a56f69667284c4c937f666758200ab4 | grab.sh |
dba0dcb8378d84abc8f7bf897825dd4f23e20e04 | data.sh profiling script |
eb3dff13ed97670e06649e8daaa6e4ab655477f6 | aws.sh v1 |
f437aeac3721a0038c936bab5a2ac1ccdb0cf222 | int.sh |
43Lfq18TycJHVR3AMews5C9f6SEfenZoQMcrsEeFXZTWcFW9jW7VeCySDm1L9n4d2JEoHjcDpWZFq6QzqN4QGHYZVaALj3U
ap-northeast-1.compute.internal.anondns[.]net everlost.anondns[.]netsilentbob.anondns[.]net everfound.anondns[.]net
207.154.218.221 45.9.148.108
http[:]//silentbob.anondns.net/bin/chattr http[:]//silentbob.anondns.net/bin/a http[:]//silentbob.anondns.net/cmd/grab.sh http[:]//silentbob.anondns.net/cmd/clean.sh http[:]//silentbob.anondns.net/cmd/aws.sh http[:]//silentbob.anondns.net/cmd/xc3.sh http[:]//silentbob.anondns.net/bin/sysfix/curl.full http[:]//silentbob.anondns.net/bin/chattr http[:]//silentbob.anondns.net/insert/gscat.php http[:]//silentbob.anondns.net/insert/tmate.php