Cloud Security

Cloud Cost Security: Preventing Cryptomining and Unexpected Bills

How attackers abuse compromised cloud accounts for cryptomining and other compute abuse — how to detect anomalous usage, set billing alerts, and use GuardDuty to catch cryptomining campaigns early.

November 15, 20258 min readShipSafer Team

In February 2020, a researcher discovered that Tesla's AWS Kubernetes cluster had been compromised and was running Monero mining software. The attackers had exploited an exposed Kubernetes dashboard to gain access, then used the cluster's compute capacity for cryptomining. The cluster's configuration minimized the cost impact, but the attack could have run up hundreds of thousands of dollars in compute charges.

Cloud accounts are increasingly valuable targets for cryptomining because they offer nearly unlimited compute capacity, can be billed to the victim, and the mining operation can remain profitable even after detection if the attacker can move to a new account or region.

How Cryptomining Attacks Work

Initial Access Methods

Exposed Credentials

The most common entry point is exposed AWS access keys or GCP service account keys. These leak through:

  • Code committed to public GitHub repositories
  • Hardcoded in environment variables logged by CI/CD systems
  • Left in Docker images published to Docker Hub
  • Exposed in .env files committed to public repos

Automated scanners like TroveHog, gitleaks, and purpose-built credential harvesters continuously monitor GitHub and Docker Hub for cloud credentials. A leaked key can be abused within minutes of appearing in a public repository.

Compromised EC2/GCE Instance Credentials

Instances with overly permissive IAM roles expose their credentials via the Instance Metadata Service. Server-Side Request Forgery (SSRF) vulnerabilities in web applications can be used to read metadata:

# SSRF attack targeting IMDS
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns the role name, then:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole
# Returns: AccessKeyId, SecretAccessKey, Token

Exposed Kubernetes Dashboards and APIs

Publicly accessible Kubernetes dashboards (the Tesla attack) or API servers without authentication allow attackers to deploy cryptomining pods directly.

The Cryptomining Playbook

Once attackers have cloud credentials, they follow a consistent playbook:

  1. Enumerate available regions and quotas to find where they can launch the most compute
  2. Increase service limits if possible (GPU instance limits are often low by default)
  3. Launch high-CPU or GPU instances in multiple regions simultaneously
  4. Install mining software (XMRig for Monero is most common due to its CPU efficiency and privacy properties)
  5. Persist by creating new IAM users/keys and rotating compromised credentials
  6. Exfiltrate mined currency to a wallet they control

A typical attack using p3.16xlarge (8x V100 GPU) instances running Ethereum mining could generate thousands of dollars of cloud charges per day while yielding hundreds of dollars in mining revenue — the economics work entirely in the attacker's favor because they're not paying the electricity or compute bills.

AWS Detection: GuardDuty Findings

GuardDuty has dedicated finding types for cryptomining:

Finding TypeWhat It Means
CryptoCurrency:EC2/BitcoinTool.B!DNSEC2 instance querying cryptocurrency mining pool DNS names
CryptoCurrency:EC2/BitcoinTool.BEC2 instance communicating with known mining pool IPs
CryptoCurrency:Lambda/BitcoinTool.B!DNSLambda function querying mining pool DNS
UnauthorizedAccess:EC2/MaliciousIPCallerEC2 instance communicating with known threat actor IPs
Behavior:EC2/TrafficVolumeUnusualAnomalously high network traffic suggesting data exfiltration or mining

Enable GuardDuty with enhanced EC2 monitoring:

aws guardduty create-detector \
  --enable \
  --data-sources '{
    "S3Logs": {"Enable": true},
    "Kubernetes": {"AuditLogs": {"Enable": true}},
    "MalwareProtection": {
      "ScanEc2InstanceWithFindings": {
        "EbsVolumes": {"Enable": true}
      }
    }
  }' \
  --features '[
    {"Name": "EC2_RUNTIME_MONITORING", "Status": "ENABLED",
     "AdditionalConfiguration": [
       {"Name": "ECS_FARGATE_AGENT_MANAGEMENT", "Status": "ENABLED"},
       {"Name": "EC2_AGENT_MANAGEMENT", "Status": "ENABLED"}
     ]
    }
  ]'

Set up automatic remediation for high-severity cryptomining findings using EventBridge:

{
  "source": ["aws.guardduty"],
  "detail-type": ["GuardDuty Finding"],
  "detail": {
    "severity": [{"numeric": [">=", 7]}],
    "type": ["CryptoCurrency:EC2/BitcoinTool.B", "CryptoCurrency:EC2/BitcoinTool.B!DNS"]
  }
}

Route to a Lambda function that isolates the instance by replacing its security groups with a quarantine group that blocks all traffic.

Billing Alerts and Cost Anomaly Detection

CloudWatch Billing Alerts

Set billing alarms at multiple thresholds. An alarm at your normal monthly spend prevents surprise bills:

# Alert at $100 (daily monitoring)
aws cloudwatch put-metric-alarm \
  --alarm-name "Billing-100" \
  --alarm-description "Alert when estimated charges exceed $100" \
  --metric-name EstimatedCharges \
  --namespace AWS/Billing \
  --statistic Maximum \
  --period 86400 \
  --threshold 100 \
  --comparison-operator GreaterThanThreshold \
  --dimensions Name=Currency,Value=USD \
  --evaluation-periods 1 \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:BillingAlerts \
  --treat-missing-data notBreaching

# Alert at $1000 (potential compromise)
aws cloudwatch put-metric-alarm \
  --alarm-name "Billing-1000-Critical" \
  --alarm-description "CRITICAL: Charges may indicate account compromise" \
  --metric-name EstimatedCharges \
  --namespace AWS/Billing \
  --statistic Maximum \
  --period 86400 \
  --threshold 1000 \
  --comparison-operator GreaterThanThreshold \
  --dimensions Name=Currency,Value=USD \
  --evaluation-periods 1 \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:CriticalAlerts

