Cloud DFIR process
Order of volatility; automate before teardown.
Working an incident on an on-prem server is like processing a crime scene in a locked room. The evidence sits still while you photograph it. Cloud DFIR (digital forensics and incident response, run against infrastructure that only exists as API calls) is closer to processing a crime scene inside a building that demolishes and rebuilds itself on a timer. An auto-scaling group can terminate the compromised instance, and everything held in its memory, a few minutes after your alert fires. The discipline is the same as classic DFIR. The clock is not.
For a detection engineer, none of this is someone else's job. Your detections open investigations, and investigations hand back the raw material for the next detection: the attacker's IP address, the exact order of API calls they made, the persistence trick they reached for. If you cannot run the response half of the loop, the detection half stops getting better.
The response loop: PICERL, NIST, and where the cloud bends it
The classic model from the SANS Institute is PICERL, six phases in order: Preparation, Identification, Containment, Eradication, Recovery, Lessons learned. NIST (the US National Institute of Standards and Technology) squeezed those into four phases in SP 800-61r2. The April 2025 revision, SP 800-61r3, retired the r2 lifecycle and re-cast incident response as a profile of CSF 2.0 (Cybersecurity Framework version 2.0), where Govern, Identify and Protect feed a continuous Detect → Respond → Recover cycle. Learn both, because certification exams like GCIH (GIAC Certified Incident Handler) and Security+ still test the older PICERL phase ordering. The cloud bends the model in exactly one place. Containment can *destroy evidence*: stopping an instance wipes its RAM (random access memory, the volatile scratch space where anything currently running lives). So containment and acquisition stop being sequential phases and become one interleaved step, usually automated.
Order of volatility when the infrastructure is disposable
Evidence has a shelf life, the way footprints on a beach do. The ones nearest the water go first, so that is where you point the camera. RFC 3227 (Request for Comments 3227, one of the numbered internet standards documents) turns that instinct into the order of volatility: collect what disappears fastest, first. Roughly, that is CPU and memory state, then running processes and network connections, then disk, then logs and backups. The cloud sharpens every tier. Reboot wipes memory, and so does a single stop API call. The root EBS volume (Elastic Block Store, the network-attached disk behind most instances) is deleted on termination whenever DeleteOnTermination is true, which is the default for root volumes attached at launch. A terminated instance with no snapshot is an investigation you cannot run. Instance-store volumes, the disks bolted to the physical host, vanish even on a stop. Only centralized logs are durable, and only if you shipped them *before* the incident. The practical cloud ordering is therefore: freeze the resource's lifecycle first (termination protection, detach from auto scaling), then capture memory, then snapshot disks, and lean on logs last because they should already be safe.
Contain the instance without destroying its state
Containment means cutting the attacker's access without powering anything off. The tool for that is a quarantine security group: a security group (SG, the per-instance firewall in AWS) with zero inbound and zero outbound rules. AWS blocks anything a rule does not explicitly allow, so zero rules blocks everything. Build one in every VPC (virtual private cloud, your own walled-off slice of the provider's network) ahead of time, so it already exists at 3am when you need it. Disk capture uses EBS snapshots. They are block-level, incremental, and taken by the control plane rather than by software running on the box, so the attacker sees nothing happen. A snapshot of a single volume is *crash-consistent*, a point-in-time image that looks like the power was pulled mid-write. Run create-snapshots against the instance instead and you capture every attached volume as one matched set.
# 0. Scope before touching anything — record instance, state, and ENIaws ec2 describe-instances --instance-ids i-0a1b2c3d4e5f6a7b8 \--query 'Reservations[0].Instances[0].[InstanceId,State.Name,NetworkInterfaces[0].NetworkInterfaceId]' \--output text# i-0a1b2c3d4e5f6a7b8 running eni-0f9e8d7c6b5a4f3e2# 1. Swap ALL security groups for the pre-built deny-all quarantine SG# (repeat for every ENI if the instance has more than one)aws ec2 modify-network-interface-attribute \--network-interface-id eni-0f9e8d7c6b5a4f3e2 \--groups sg-0quarantine1234567# (no output on success)# 2. Termination protection — but note: this does NOT stop Auto Scaling# from terminating it, so detach from the ASG as wellaws ec2 modify-instance-attribute --instance-id i-0a1b2c3d4e5f6a7b8 \--disable-api-terminationaws autoscaling detach-instances --instance-ids i-0a1b2c3d4e5f6a7b8 \--auto-scaling-group-name web-asg --no-should-decrement-desired-capacity# 3. Snapshot every attached volume as one consistent set, tagged to the caseaws ec2 create-snapshots \--instance-specification InstanceId=i-0a1b2c3d4e5f6a7b8 \--description "IR-2026-0142 pre-containment capture" \--copy-tags-from-source volume \--tag-specifications 'ResourceType=snapshot,Tags=[{Key=case,Value=IR-2026-0142}]'# {# "Snapshots": [# { "SnapshotId": "snap-0e9f8a7b6c5d4e3f2", "VolumeId": "vol-0c1d2e3f4a5b6c7d8",# "State": "pending", "Encrypted": true,# "StartTime": "2026-07-13T09:47:12.804000+00:00" }# ]# }
Contain the identity, because sessions outlive the machine
Most cloud incidents are credential incidents. An attacker sitting on the box has almost certainly already read the role's credentials out of IMDS (the instance metadata service, an address every instance can query to get its own temporary keys). Those STS (Security Token Service) session tokens keep working from the attacker's own laptop for hours after you quarantine the instance. GuardDuty's InstanceCredentialExfiltration finding exists because this happens constantly. Here is the awkward part: you cannot revoke one individual STS token. What you can do is deny every session issued before a chosen moment, which is precisely what the console's *Revoke sessions* button does under the hood. It writes an inline deny policy conditioned on token issue time.
# Deny every session issued before this moment; new (legitimate) sessions still workaws iam put-role-policy --role-name web-prod-role \--policy-name AWSRevokeOlderSessions \--policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Deny", "Action": ["*"], "Resource": ["*"],"Condition": {"DateLessThan": {"aws:TokenIssueTime": "2026-07-13T09:50:00Z"}}}]}'# (no output — verify it landed:)aws iam get-role-policy --role-name web-prod-role \--policy-name AWSRevokeOlderSessions --query PolicyName# "AWSRevokeOlderSessions"# Long-lived key implicated? Deactivate, don't delete — deleting destroys the# artifact and your ability to alert on attempted reuse:aws iam update-access-key --user-name deploy-bot \--access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive
CloudTrail is your first timeline
CloudTrail (Azure calls it the Activity Log, Google Cloud calls it Cloud Audit Logs) is the building's visitor book. It records every control-plane API call: who, what, when, from which IP address. It tells you what an *identity* did, which in a cloud investigation usually matters more than what a *host* did. For fast triage, lookup-events queries the last 90 days with no setup at all. Know where it stops: management events only, one attribute filter per query, and a throttle of two requests per second, per account, per Region. For anything past a quick scoping pass, query the trail's S3 (Simple Storage Service) archive with Athena, which the *Log pipelines* lesson covers. One small detail saves real time here. Temporary session keys start with ASIA, long-lived keys with AKIA, so the prefix alone tells you which kind of credential you are chasing.
# What did the stolen session credential actually do?aws cloudtrail lookup-events \--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=ASIAX7EXAMPLE55Q \--start-time 2026-07-13T06:00:00Z --end-time 2026-07-13T10:00:00Z \--max-items 50 --output json |jq -r '.Events[] | [.EventTime, .EventName,(.CloudTrailEvent | fromjson | .sourceIPAddress)] | @tsv'# 2026-07-13T09:41:03+00:00 GetCallerIdentity 198.51.100.23# 2026-07-13T09:41:29+00:00 ListBuckets 198.51.100.23# 2026-07-13T09:43:11+00:00 CreateUser 198.51.100.23# 2026-07-13T09:43:12+00:00 CreateAccessKey 198.51.100.23# 2026-07-13T09:44:56+00:00 PutUserPolicy 198.51.100.23
Read that output as a story. GetCallerIdentity is the attacker asking *who am I*. ListBuckets is recon. The CreateUser → CreateAccessKey → PutUserPolicy burst is persistence: a brand-new identity that did not exist thirty seconds ago and that your containment plan says nothing about. Your scope just grew. That is why the process diagram is a loop and not a line, since analysis widens containment and wider containment gives you more to acquire. The source IP and that exact API sequence also go straight back into detection engineering as a new correlation rule.
Chain of custody and the case file
Chain of custody works like the label on a hospital blood sample. Everyone who touches it signs and dates the handover, so nobody can argue later that the vial was swapped. In forensics it is the unbroken, documented record of who acquired each piece of evidence, when, how, and proof it has not changed since. In court that record decides admissibility. Inside your own company it decides whether anyone believes your findings. In cloud terms: tag every snapshot with the case ID, copy evidence into a dedicated locked-down account with its own KMS (Key Management Service) key and an SCP (service control policy) that denies deletion, hash every exported artifact the moment you collect it, and keep an append-only custody log. Budget for it as well. Snapshots bill per gigabyte-month, and a legal hold means they pile up for years, so exempt case-tagged snapshots from lifecycle cleanup deliberately rather than by accident.
CASE=IR-2026-0142mkdir -p ~/cases/$CASE && cd ~/cases/$CASE# Confirm the evidence actually exists before you rely on itaws ec2 describe-snapshots --snapshot-ids snap-0e9f8a7b6c5d4e3f2 \--query 'Snapshots[0].[SnapshotId,State,Progress]' --output text# snap-0e9f8a7b6c5d4e3f2 completed 100%# Hash every exported artifact the moment it is createdsha256sum triage-i-0a1b.tar.gz | tee -a evidence.sha256# 9c22b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069 triage-i-0a1b.tar.gz# Append-only custody log: timestamp, analyst, action, artifactecho "$(date -u +%FT%TZ) a.reyes acquired snap-0e9f8a7b6c5d4e3f2 (root vol, pre-reboot)" >> custody.log
Every command above is scriptable, and in production it has to be. Wire the detection to an EventBridge rule that triggers a Step Functions or Lambda runbook, and the quarantine-SG swap, the session revocation and the snapshot set all fire within seconds. A paged human needs twenty minutes or more, by which point auto-scaling may already have eaten the evidence. The trade-off is blast radius. An over-eager auto-containment rule that quarantines production on a false positive is its own incident, so gate the destructive steps on how confident the alert is. Notice what this lesson deliberately skipped: the hands-on capture itself. Dumping memory from a live instance, mounting snapshots on a forensic workstation, collecting triage artifacts without trampling timestamps. That is the next lesson, *Evidence acquisition*.
Cloud DFIR preparation is mostly automation you hope never to run: pre-built evidence buckets with tight IAM (identity and access management) rules, snapshot scripts that can target an instance by tag, legal hold procedures, and a call tree that includes the cloud operations team, because terminating the wrong auto-scaling group destroys evidence and customer availability in one move. Keep a printed or offline copy of the break-glass path too. Identity provider outages and locked admin accounts show up in real incidents far more often than tabletop slides admit.
During identification and containment, isolate rather than terminate until memory and disk are preserved. Cut egress with security groups, NSGs (network security groups, the Azure equivalent) or Kubernetes network policies, and leave the box alive so you can still acquire from it. Write down every API call you make in the case notes, because your own response is part of the timeline somebody will read later. After eradication and recovery, book the lessons-learned session inside a week, and file the detection tickets with named owners before the adrenaline wears off.
Try this
In a disposable lab account, practice the isolate-then-snapshot order on a tagged instance. What you want at the end is a box that cannot reach the internet and is still running, ready for acquisition.
$ INSTANCE=i-0abc12def456$ aws ec2 create-tags --resources $INSTANCE --tags Key=incident,Value=IR-lab$ aws ec2 modify-instance-attribute --instance-id $INSTANCE \--groups sg-isolate-no-egress$ aws ec2 describe-instances --instance-ids $INSTANCE \--query 'Reservations[0].Instances[0].{State:State.Name,SGs:SecurityGroups[*].GroupId}'{"State": "running","SGs": ["sg-isolate-no-egress"]}$ aws ec2 create-snapshots --instance-specification InstanceId=$INSTANCE,ExcludeBootVolume=false# Snapshot IDs returned while state is still running — that is the point.
Takeaway
Cloud evidence expires on a timer somebody else set, so the order of your first four moves decides what you will still have an hour later: freeze the lifecycle, isolate the network, revoke the sessions, then snapshot. Terminate only once you hold copies of what you need.
Next step: script isolate plus snapshot for one cloud provider in your lab, attach it to a high-severity alert as a SOAR (security orchestration, automation and response) suggestion that a human still has to approve, and time the whole path on a game day.
--disable-api-termination with an Auto Scaling detach precisely because the attribute does not stop the group from terminating its own member.CreateUser, CreateAccessKey and PutUserPolicy, all from 198.51.100.23, timestamped a few minutes before you contained anything. What is your next move?