Cloud DFIR process

Order of volatility; automate before teardown.

Expert35 min · lesson 10 of 15

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.

Cloud DFIR: detection to detection
1Detect
alert fires
2Contain
quarantine + revoke
3Acquire
volatile first
4Analyze
timeline the actor
5Learn
new detections
Contain and Acquire fire almost together in the cloud: automation runs both before auto-scaling teardown can wipe the state, and Analyze regularly loops back to widen containment.

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.

contain-instance.sh
# 0. Scope before touching anything — record instance, state, and ENI
aws 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 well
aws ec2 modify-instance-attribute --instance-id i-0a1b2c3d4e5f6a7b8 \
--disable-api-termination
aws 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 case
aws 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" }
# ]
# }
A quarantine security group will not cut a live session
Security groups are stateful, meaning they remember connections they have already allowed. Swap in a deny-all group and you block *new* connections, while existing tracked flows, such as the attacker's open reverse shell, keep running until they time out or close. To break an established connection you need a deny-all network ACL (NACL, an access control list applied at the subnet level). NACLs are stateless, so they do interrupt tracked flows, at the cost of isolating the whole subnet. Moving the instance into an isolated subnet works too. Either way, check VPC Flow Logs and confirm the traffic actually stopped. Assuming containment worked is how exfiltration keeps running *during* your response.

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.

contain-identity.sh
# Deny every session issued before this moment; new (legitimate) sessions still work
aws 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.

cloudtrail-triage.sh
# 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 CreateUserCreateAccessKeyPutUserPolicy 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-file.sh
CASE=IR-2026-0142
mkdir -p ~/cases/$CASE && cd ~/cases/$CASE
# Confirm the evidence actually exists before you rely on it
aws 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 created
sha256sum triage-i-0a1b.tar.gz | tee -a evidence.sha256
# 9c22b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069 triage-i-0a1b.tar.gz
# Append-only custody log: timestamp, analyst, action, artifact
echo "$(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.

terminal
$ 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.

Quick check
01You contain a compromised EC2 instance by swapping all of its security groups for a pre-built deny-all quarantine group. Minutes later, VPC Flow Logs still show the attacker's reverse shell shipping data out. What went wrong?
Correct — The swap only blocks new flows, so the tracked reverse shell survives until it times out or closes. A stateless NACL is what interrupts an established connection.
Incorrect — No. This is the common misconception. Even a flawless zero-rule group cannot sever an existing tracked flow, because statefulness keeps it alive, not the rule count.
Incorrect — No. Termination protection, and detaching from the auto scaling group, only stop teardown that would destroy evidence. Neither one touches network sessions.
Incorrect — No. A stolen STS token lets the attacker replay credentials against the control-plane API from anywhere, but it does not hold a host TCP connection open. Statefulness is why the shell persists.
02You enable termination protection on the compromised instance and kick off your snapshot. Ten minutes later the instance is gone and the capture never finished. What did you miss?
Correct — The lesson pairs --disable-api-termination with an Auto Scaling detach precisely because the attribute does not stop the group from terminating its own member.
Incorrect — No. Stopping the instance is the thing you are trying to avoid, since it wipes memory. The attribute applies to a running instance perfectly well.
Incorrect — No. Nothing revokes the attribute. Failing health checks make the auto scaling group replace the instance, which is why detaching it from the group is the fix.
Incorrect — No. Snapshots are taken by the control plane with no agent on the box, and they do not change instance attributes.
03The instance is quarantined and you have revoked the role's sessions. Your CloudTrail triage then returns CreateUser, CreateAccessKey and PutUserPolicy, all from 198.51.100.23, timestamped a few minutes before you contained anything. What is your next move?
Incorrect — No. The revoke policy is scoped to that role's sessions by issue time. The freshly created IAM user has its own long-lived key that keeps working.
Correct — Analysis widens containment. Deactivating keeps the artifact and lets you alert on attempted reuse, and the IP and call sequence become your next detection.
Incorrect — No. Deleting destroys the artifact and your ability to alert when the attacker tries the key again. The lesson's rule is deactivate, do not delete.
Incorrect — No. Stopping wipes memory and any instance-store volumes, so you destroy evidence, and the new identity works from the attacker's own machine anyway.

Related