Note: Billing metrics are only available in us-east-1, regardless of where your resources run.

AWS Cost Anomaly Detection

Cost Anomaly Detection uses ML to identify unusual spending patterns, including sudden spikes from cryptomining:

# Create a monitor for all AWS services
aws ce create-anomaly-monitor \
  --anomaly-monitor '{
    "MonitorName": "AllServices",
    "MonitorType": "DIMENSIONAL",
    "MonitorDimension": "SERVICE"
  }'

# Create subscription for high-impact anomalies
aws ce create-anomaly-subscription \
  --anomaly-subscription '{
    "SubscriptionName": "CriticalAnomalies",
    "MonitorArnList": ["arn:aws:ce::123456789012:anomalymonitor/abc123"],
    "Subscribers": [
      {
        "Address": "arn:aws:sns:us-east-1:123456789012:BillingAlerts",
        "Type": "SNS"
      }
    ],
    "Threshold": 100,
    "Frequency": "IMMEDIATE"
  }'

This alerts when the detected anomaly impact exceeds $100. Configure separate monitors per service (EC2, Lambda) for faster detection.

Service Quotas: Limiting Blast Radius

Default EC2 service quotas vary by account age and usage. Request reduced limits for high-cost instance types that your workloads don't use:

# Check current limits for GPU instances
aws service-quotas get-service-quota \
  --service-code ec2 \
  --quota-code L-DB2E81BA  # p3 On-Demand instances

# Request a lower limit (e.g., 0 for GPU instances you don't need)
aws service-quotas request-service-quota-increase \
  --service-code ec2 \
  --quota-code L-DB2E81BA \
  --desired-value 0

If your workloads don't need GPU instances, p3, p4, or g4 instances, reducing quotas to 0 means an attacker with your credentials cannot launch them.

Use SCPs to Restrict Instance Types

At the AWS Organizations level, use Service Control Policies to prevent launching expensive instance types in workload accounts:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RestrictExpensiveInstanceTypes",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringLike": {
          "ec2:InstanceType": [
            "p3.*", "p4.*", "p5.*",
            "g4.*", "g5.*",
            "x1.*", "x2.*",
            "u-*"
          ]
        }
      }
    }
  ]
}

Restrict to Specific Regions

If your workloads only run in us-east-1 and eu-west-1, deny operations in all other regions:

{
  "Sid": "DenyUnauthorizedRegions",
  "Effect": "Deny",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["us-east-1", "eu-west-1"]
    },
    "ArnNotLike": {
      "aws:PrincipalARN": "arn:aws:iam::*:role/AllowedGlobalRole"
    }
  }
}

Detecting Credential Compromise Early

Monitor CloudTrail for Unusual API Calls

Cryptomining attacks typically exhibit patterns in CloudTrail that don't match normal operations:

# CloudWatch Logs Insights query
fields @timestamp, userIdentity.arn, eventName, awsRegion, sourceIPAddress
| filter eventName in [
    "RunInstances",
    "RequestSpotInstances",
    "CreateSpotFleet",
    "RunJobFlow",
    "CreateCluster"
  ]
| filter awsRegion not in ["us-east-1", "eu-west-1"]  # Your normal regions
| stats count() by userIdentity.arn, awsRegion
| sort count desc

GitHub Token Scanning Integration

For organizations using GitHub, enable GitHub's secret scanning and push protection. For AWS, use the AWSCompromisedKeyQuarantine policy — when AWS detects your key in a public GitHub repo, they automatically attach this policy to quarantine the key:

# Check if your keys have been quarantined
aws iam list-attached-user-policies --user-name my-user
# Look for AWSCompromisedKeyQuarantineV2

Don't wait for AWS to detect the exposure. Use a pre-receive hook or CI check to prevent keys from being committed:

# .git/hooks/pre-commit
#!/bin/bash
if git diff --cached --diff-filter=A -S'AKIA' | grep -q 'AKIA'; then
  echo "ERROR: Potential AWS access key detected in staged files"
  exit 1
fi

Response Playbook for Suspected Compromise

If you detect cryptomining or a billing anomaly that suggests compromise:

  1. Identify the compromised principal using CloudTrail: which IAM user/role made the RunInstances calls?

  2. Immediately disable the credentials:

aws iam update-access-key --access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive --user-name compromised-user
  1. Terminate all instances launched by the compromised principal:
aws ec2 describe-instances \
  --query 'Reservations[].Instances[?State.Name==`running`].InstanceId' \
  --output text | xargs aws ec2 terminate-instances --instance-ids
  1. Check all regions — attackers frequently launch instances in regions you don't normally use:
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
  count=$(aws ec2 describe-instances --region $region \
    --query 'length(Reservations[].Instances[])' --output text 2>/dev/null)
  if [ "$count" -gt "0" ]; then
    echo "$region: $count instances"
  fi
done
  1. Review IAM changes made by the compromised principal — check for new users, access keys, or policy changes.

  2. Contact AWS Support to request bill forgiveness. AWS has a process for bill reduction when compromise is demonstrated, but it's not guaranteed.

Billing alerts alone are not sufficient — by the time a billing alarm fires, significant charges may have accrued. The combination of GuardDuty (detecting mining pool communication), Cost Anomaly Detection (detecting spend spikes), and region/instance-type SCPs (limiting blast radius) is the right multi-layered approach.

cloud security
cryptomining
AWS billing
GuardDuty
cost anomaly detection
IAM security

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.