Post

Week 4 — Day 22: Cloud Incident Response

A full walkthrough of cloud incident response on AWS — the IR lifecycle, detecting and investigating a compromised IAM key or EC2 instance, containment steps, and building a runbook.

Week 4 — Day 22: Cloud Incident Response

Why Cloud IR Is Different

Traditional incident response assumed you could physically touch the affected machine — pull the hard drive, image it, run forensics. In the cloud:

  • Instances can be terminated and their storage wiped in seconds
  • Evidence is spread across CloudTrail, VPC Flow Logs, GuardDuty, application logs — in different services
  • Actions happen via API calls, not keyboard input
  • The “attacker” may be an IAM key operating autonomously long after the initial breach
  • Blast radius can span multiple accounts and regions within minutes

Cloud IR requires different procedures, different tools, and speed above all else.


The IR Lifecycle in AWS

1
Detection → Analysis → Containment → Eradication → Recovery → Lessons Learned

Detection

  • GuardDuty finding fires
  • Security Hub alert
  • CloudTrail anomaly (unusual API calls, new IAM user created)
  • Application alert (spike in errors, auth failures)
  • External notification (AWS Abuse report, bug bounty)

Analysis

  • Understand the scope: what was accessed, what was changed, what was exfiltrated?
  • Who is the identity involved?
  • What resources are affected?
  • Is the attack ongoing?

Containment

  • Stop the bleeding — revoke credentials, isolate instances, block IPs
  • Preserve evidence — snapshot EBS volumes, export logs before they expire

Eradication

  • Remove the attacker’s access and any persistence they established
  • Patch the vulnerability that allowed entry

Recovery

  • Restore from known-good snapshots or redeploy from IaC
  • Monitor closely post-recovery for re-entry attempts

Lessons Learned

  • Root cause analysis
  • Update runbooks, detection rules, and preventive controls

Scenario 1 — Compromised IAM Access Key

This is the most common cloud incident — a key leaked in a GitHub repo, a .env file exposed, or malware exfiltrating credentials from an EC2 instance.

Step 1 — Detect

GuardDuty finding: UnauthorizedAccess:IAMUser/MaliciousIPCaller or CredentialAccess:IAMUser/AnomalousBehavior

Or you notice it manually — someone emails you a “your AWS key is in this public repo” notification.

Step 2 — Identify the Key

1
2
3
4
5
# Find the access key ID mentioned in the alert or repo
# Format: AKIA... (long-term) or ASIA... (temporary STS)

# Find which IAM user/role owns it
aws iam get-access-key-last-used --access-key-id AKIAIOSFODNN7EXAMPLE

[SCREENSHOT]Terminal showing aws iam get-access-key-last-used output returning the UserName, last used date, service, and region — showing recent suspicious activity

Step 3 — Preserve Evidence First

Before touching anything, export relevant CloudTrail events:

1
2
3
4
5
# Export all API calls made with this key in the last 24 hours
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
  --start-time $(date -d '24 hours ago' --iso-8601=seconds) \
  --output json > compromised-key-events.json

[SCREENSHOT]Terminal showing cloudtrail lookup-events output listing API calls made with the compromised key — showing eventNames like CreateUser, AttachRolePolicy, RunInstances made from unusual IPs

Step 4 — Contain — Revoke the Key Immediately

1
2
3
4
5
6
7
8
9
10
# Deactivate the key (reversible)
aws iam update-access-key \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --status Inactive \
  --user-name affected-user

# Delete the key (irreversible — do this after you've saved evidence)
aws iam delete-access-key \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --user-name affected-user

[SCREENSHOT]Terminal showing the aws iam update-access-key command completing with no error, confirming the key is deactivated

Step 5 — Check for Attacker Persistence

The attacker may have created resources to maintain access even after the key is revoked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Check for new IAM users created recently
aws iam list-users --query 'Users[?CreateDate>`2026-05-01`]'

# Check for new access keys on all users
aws iam list-users --query 'Users[*].UserName' --output text | \
  xargs -I{} aws iam list-access-keys --user-name {}

# Check for new roles with external trust
aws iam list-roles --query \
  'Roles[?AssumeRolePolicyDocument.Statement[?Principal.AWS!=`null`]]'

