Signals
- GuardDuty findings like
CryptoCurrency:EC2/BitcoinTool.B!DNS,Backdoor:EC2/C&CActivity.B,UnauthorizedAccess:EC2/SSHBruteForce(outbound), orInstanceCredentialExfiltration. - CPU pinned at 100% with no matching traffic; unknown processes; outbound traffic to odd ports.
Rules of engagement
- Don't terminate first. You lose the evidence and the chance to learn how they got in.
- Don't log in and poke around with your admin account if you can avoid it — capture first.
- Tell your security team; follow their process if one exists.
1. Protect the instance from termination and scaling
aws ec2 modify-instance-attribute --instance-id i-0bad123 --disable-api-termination
aws autoscaling detach-instances --instance-ids i-0bad123 \
--auto-scaling-group-name asg-web --no-should-decrement-desired-capacity
aws elbv2 deregister-targets --target-group-arn <tg-arn> --targets Id=i-0bad123
The ASG launches a clean replacement, so service continues.
2. Isolate the network
Create a security group with no inbound and no outbound rules:
SG=$(aws ec2 create-security-group --group-name forensic-isolation --description "isolation" --vpc-id vpc-0123 --query GroupId --output text)
aws ec2 revoke-security-group-egress --group-id $SG --ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'
aws ec2 modify-instance-attribute --instance-id i-0bad123 --groups $SG
Warning
Security groups are stateful: existing tracked connections may continue after you swap groups. Add a network ACL deny on the subnet (or move to an isolated subnet) to cut established sessions too.
3. Revoke the instance role's credentials
Credentials from the instance profile may already be stolen. Revoke sessions issued before now on the role (see the compromised keys runbook) and check CloudTrail for API calls by that role from IPs that are not the instance.
aws iam put-role-policy --role-name web-instance-role --policy-name AWSRevokeOlderSessions \
--policy-document file://revoke-before-now.json
4. Capture evidence
aws ec2 describe-instances --instance-ids i-0bad123 > instance-metadata.json
for v in $(aws ec2 describe-instances --instance-ids i-0bad123 --query "Reservations[].Instances[].BlockDeviceMappings[].Ebs.VolumeId" --output text); do
aws ec2 create-snapshot --volume-id $v --description "forensic i-0bad123 $(date -u +%FT%TZ)" \
--tag-specifications 'ResourceType=snapshot,Tags=[{Key=case,Value=IR-2026-1024}]'
done
Memory capture (if your team does it) must happen before stopping — use a pre-approved tool via SSM Run Command. Copy GuardDuty findings, VPC Flow Logs and CloudTrail for the window.
5. Find the entry point
| Common entry | Check |
|---|---|
| Exposed SSH/RDP with weak credentials | Security group history in AWS Config, auth logs |
| Vulnerable app (RCE) | Web server logs, WAF logs |
| Leaked keys used to launch instances | CloudTrail RunInstances caller |
| Malicious container image or package | Image provenance, package lock files |
6. Recover
Rebuild from a known-good AMI and IaC — never "clean" a compromised host. Patch the entry point first, rotate any secrets the instance could read, then release the isolated instance to security for analysis and later termination.
Prevent it next time
- No public SSH/RDP; use Session Manager.
- IMDSv2 required (
HttpTokens=required) to reduce credential theft via SSRF. - GuardDuty with runtime monitoring, Inspector for vulnerabilities, findings to on-call.