Signal9Signal9 — home
online
all lessons

After-Action Review & Turning Incidents into Detections

Intermediate35 min6 steps
What you'll learn
  • Run a blameless after-action review that produces a timeline of the response, not a list of culprits
  • Compute the metrics that prove improvement — dwell time, MTTD, and MTTR — from the incident timeline
  • Turn every concrete finding from an incident into a durable detection or hardening change so it can't recur silently
  • Feed the lessons learned back into the runbook and hardening backlog so the next responder starts ahead of where you did
  1. Why the after-action is the most important phase

    Frame the post-incident review as the step that converts a painful incident into permanent capability.

    You contained the host, eradicated the persistence, and recovered service. It is tempting to close the ticket and exhale. But an incident you don't learn from is an incident you've agreed to have again. The whole IR lifecycle — detect, contain, eradicate, recover — bends back here, to lessons learned, because this is the only phase that changes the future. Everything before it restored yesterday; this phase buys you tomorrow.

    The deliverable of this phase is not a feeling that you did well. It is measurable improvement: detections that didn't exist before, a faster runbook, a shorter time-to-detect on the next event. If nothing about your environment is different after the review, the review didn't happen — you just had a meeting.

    Think of an aviation accident investigation. When a flight has a serious incident, investigators do not convene to decide which pilot to fire. They reconstruct exactly what happened, second by second, and then they change the checklist so the next crew — who will be tired, rushed, and human in all the same ways — cannot make the same mistake. The output is a procedure change, not a punishment. That is precisely the mindset of a good after-action review.

    Check yourself

    What is the actual deliverable of a post-incident review — a write-up that says the team did well, or something else?

  2. Run it blameless: the response timeline

    Reconstruct a factual timeline of the response and surface what was slow without assigning fault.

    The core artifact of the review is a timeline of the response: when the alert fired, when a human first looked, when containment happened, when the host was confirmed clean, when service was restored. Lay these moments out in order and three things jump out on their own — what worked, what was slow, and where the gaps were (the alert nobody saw for six hours, the runbook step that didn't exist, the credential that should have been rotated sooner).

    The single rule that makes this work is blameless. You are reconstructing a system's behavior, not grading a person's performance. "The alert sat unacknowledged for four hours" is a finding about a paging gap; "Sam ignored the alert" is a finding about nothing useful and a guarantee that next time Sam quietly fixes things off the record so they don't get blamed again. The moment people fear the review, they stop telling you the truth — and a review built on hidden facts improves nothing.

    A simple structure: what happened (the timeline), what went well (keep doing it), what was slow or missing (the improvement candidates), and action items (each with an owner). Note that the response timeline here is distinct from — but built alongside — the attacker timeline you assemble during forensics; cross-reference your timeline-building skill, because the two share the same discipline of ordering events by timestamp from your evidence sources (logs, auditd, file mtimes).

    Check yourself

    Why does a blameless review produce better data than one that identifies who was at fault?

  3. Metrics that prove improvement: dwell time, MTTD, MTTR

    Define the response metrics and read them straight off the incident timeline.

    "We got faster" is an opinion. Metrics turn it into evidence. Three numbers, all derived from the timeline you just built, tell the story of an incident's handling:

    • Dwell time — from initial compromise to detection. How long the attacker lived in your environment undetected. This is the headline number; lower is better and it is brutally honest about your detection coverage.
    • MTTD (mean time to detect) — averaged across incidents, how long from the malicious activity occurring to your team noticing it. Dwell time is the per-incident version of this idea.
    • MTTR (mean time to respond / recover) — from detection to the incident being fully resolved (contained, eradicated, service restored). This measures how good your response is once the alarm rings.

    The split matters: a low dwell time with a high MTTR means you see threats fast but are slow to deal with them (a runbook/staffing problem); a low MTTR with a high dwell time means you respond well but are blind for too long (a detection-engineering problem — exactly what the next step fixes). The metric points you at which part of the program to invest in.

    Every one of these is just a difference between two timestamps from the timeline. You don't need a SIEM to compute one — two ISO timestamps and date will do it. Suppose forensics dated the initial compromise (earliest attacker artifact, e.g. the persistence file's creation) to 2026-06-20 02:14:00, and your detection alert fired at 2026-06-23 09:40:00.

    compromise=$(date -d '2026-06-20 02:14:00' +%s); detected=$(date -d '2026-06-23 09:40:00' +%s); echo "dwell: $(( (detected - compromise) / 3600 )) hours"
    dwell: 79 hours
    -d '<string>'
    Parse the given date/time string instead of using 'now' — lets you feed in any timeline timestamp.
    +%s
    Output format: seconds since the Unix epoch (1970-01-01 UTC), a plain integer that's trivial to do arithmetic on.
    $(( ... ))
    Shell arithmetic expansion: integer math on the two epoch values; here (detected - compromise) / 3600 turns seconds into whole hours.
    date -d '<timestamp>' +%s converts each human-readable time into a Unix epoch second count — a single integer — so the two moments become directly subtractable. Subtracting them gives the gap in seconds; dividing by 3600 converts seconds to hours. The attacker had roughly 79 hours (just over three days) of undetected dwell time. That single number is your detection program's report card for this incident, and you can track it falling over time as you add detections.

    Check yourself

    Which metric measures the time from the moment of compromise to the moment you detected it?

  4. Detection engineering: every finding becomes a detection

    Convert a concrete IOC/TTP from this incident into a durable, automatic detection.

    Here is the engine of improvement, stated as a rule: every concrete finding from the incident becomes either a detection or a hardening change. A finding you merely remembered protects you for about a week. A finding you encoded protects you forever, automatically, even at 3 a.m. when nobody is watching. This is what actually drives dwell time down on the next incident — you catch tomorrow what you missed today.

    Work from the artifacts the incident handed you. Say forensics found the attacker established persistence by writing to /etc/ld.so.preload (a classic library-preload backdoor). That is a specific, durable indicator: legitimate software almost never modifies that file. So you turn it into an auditd watch rule that records every write to it from now on. The next time anyone — attacker or not — touches that file, the kernel's audit subsystem logs it, and your alert fires.

    Let's draft (not install) that rule from the finding. We only print it here; loading rules is a deliberate, reviewed action you do separately.

    echo '-w /etc/ld.so.preload -p wa -k preload_tamper'
    -w /etc/ld.so.preload -p wa -k preload_tamper
    -w <path>
    Watch this filesystem path for the audited operations.
    -p wa
    Permissions to audit: w = write, a = attribute change (ownership/permissions). Captures both planting and tampering.
    -k <key>
    Attach a label to every matching event so it's searchable later with ausearch -k <key>.
    This prints an auditd rule line derived directly from the finding. -w sets a watch on the exact path the attacker abused; -p wa audits write and attribute changes (the operations that would plant or re-plant the backdoor); -k preload_tamper tags every matching event with a searchable key, so a responder can later run ausearch -k preload_tamper and pull every tamper event instantly. We echo it rather than apply it: drafting and reviewing the rule is separate from loading it.
    command -v auditctl >/dev/null && echo "audit present: drop the rule into /etc/audit/rules.d/preload.rules, then run augenrules --load" || echo "auditd not installed here — the rule is drafted; load it on a host that has auditd"
    audit present: drop the rule into /etc/audit/rules.d/preload.rules, then run augenrules --load
    command -v <name>
    Print the path of a command if it exists; exit non-zero (silently) if it doesn't — a safe presence test.
    >/dev/null
    Discard the path output; we only care about the exit status (found / not found).
    && ... || ...
    Run the first echo if auditctl exists, otherwise the second — branch on presence without ever failing.
    command -v auditctl checks whether the audit tooling exists without erroring if it doesn't, so the line is safe on any host. When audit is present, the message states the real deployment path: a watch rule made permanent lives in a .rules file under /etc/audit/rules.d/, and augenrules --load compiles every file there into the running ruleset (and makes it survive reboot). Note we still don't load anything — this step only tells you how, keeping the lesson non-destructive.

    Check yourself

    Forensics found the attacker tampered with /etc/ld.so.preload. What durable artifact should that finding become so the same move is caught automatically next time?

  5. Feed it back: runbook and hardening backlog

    Route every action item into the runbook or the hardening backlog so the improvement is durable.

    Detections catch the attacker faster; the runbook makes the responder faster. Every "that step was slow because we had to figure it out live" moment from the timeline is a runbook gap. If isolating the host took twenty minutes because nobody remembered the firewall command, the runbook gets that exact command, copy-paste ready, so the next responder spends those twenty minutes on the actual investigation. The aviation parallel holds precisely here: you are editing the checklist so the next crew flies the procedure instead of improvising it.

    Findings that aren't detections and aren't runbook fixes are usually hardening changes, and they belong in a tracked hardening backlog with owners and dates — not buried in a report PDF nobody reopens. "Disable library preloading where unused," "enforce MFA on the VPN the attacker phished," "remove the world-writable cron directory" — each is a backlog item that shrinks the attack surface so the class of incident gets harder, not just this instance.

    The loop closes like this: timeline → metrics → findings → (detections + runbook + hardening) → measurably lower dwell time and MTTR next incident. Run it every time, and your program compounds: each incident leaves you better defended than it found you.

    Check yourself

    A timeline shows containment was slow because no one had the host-isolation command handy. Detection, runbook, or hardening backlog — where does the fix go?

  6. Try it in your live terminal

    Compute a real dwell-time metric and draft a detection rule yourself — non-destructively.

    You have a real root shell. Everything here is safe and non-destructive — you'll compute a metric and print a detection rule, but you will not load anything into the audit system. The point is to make the closing-the-loop moves reflex: turn two timestamps into a number, and turn a finding into a draft rule.

    d1=$(date -d '2026-06-20 02:14:00' +%s); d2=$(date -d '2026-06-23 09:40:00' +%s); echo "dwell: $(( (d2-d1)/3600 )) hours"
    Compute dwell time from two ISO timestamps: the first is when forensics dated the compromise, the second when detection fired. Swap in real timestamps from any incident timeline and you have the headline metric in one line — no SIEM required.
    echo '-w /etc/ld.so.preload -p wa -k preload_tamper'
    Draft (print only) an auditd watch rule from a finding. This does not change the system — it produces the rule text you would review and then place in /etc/audit/rules.d/ on a real host.
    command -v auditctl >/dev/null && echo "load into /etc/audit/rules.d/ then augenrules --load" || true
    Safely check whether this host even has auditd before talking about loading rules. If present, it prints the deployment path; if not, the || true makes the line succeed quietly. Still loads nothing — drafting and loading stay separate, deliberate steps.
