AWS crypto-mining on a compromised account: respond fast
GPU instances, a bill spike, a region nobody uses: confirm crypto-mining on AWS from CloudTrail and flow logs, contain it, and find how they got in.
TL;DR. Crypto-mining is usually the last step of an intrusion, not the first. Confirm it with RunInstances of GPU or large instance types in regions you do not use, often after RequestServiceQuotaIncrease, CreateKeyPair and a security group opened to 0.0.0.0/0; then with VPC Flow Logs showing long-lived outbound connections to mining-pool ports. Contain in every region (snapshot, then terminate), deactivate the credential that launched them, and work backwards to the entry point — the same key probably also created a backdoor user.
A cost alert on a Sunday morning is how many AWS compromises are discovered. Mining is noisy by design: it needs compute, lots of it, for as long as possible. MITRE ATT&CK files it under T1496 Resource Hijacking, with T1496.001 Compute Hijacking as the sub-technique.
What mining looks like in CloudTrail
The launch sequence is short and repetitive:
| Order | Event | What to read |
|---|---|---|
| 1 | ec2:DescribeRegions, DescribeInstances across regions | The attacker looks for unused regions |
| 2 | servicequotas:RequestServiceQuotaIncrease | quotaCode for vCPU / GPU families, desiredValue |
| 3 | ec2:CreateKeyPair or ImportKeyPair | SSH access to what comes next |
| 4 | ec2:CreateSecurityGroup, AuthorizeSecurityGroupIngress | Port 22 open to 0.0.0.0/0 |
| 5 | ec2:RunInstances | instanceType (p*, g* families, or simply the largest available), minCount, userData |
| 6 | Sometimes ec2:RequestSpotInstances, CreateLaunchTemplate, CreateAutoScalingGroup | Scale and resilience |
Variations exist — ECS or Fargate tasks, Lambda, SageMaker, Batch — but EC2 in an unused region remains the classic.
Two details worth knowing:
RunInstancesrecords theuserDataas<sensitiveDataRemoved>in CloudTrail, so you will not see the miner's install script there. Retrieve it from the instance (DescribeInstanceAttribute --attribute userData) before terminating.- Regions launched after 20 March 2019 are opt-in and disabled by default (AWS documentation); an
EnableRegioncall from the attacker is itself a signal.
What mining looks like on the network
With VPC Flow Logs on the attacker's VPC — often the default VPC of the unused region, where nobody enabled flow logs, so check — mining shows as:
- steady, long-lived outbound connections from every new instance to the same few public addresses;
- on pool ports commonly used by mining software (3333, 4444, 5555, 7777, 14444 and similar), although pools on 443 exist;
- small, regular byte counts in both directions (work shares), not bulk transfers;
- an inbound SSH connection from the attacker's address shortly after boot.
GuardDuty has dedicated finding types when it is enabled in that region, such as CryptoCurrency:EC2/BitcoinTool.B and CryptoCurrency:EC2/BitcoinTool.B!DNS (EC2 finding types). Attackers know this, which is why deleting detectors often precedes the launch.
Containment, in order
- Deactivate the credential that called
RunInstances(key or role session). Otherwise the attacker relaunches as fast as you terminate. - Inventory every region.
aws ec2 describe-instancesper region, or the EC2 Global View in the console. Include Spot requests, launch templates, Auto Scaling groups, ECS services and Lambda functions created in the window. - Preserve then terminate. Snapshot one representative instance's volume and capture its user data for the case file; terminate the rest.
- Remove the scaffolding. Key pairs, security groups, launch templates; withdraw pending quota requests you did not make.
- Contact AWS Support about unauthorised usage and charges. The re:Post guidance on unauthorized activity describes what AWS expects you to have done before asking.
- Deny unused regions with a service control policy if you use AWS Organizations.
Estimating the damage
Finance will ask for a number before you have finished containment. Build it from the logs rather than waiting for the invoice:
- Instance-hours. For each instance, the
RunInstancesevent gives the launch time and type; theTerminateInstancesevent (yours) gives the end. Multiply by the on-demand price of the type in that region. - Everything else. Data transfer out, EBS volumes, Elastic IPs and anything created in the window. Cost Explorer grouped by region and service, filtered to the incident days, shows what the logs may have missed.
- Pending requests. A quota increase that is approved after containment can be exploited again if the credential is not revoked everywhere.
Keep these figures with their sources in the case file; AWS Support will ask for the resource IDs and times when you raise the unauthorised charges.
Work backwards to the entry point
The instances are the symptom. Pivot on the principal in the RunInstances events:
- Was it a user the attacker created? Then find its
CreateUserand the credential that made it — see IAM persistence and privilege escalation. - Was it a long-term key used from a new address? Then it is a leaked access key investigation.
- Was it an instance role? Then the entry was a workload, and the investigation continues on the host (and, for EKS, in the Kubernetes audit log — kubernetesforensics.com).
Also check what else the same principal did before launching instances: mining is often paired with data theft, because the attacker has already paid the cost of getting in.
In the analyzer
AWS Forensics flags GPU instance launches (p and g families) by region, quota increase requests, activity in a region with no activity during the first day of the logs, security groups opened to the internet, key pairs created and, from flow logs, accepted outbound connections to known pool ports. The remediation checklist lists the instance IDs and regions to terminate. The fictional incident walkthrough ends in exactly this pattern.