Simulate an Attacker Mining Crypto with User Data

The user-data field isn't only for passing in secrets — it can tell an instance to run commands. Today we'll learn how attackers abuse it.

Prerequisites

  • You must have GuardDuty configured, and an EventBridge rule to send you an email for GuardDuty (or SecurityHub) alerts.

The Lesson

In our last lab, Explore the Power and Pain of User-Data, we learned about the user-data field and how we can use it to pass information into instances when we launch them. We embedded secrets, and then explored how they could be exposed if someone compromised an instance and poked around its metadata. We also saw how CloudTrail logs mask user-data, which can make it a pain to investigate after the fact.

📣 Shout Out Time!

I want to thank Daniel Grzelak for providing some behind the scenes feedback on user-data (and cloud-init). You should absolutely check out his newsletter, AWS Security Digest, if you want to keep up on the latest AWS security news and changes. That’s my favorite part — he doesn’t just link to stories, he also has sections for API, IAM, and other changes. Some of the security things that pop up in those are impressive, and surprise even us tired old cloud security professionals.

In that post I mentioned two uses for user-data (or user data — sometimes I forget I’m not writing command lines):

  • To pass information/data into an instance/container, such as license keys or… BUT DON’T PASS IN SECRETS — YOU KNOW BETTER NOW!

  • To run commands when an instance launches.

How do you run commands on boot? Easy, assuming the operating system you are running supports cloud-init.

Cloud-init is a widely used open-source project to enable initialization and configuration of cloud instances. Developed in 2009 by Canonical, the company behind Ubuntu, cloud-init has become a standard tool for customizing cloud instances across various platforms — including Amazon Web Services (AWS). It enables users to automate the process of setting up a new instance by executing scripts and applying configurations at launch time.

My first automations used cloud-init to customize EC2 instances. This was back when it was painful to create your own AMIs. I could configure an entire complex application stack in an existing AMI using cloud-init. Here’s a snippet of an example that deploys WordPress into an instance (truncated to save space):

#cloud-config
package_update: true
package_upgrade: true

packages:
  - httpd
  - php
  - php-mysqlnd
  - mysql
  - mysql-server
  - php-gd
  - php-mbstring

runcmd:
  - systemctl start httpd
  - systemctl enable httpd
  - systemctl start mysqld
  - systemctl enable mysqld
  - wget https://wordpress.org/latest.tar.gz
  - tar -xzf latest.tar.gz
  - cp -r wordpress/* /var/www/html/
  - chown -R apache:apache /var/www/html/
  - chmod -R 755 /var/www/html/
  - mkdir -p /var/www/html/wp-content/uploads
  - chown -R apache:apache /var/www/html/wp-content/uploads
  - systemctl restart httpd
... and so on...

This is clearly a powerful tool for attackers who can compromise credentials with privileges to run an instance!

Why can this be a tricky attack? It’s often abused for installing cryptomining and malware into someone’s environment, in a way that is harder to detect than running a pre-built image loaded with attack tools. An attacker runs a standard image that defenders allow, but uses cloud-init to give themselves access and/or run automated (or manual) attack tools.

As you saw last week, user-data doesn’t show up in CloudTrail logs, so it isn’t like you can write a rule in your security monitoring tooling to “alert me if some jerk tries to use user-data to mine Dogecoin”. (I call that the ElonIsHere1 rule in my tool).

I largely moved away from using cloud-init because it’s much easier to build custom images these days — especially with build pipelines. In fact I think I mostly only use it to run pretend attacks in training. But it does still serve a purpose, it can be a quick way to test some things out, and sometimes you need it to trigger auto-registration of new instances with a service or other first-run automations.

Funny (to me) aside… I was pulling together materials for this lab and my search turned up a Securosis post from 2013 on cloud-init! I have no memory of writing it, but I wrote thousands of posts over… checks notes… 17 years.

Key Lesson Points

  • Cloud-init is a software package installed on many cloud images to run configuration scripts when an instance first runs.

  • If an attacker has the right IAM privileges to run an instance, they can abuse cloud-init to run their own code.

  • This is often used for cryptomining.

The Lab

This is another fun one, which ties together a lot of what we’ve been doing. We will set up our environment with the same CloudFormation template as last week. Then we’ll launch a new instance and run a special command using cloud-init, by passing in special commands using user-data.

This command is a special one we use for testing whether GuardDuty is running in an account. It calls out to a cryptomining URL, which should trigger a GuardDuty alert within about 20 minutes. I tested it in my account and it worked perfectly.

Video Walkthrough

Step-by-Step

First set up our environment with our little friend, CloudFormation. Log into your Sign-in portal > TestAccount1 > AdministratorAccess > CloudFormation > Create stack, and use these details:

Once it launches go to EC2 > Instances > Launch instances, and follow the screenshots.

Name it Miner49er because you love dad jokes as much as I do:

Choose Proceed without a key pair and throw it on the public subnet (the only one), in the default security group:

This next one is optional. You don’t need to log into the instance, so we don’t need the SSMClient role, but if you want it so you can log in on your own and poke around, I won’t get in your way:

Keep scrolling all the way down to the user data field. Then copy this line and paste it in the box, then Launch instance:

#cloud-config
runcmd:
  - "curl -s http://xmr.pool.minergate.com/dhdhjkhdjkhdjkhajkhdjskahhjkhjkahdsjkakjasdhkjahdjk > /dev/null &"

This tells the instance “when you first launch, connect to this URL and pipe the output to nowhere.” That URL is a (currently) defunct bitcoin mining pool. One of GuardDuty’s features is detecting mining by detecting connections to mining pools.

Now what?

Patience, Grasshopper. You should get an email in 20 or so minutes warning you of cryptomining in your account, if you set things up correctly.

You do not actually need to wait for the alert to terminate the instance and delete your CloudFormation stack. This is the magic of cloud-init — it runs instantly on boot.

Now you’ve learned the second risk of user-data: attackers can use it to inject commands into new instances. In our example it’s cryptomining, one of the most common real-world attacks. As you see, they could also use cloud-init to load up attack toolkits, backdoors, or photos of cats.

GuardDuty Findings

Lab Key Points

  • Cloud-init runs commands, including complex scripts, when an instance launches for the first time.

  • Anyone with access to run an instance and provide user-data can use cloud-init.

  • Note that instances in private subnets, without a NAT Gateway, can’t connect to the Internet and thus can’t be used for mining or botnets, but smart attackers still get creative.

-Rich

Reply

or to participate.