Chapter 19

Cloud Incident Response Playbook

AWS, Azure, and GCP incident response — the cloud-specific evidence sources, isolation primitives, and the key difference between cloud incidents and on-premises ones: the shared responsibility model means some of what you need is controlled by the cloud provider.

Scenario

Your AWS GuardDuty fires: UnauthorizedAccess:IAMUser/TorIPCaller — someone is making API calls from a Tor exit node using your production IAM credentials. Two hours of CloudTrail logs show that the attacker created a new IAM user, created access keys for it, and started enumerating S3 buckets. You have two problems: the attacker has exfiltrated the access keys to a new identity you don't know about, and you're not sure which S3 buckets they read or what data was in them. This playbook covers cloud-specific IR — where to find evidence, how to contain without taking down production, and what you can and can't do in a cloud environment.

How Cloud IR Differs From On-Premises

FactorOn-premisesCloud (AWS/Azure/GCP)
Primary evidence sourceWindows Event Log, EDR telemetry, memory imageCloudTrail/Activity Log/Cloud Audit Logs — API-level record of everything
Isolation primitiveEDR network containment, VLAN change, firewall ruleSecurity Group modification, IAM permission revocation, VPC network ACL
Log retentionDepends on SIEM configuration (often 90 days+)CloudTrail default 90 days in management events; S3 access logs depend on configuration
Infrastructure controlFull control — you own the hardwareShared responsibility — you control what you configured; hypervisor/host security is the provider's
Memory forensicsWinPmem on local or remote machineNot available on shared hypervisor — snapshot the volume and analyze disk artifacts; OR use provider debug mode if available
Volatile evidence preservationEDR memory collection before stopping processCreate a snapshot (AMI/disk image) before stopping the instance — disk snapshot is your equivalent

AWS Investigation

Bashaws-ir-investigation.sh
# AWS CloudTrail IR Investigation
# Assumes CloudTrail is enabled (if not: your evidence is severely limited)

# 1. Find all API calls made by compromised access key
COMPROMISED_KEY="AKIAIOSFODNN7EXAMPLE"

aws logs filter-log-events \
    --log-group-name "aws-cloudtrail-logs-ACCOUNT-ID" \
    --filter-pattern "{ $.userIdentity.accessKeyId = \"$COMPROMISED_KEY\" }" \
    --start-time $(date -d "7 days ago" +%s000) \
    --output json > cloudtrail_compromised_key.json

# 2. Enumerate all IAM users/roles/keys created after compromise date
aws iam list-users --query 'Users[?CreateDate>`2026-08-01`]' --output table
aws iam list-roles  --query 'Roles[?CreateDate>`2026-08-01`]' --output table

# For each new user, list their access keys:
aws iam list-access-keys --user-name NEWUSER

# 3. Check what S3 data was accessed — requires S3 server access logging to be enabled
# If enabled: logs are in your designated S3 logging bucket
aws s3 ls s3://YOUR-ACCESS-LOG-BUCKET/ --recursive | grep "2026-08-"
# Then download and search the access logs for your bucket name and GET operations

# 4. Check for new EC2 instances (compute for C2 or crypto mining)
aws ec2 describe-instances \
    --filters "Name=launch-time,Values=2026-08-01T00:00:00*" \
    --query 'Reservations[].Instances[].{ID:InstanceId,State:State.Name,LaunchTime:LaunchTime,IP:PublicIpAddress}' \
    --output table
Bashaws-containment.sh
# AWS IAM containment — revoke a compromised key and deny all permissions

# 1. Deactivate the compromised access key
aws iam update-access-key \
    --access-key-id AKIAIOSFODNN7EXAMPLE \
    --status Inactive \
    --user-name jsmith

# 2. Attach an explicit DENY policy to the user (belt-and-suspenders with key disable)
cat > deny_all_policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Deny",
    "Action": "*",
    "Resource": "*"
  }]
}
EOF
aws iam put-user-policy \
    --user-name jsmith \
    --policy-name IR-DenyAll \
    --policy-document file://deny_all_policy.json

# 3. Isolate a compromised EC2 instance — modify its security group to block all traffic
aws ec2 modify-instance-attribute \
    --instance-id i-0abcdef1234567890 \
    --groups sg-QUARANTINE-SG-ID  # a security group with no inbound/outbound rules

# 4. Preserve the compromised instance before stopping it
aws ec2 create-image \
    --instance-id i-0abcdef1234567890 \
    --name "IR-Forensic-Snapshot-$(date +%Y%m%d-%H%M)" \
    --no-reboot  # critical: don't reboot — preserves current disk state