Recap
  • The after-action is the only IR phase that changes the future — its deliverable is measurable improvement, not a debrief or a culprit.
  • Run it blameless: investigate the system via a response timeline, never the person, or people hide the facts the review depends on.
  • Metrics make improvement provable — dwell time and MTTD (detection coverage) and MTTR (response speed) are just timestamp differences from the timeline.
  • Every finding becomes a detection or a hardening change — encoded controls (auditd rules, saved queries, Sigma rules, backlog items) protect you automatically; memory doesn't.
  • Close the loop by feeding fixes into the runbook (responder speed) and hardening backlog (attack surface), each item owned and dated.
Finding → durable control it becomes
Incident findingDurable controlForm
Attacker wrote to /etc/ld.so.preloadauditd watch rule on that path (-p wa -k preload_tamper)Detection
Beacon to a known-bad external IPSaved log/SIEM query as a standing alert + firewall blockDetection + hardening
Distinctive malicious process name / command lineSigma-style rule compiled to your SIEMDetection
VPN credential was phishedEnforce MFA on the VPN; rotate the credentialHardening
World-writable cron directory enabled persistenceTighten permissions; add to hardening backlogHardening
Containment was slow (no isolation command handy)Add the exact isolation command to the runbookRunbook (lowers MTTR)
79-hour dwell time went unmeasuredRecord dwell time + MTTR every incident; track the trendMetric