← All articles
Incident Response 7 min read

Detecting Compromised AWS Credentials Before the Damage Is Done

Early detection signals for compromised IAM credentials: unusual API call patterns, new region activity, impossible travel, GetCallerIdentity usage, and EventBridge alerting that fires in minutes.

CloudDefender Team ·

The uncomfortable truth about compromised AWS credentials is that the damage window is measured in hours. Threat actors with valid IAM credentials move quickly: validate the credentials, enumerate permissions, identify high-value targets, exfiltrate. The entire sequence can complete in under two hours. The average organization detects credential compromise in days or weeks — long after the exfiltration is complete.

This gap is not a tooling problem. AWS CloudTrail captures every API call with the source IP, user agent, timestamp, and principal identifier. The signals are there. The problem is that most teams don’t have EventBridge rules watching for the specific behavioral patterns that distinguish a compromised credential from a legitimate session. Building those detections takes hours, and each one shifts your detection window from days to minutes.

Detection Signal Flow: CloudTrail → EventBridge → Alerting PipelineCloudTrailEvery API callEventBridgePattern matchingLambdaEnrichment + scorePagerDuty P1 (immediate)Slack #security-alertsJira ticket (auto-created)Auto-remediation: Lambda attaches deny-all inline policy to compromised principal within 2 minutes of detection
Detection pipeline for compromised credentials. CloudTrail events flow to EventBridge pattern rules, are enriched by a Lambda evaluator (geo lookup, baseline comparison), and trigger multi-channel alerts. High-confidence signals trigger automatic isolation within 2 minutes.

Signal 1: sts:GetCallerIdentity as a Recon Indicator

sts:GetCallerIdentity is the first API call most attackers make after stealing credentials. It requires zero IAM permissions — any valid credential can call it — and it returns the account ID, user ID, and ARN of the calling principal. For attackers, it confirms that the credentials are valid and reveals the target account.

Legitimate users almost never call sts:GetCallerIdentity manually. It appears in automated tooling, SDK initialization, and occasional aws sts get-caller-identity debugging — but from consistent IP ranges and user agents. An EventBridge rule that fires when this API is called from an IP address not in your known CIDR ranges is a high-fidelity signal with very few false positives.

{
  "source": ["aws.sts"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventName": ["GetCallerIdentity"],
    "sourceIPAddress": [{ "anything-but": ["10.0.0.0/8", "172.16.0.0/12"] }]
  }
}

Signal 2: New Geographic Region Activity

Your engineering team operates from consistent AWS regions. If your infrastructure is in us-east-1 and us-west-2, a CloudTrail event originating from ap-southeast-2 is immediately suspicious. Attackers frequently spin up resources in regions far from the victim’s normal operations because they assume alerting coverage is thinner there.

Build an EventBridge rule that fires on any CloudTrail management event where the awsRegion field doesn’t match your approved region list:

{
  "source": ["aws.iam", "aws.ec2", "aws.s3"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "awsRegion": [{ "anything-but": ["us-east-1", "us-west-2"] }],
    "errorCode": [{ "exists": false }]
  }
}

Signal 3: Impossible Travel Detection

Impossible travel detection identifies the same IAM principal making API calls from New York at 14:00 UTC and from Singapore at 14:25 UTC. No legitimate automated process should be generating credentials from multiple continents simultaneously.

Implementing this requires a Lambda evaluator that:

  1. Receives every CloudTrail event involving a human IAM principal
  2. Resolves the sourceIPAddress to a geographic coordinate using MaxMind GeoIP2
  3. Looks up the last known location for that principal in DynamoDB
  4. Computes the implied travel speed; if it exceeds 900 km/h, fires an alert
  5. Updates the DynamoDB record with the current location
def check_impossible_travel(principal_id, source_ip, event_time):
    last = get_last_location(principal_id)  # from DynamoDB
    if not last:
        save_location(principal_id, source_ip, event_time)
        return False
    
    current_coords = geoip.city(source_ip).location
    elapsed_hours = (event_time - last["time"]).total_seconds() / 3600
    distance_km = haversine(last["lat"], last["lon"],
                            current_coords.latitude, current_coords.longitude)
    
    if elapsed_hours > 0 and (distance_km / elapsed_hours) > 900:
        return True  # impossible travel detected
    
    save_location(principal_id, source_ip, event_time)
    return False

Signal 4: Unusual API Call Sequences

An IAM user who has called ec2:DescribeInstances and s3:GetObject every day for six months suddenly calling iam:ListRoles, sts:AssumeRole, and iam:CreateAccessKey is not acting like themselves.

The API calls most indicative of attacker reconnaissance when seen from an unusual principal:

Create a CloudTrail Insights rule in your trail to detect anomalous API call rates — it will fire when any management API call rate deviates more than two standard deviations from the 7-day baseline. This catches the reconnaissance burst pattern effectively.

Signal 5: GuardDuty Credential-Specific Findings

GuardDuty provides several out-of-box findings specifically for compromised credentials:

Treat InstanceCredentialExfiltration.OutsideAWS as a P0 finding. It means an attacker has compromised an EC2 instance (via SSRF or RCE), exfiltrated the IMDS credentials, and is using them from their own infrastructure. The attack chain is already well advanced.

The Three-Tier Response

Detection is only the first tier. When a credential compromise signal fires, the response has three phases:

Tier 1: Isolate (0–2 minutes) — Attach a deny-all inline policy to the compromised principal. This is faster and more reversible than deleting the access key, and takes effect within seconds:

iam.put_user_policy(
    UserName=affected_user,
    PolicyName="INCIDENT-deny-all",
    PolicyDocument=json.dumps({
        "Version": "2012-10-17",
        "Statement": [{"Effect": "Deny", "Action": "*", "Resource": "*"}]
    })
)

Tier 2: Preserve (2–10 minutes) — Export all CloudTrail events for the compromised principal for the last 24 hours. This is your forensic baseline — you need to know what happened before the isolation, not just after.

Tier 3: Blast radius (10–30 minutes) — Determine what actions the attacker took: Did they assume other roles? Create new users or access keys? Modify S3 bucket policies? Exfiltrate data? Export the findings to a secure S3 bucket for the investigation.

The organizations that handle credential compromises well practiced this playbook before they needed it. Build the EventBridge rules, test them with a canary IAM user that deliberately calls sts:GetCallerIdentity from a VPN exit node, and verify your alert fires within 3 minutes.


CloudDefender builds and maintains the full credential compromise detection stack — EventBridge rules, Lambda evaluators with geo-IP enrichment, and automated isolation — so your team is alerted within minutes, not hours.

CloudDefender

Defend your cloud. Continuously.

CloudDefender Suite gives security teams continuous posture management, threat detection, and compliance automation across AWS, Azure, and GCP — with zero false-positive fatigue.

Try CloudDefender →