# Check for new Lambda functions (possible backdoor)
aws lambda list-functions --query 'Functions[?LastModified>`2026-05-01`]'

# Check for EC2 instances launched recently
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[*].Instances[?LaunchTime>`2026-05-01`]'

[SCREENSHOT]Terminal showing aws iam list-users query returning a new user “backup-admin” created at the time of the incident — confirming the attacker created a backdoor account

Step 6 — Revoke All Active Sessions

Even after deleting the key, existing STS sessions derived from it may still be valid (up to their expiry):

1
2
3
4
5
# Attach a policy to the user that explicitly denies everything
aws iam put-user-policy \
  --user-name affected-user \
  --policy-name DenyAll \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*"}]}'

For roles — update the trust policy to remove external trust or add a condition that denies all.


Scenario 2 — Compromised EC2 Instance

GuardDuty finding: Backdoor:EC2/C&CActivity.B or CryptoCurrency:EC2/BitcoinTool.B — an instance is communicating with known bad IPs or running a miner.

Step 1 — Preserve Evidence

Before isolating the instance, capture its current state:

1
2
3
4
5
6
7
8
9
10
INSTANCE_ID="i-0abc123def456"

# Create a snapshot of each attached EBS volume
aws ec2 describe-volumes \
  --filters "Name=attachment.instance-id,Values=$INSTANCE_ID" \
  --query 'Volumes[*].VolumeId' \
  --output text | \
  xargs -I{} aws ec2 create-snapshot \
    --volume-id {} \
    --description "IR snapshot $INSTANCE_ID $(date +%Y%m%d)"

[SCREENSHOT]Terminal showing the aws ec2 create-snapshot commands completing, returning snapshot IDs for each volume attached to the compromised instance

Step 2 — Isolate the Instance (Quarantine Security Group)

Create a quarantine security group that blocks all traffic:

1
2
3
4
5
6
7
8
9
10
11
12
# Create quarantine SG — no inbound or outbound rules
QUARANTINE_SG=$(aws ec2 create-security-group \
  --group-name "quarantine-ir-$(date +%Y%m%d)" \
  --description "Incident Response Quarantine - blocks all traffic" \
  --vpc-id $VPC_ID \
  --query 'GroupId' \
  --output text)

# Replace ALL security groups on the instance with the quarantine SG
aws ec2 modify-instance-attribute \
  --instance-id $INSTANCE_ID \
  --groups $QUARANTINE_SG

[SCREENSHOT]Terminal showing the quarantine security group being created and applied to the instance, followed by aws ec2 describe-instances confirming the instance now only has the quarantine SG attached

The instance is now isolated — no inbound, no outbound. It’s running but can’t communicate.

Step 3 — Revoke the Instance’s IAM Role Credentials

The IAM role on the instance may have been used maliciously. Revoke active sessions:

1
2
3
4
5
6
7
8
9
10
11
# Find the instance profile / role
ROLE_NAME=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query 'Reservations[0].Instances[0].IamInstanceProfile.Arn' \
  --output text | cut -d'/' -f2)

# Revoke all active sessions for this role
aws iam put-role-policy \
  --role-name $ROLE_NAME \
  --policy-name DenyAll-IR \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*","Condition":{"DateLessThan":{"aws:TokenIssueTime":"2026-06-11T12:00:00Z"}}}]}'

This denies any API call from sessions issued before the specified time — killing all existing sessions derived from the role without affecting new legitimate ones.

Step 4 — Forensic Analysis

With the instance isolated, connect for analysis via SSM (if SSM agent is running):

1
aws ssm start-session --target $INSTANCE_ID

Or mount the EBS snapshot to a forensic EC2 instance:

1
2
3
4
5
6
# Launch a forensic instance in an isolated VPC
# Attach the snapshot as a volume
aws ec2 attach-volume \
  --volume-id vol-snapshot-id \
  --instance-id forensic-instance-id \
  --device /dev/sdf

[SCREENSHOT]SSM Session Manager terminal connected to the quarantined instance showing investigation commands — checking running processes, cron jobs, bash history, and network connections

What to look for:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Suspicious processes
ps auxf | grep -v "^\[" | sort -k3 -rn | head -20