Azure Investigation

PowerShellazure-ir-investigation.ps1
# Azure Activity Log and Entra ID IR investigation

Connect-AzAccount
Connect-MgGraph -Scopes "AuditLog.Read.All","Directory.Read.All"

# 1. Azure Activity Log — all control-plane actions in the subscription
# (Portal: Monitor → Activity Log; or via API)
$startDate = "2026-08-01"
Get-AzActivityLog -StartTime $startDate -MaxRecord 1000 |
    Where-Object { $_.Status.Value -eq "Succeeded" } |
    Select-Object EventTimestamp, Caller, OperationName,
                  ResourceGroupName, ResourceProviderName |
    Sort-Object EventTimestamp |
    Format-Table

# 2. Find new role assignments (privilege escalation indicator)
Get-AzRoleAssignment |
    Where-Object { $_.CreatedOn -gt [DateTime]$startDate } |
    Select-Object DisplayName, RoleDefinitionName, Scope, CreatedOn |
    Sort-Object CreatedOn

# 3. Entra ID: new service principals (attacker may have registered an app)
Get-MgServicePrincipal -All |
    Where-Object { $_.CreatedDateTime -gt [DateTime]$startDate } |
    Select-Object DisplayName, AppId, CreatedDateTime |
    Sort-Object CreatedDateTime

# 4. Isolate an Azure VM — using JIT (Just-in-Time) access revocation and NSG
$nsg = Get-AzNetworkSecurityGroup -ResourceGroupName "prod-rg" -Name "vm-nsg"
# Add a deny-all rule with highest priority (lower number = higher priority in Azure)
$rule = New-AzNetworkSecurityRuleConfig `
    -Name "IR-DenyAll" -Priority 100 -Direction Inbound `
    -Access Deny -Protocol * -SourceAddressPrefix * `
    -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange *
$nsg.SecurityRules.Add($rule)
Set-AzNetworkSecurityGroup -NetworkSecurityGroup $nsg

What You Can't Do in Cloud IR

The shared responsibility model means some forensic actions that are straightforward on-premises are impossible or require cloud provider assistance in the cloud.

On-premises capabilityCloud equivalent / limitation
Physical memory acquisition (WinPmem)Not possible on shared hypervisor. Nearest equivalent: crash dump if enabled, or live debugging if provider offers it. Use disk snapshot instead.
Physical network tap for traffic captureVPC Traffic Mirroring (AWS), vNet TAP (Azure) — requires pre-configuration. If not enabled, you have no packet-level visibility.
View hypervisor logs (who else ran on my hardware?)Not available to cloud customers. If you need proof of hardware-level isolation, contact cloud provider's security team via their abuse/security channel.
Full disk forensic imageCreate EBS snapshot (AWS) or Managed Disk snapshot (Azure). Mount to forensic instance. Equivalent to disk imaging, but you're working with the snapshot, not the live disk.

Q & A

Q: CloudTrail is enabled but the attacker deleted the trail. Is your evidence gone?

Partially — but not entirely. CloudTrail management events go to an S3 bucket. If the attacker deleted the trail, new events stop being logged. However: (1) CloudTrail also delivers events to CloudWatch Logs — if this was configured, the logs there may remain even if the S3 trail was deleted. (2) AWS retains a read-only copy of CloudTrail management events accessible via the console (Event History) for 90 days regardless of trail deletion. This shows you what happened, including the trail deletion itself. (3) GuardDuty, if enabled, has its own data source that is separate from CloudTrail and cannot be deleted by IAM users. Post-incident: implement a CloudTrail trail protected by S3 Object Lock and an S3 bucket policy that prevents deletion.

Q: The attacker used AWS STS AssumeRole to assume a role rather than using static IAM user keys. How does this change the investigation?

AssumeRole calls generate temporary credentials (STS tokens) that expire within hours. The CloudTrail record of the AssumeRole call shows: which principal assumed the role, from where, and the resulting session name. Once assumed, the session credentials generate their own activity in CloudTrail, with the userIdentity showing both the original principal and the role assumed. For investigation: search CloudTrail for AssumeRole events in the compromise window to understand the full chain of identity. For containment: you can't revoke an in-flight STS token (it expires on its own), but you can: remove the trust policy from the role so it can't be assumed again, or attach a deny-all permissions boundary to the role. For immediate containment of an active STS session, attach a deny-all inline policy to the role itself — this takes effect immediately for any calls made after the policy is attached, even with the current STS token.