# Cron persistence
crontab -l
cat /etc/cron* 2>/dev/null

# Recently modified files
find / -newer /tmp -type f 2>/dev/null | grep -v proc | head -50

# Bash history
cat ~/.bash_history
cat /home/*/.bash_history

# Network connections at time of incident (from VPC flow logs)
# Query from Athena or CloudWatch Logs

CloudTrail Forensic Queries

During any incident, Athena is your best tool for querying CloudTrail at scale:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- What did the compromised identity do?
SELECT eventtime, eventname, sourceipaddress, requestparameters, errorcode
FROM cloudtrail_logs
WHERE useridentity.accesskeyid = 'AKIAIOSFODNN7EXAMPLE'
  AND eventtime > '2026-06-10'
ORDER BY eventtime;

-- Were any IAM changes made?
SELECT eventtime, eventname, requestparameters, useridentity.arn
FROM cloudtrail_logs
WHERE eventsource = 'iam.amazonaws.com'
  AND eventtime > '2026-06-10'
  AND errorcode IS NULL
ORDER BY eventtime;

-- Any new resources created (possible persistence)?
SELECT eventtime, eventname, awsregion, requestparameters
FROM cloudtrail_logs
WHERE eventname IN ('CreateUser','CreateRole','RunInstances','CreateFunction20150331','CreateBucket')
  AND eventtime > '2026-06-10'
ORDER BY eventtime;

-- Console logins from unusual IPs
SELECT eventtime, useridentity.username, sourceipaddress, additionaleventdata
FROM cloudtrail_logs
WHERE eventname = 'ConsoleLogin'
  AND eventtime > '2026-06-01'
  AND sourceipaddress NOT LIKE '10.%'
ORDER BY eventtime DESC;

[SCREENSHOT]Athena query editor showing one of the IR queries running with results below — listing the attacker’s API calls with timestamps, event names, and source IPs


IR Runbook Template

Document your IR procedures as runbooks — executable checklists that any team member can follow during an incident.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
## Runbook: Compromised IAM Access Key

**Severity:** P1 — drop everything
**Detection source:** GuardDuty / CloudTrail alert / external report

### Immediate Actions (first 15 minutes)
- [ ] Identify the key ID from the alert
- [ ] Run: `aws iam get-access-key-last-used --access-key-id <KEY_ID>`
- [ ] Export CloudTrail events for the key (last 24h)
- [ ] Deactivate the key: `aws iam update-access-key --status Inactive`
- [ ] Notify security team and management

### Investigation (first hour)
- [ ] Review all API calls made with the key
- [ ] Check for new IAM users, roles, access keys created
- [ ] Check for new EC2, Lambda, RDS instances launched
- [ ] Check for data exfiltration (S3 GetObject on sensitive buckets)
- [ ] Identify how the key was exposed (git, logs, env var)

### Containment
- [ ] Delete the key (after evidence preserved)
- [ ] Add DenyAll inline policy to revoke active sessions
- [ ] Delete any attacker-created IAM users/roles/keys
- [ ] Terminate any attacker-launched resources

### Recovery
- [ ] Issue new credentials to the legitimate user
- [ ] Rotate any secrets that may have been accessed
- [ ] Verify system integrity of any instances the key touched

### Post-Incident
- [ ] Root cause written up
- [ ] Detection gap identified and new GuardDuty/Config rule added
- [ ] Runbook updated with lessons learned

[SCREENSHOT]The runbook opened in a text editor or wiki, showing the checklist format with checkboxes — a clean, executable document


Key Takeaways

  • Speed is everything — the longer a compromised key is active, the more damage is done
  • Preserve evidence before containment — snapshots and CloudTrail exports first
  • Quarantine security group is the cleanest EC2 isolation technique — fast and reversible
  • The DenyAll inline policy with a DateLessThan condition revokes existing STS sessions without breaking new legitimate access
  • Always check for attacker persistence — new IAM users, Lambda backdoors, cron jobs
  • Athena + CloudTrail is your forensic platform — practice the queries before you need them
  • Runbooks must be written before an incident — not during one

References


You can find me online at:

My signature image

This post is licensed under CC BY 4.0 by the author.