# Chapter 18 Labs: Incident Analysis and Response (Part II)

This lab pack supports Chapter 18 of *Applied Cybersecurity: Foundations to Mastery*. The chapter covers the NIST incident-response lifecycle, SOC/SIEM operations, network and endpoint detection, detection engineering with Sigma and ATT&CK, tabletop exercises, breach-notification clocks, and post-incident review. Two labs (Lab 1 and Lab 2) run entirely in containers; one lab (Sigma authoring) runs in a dedicated tools container or on the host; and two labs (18.4 tabletop, 18.5 notification drill) are discussion-based exercises that require no infrastructure.

## Prerequisites

| Item | Requirement |
|------|-------------|
| Docker Engine | 24.x or later |
| Docker Compose | v2.20 or later (the `docker compose` plugin, not standalone `docker-compose`) |
| Free disk space | ~3 GB for Elasticsearch + Kibana images |
| RAM | 4 GB available to Docker (8 GB recommended for Wazuh Lab 1) |
| Internet access | Required on first run to pull images; offline after that |

**Wazuh Lab 1 only:** also requires `git`, about 8 GB RAM headroom, and a working internet connection to clone the official Wazuh Docker repository.

## Quick Start

```bash
# Start the ELK stack (Lab 2)
docker compose up -d elasticsearch kibana

# Run the Sigma tools container (Lab 18.3)
docker compose --profile tools run --rm sigma-tools bash

# Ingest the synthetic breach dataset once Elasticsearch is healthy
docker compose --profile tools run --rm elk-tools bash /lab/elk/ingest.sh
```

---

## Lab 1: Wazuh in Docker -- Ingest Endpoint and Web Logs, Build a Small Detection Lab

**Host setup (not containerised here):** Wazuh ships and maintains its own tested single-node Docker Compose. Do not hand-roll an alternative; use the official deployment. The upstream project is at <https://github.com/wazuh/wazuh-docker>.

### Objective

Stand up an open-source SIEM/XDR (Wazuh v4.14.x stable), feed it web-server and Windows-style authentication logs, watch its built-in rules fire, and read the rule that triggered to understand detection engineering from the analyst side.

### Setup

1. **Clone the official Wazuh Docker repository and check out the v4.14.5 tag:**

   ```bash
   git clone https://github.com/wazuh/wazuh-docker.git -b v4.14.5
   cd wazuh-docker/single-node
   ```

2. **Generate the required TLS certificates** (Wazuh's indexer, manager, and dashboard all require mutual TLS):

   ```bash
   docker compose -f generate-indexer-certs.yml run --rm generator
   ```

3. **Bring the stack up:**

   ```bash
   docker compose up -d
   ```

4. **Confirm health:**

   ```bash
   docker compose ps
   # All three services (wazuh.manager, wazuh.indexer, wazuh.dashboard) should show healthy.
   ```

5. **Browse to the dashboard:** open `https://localhost:443` in a browser. Accept the self-signed certificate warning. Log in with the default credentials documented for v4.14.5 in the official Wazuh documentation, then **change them immediately**.

### Walkthrough

**Step 1 - Ingest a web log.** Copy the synthetic Apache access log from this pack into the Wazuh manager container and tell Wazuh to read it.

The sample log file is at `seed/wazuh/sample-apache-access.log`. It contains crafted sqlmap and path-traversal request lines that will trigger Wazuh's built-in web-attack rules (rule groups 30100-30199 in the default ruleset).

```bash
# Copy the log into the running manager container
docker cp seed/wazuh/sample-apache-access.log \
  $(docker compose ps -q wazuh.manager):/var/ossec/logs/alerts/apache-test.log

# Tell the agent/manager to read the file (edit ossec.conf or use the CLI).
# Simplest for lab use: append to an existing monitored localfile block, or
# inject via the Wazuh API. See the Wazuh docs: "Adding log files".
```

After ingestion, open **Threat Hunting** in the Wazuh dashboard and filter by the source log file. You should see alerts for the SQL-injection attempt and the path-traversal.

**Step 2 - Ingest endpoint events.** Enrol a second container (or a VM) as a Wazuh agent. On Ubuntu:

```bash
curl -s https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/\
wazuh-agent_4.14.5-1_amd64.deb -o /tmp/wazuh-agent.deb
WAZUH_MANAGER=<manager-ip> dpkg -i /tmp/wazuh-agent.deb
systemctl enable --now wazuh-agent
```

Trigger a burst of failed SSH logons followed by a success on the agent host. The Wazuh rule 5710-series (authentication failure storm) and rule 5503 (successful login after failures) should fire.

**Step 3 - Read the rule that fired.** In the Wazuh manager container:

```bash
docker exec -it $(docker compose ps -q wazuh.manager) bash
grep -r "id=\"5710\"" /var/ossec/ruleset/rules/
```

Read the rule XML to trace why it matched. This is the bridge between detection and detection engineering covered in section 18.4.

### Expected Output

- Three Wazuh services show `healthy` in `docker compose ps`.
- Dashboard at `https://localhost:443` shows alerts for the path-traversal and SQLi lines.
- An auth-failure-storm alert fires for the SSH brute-force scenario.

### Verify Completion

```bash
# From the dashboard: Threat Hunting -> filter last 24h
# You should see at least one alert with rule.group: web-attack
# and one with rule.id in the 5710 range.

# Or via the Wazuh API:
curl -k -u wazuh-wui:<password> \
  "https://localhost:55000/alerts?limit=5&sort=-timestamp" | python3 -m json.tool
```

### Cleanup

```bash
cd wazuh-docker/single-node
docker compose down -v
```

---

## Lab 2: ELK Stack Triage -- Investigate a Simulated-Breach Dataset

### Objective

Practise the analysis half of detection-and-analysis: load a synthetic breach dataset into Kibana (Elasticsearch + Kibana 8.13.x), triage it using Discover filters and pivots, reconstruct the kill chain in timestamp order, and write a short incident timeline.

### Setup

```bash
# Start the ELK stack (takes 60-90 s for both services to become healthy)
docker compose up -d elasticsearch kibana

# Wait for Kibana:
docker compose ps
# ch18-elasticsearch should show "(healthy)" and ch18-kibana likewise.

# Load the synthetic breach dataset:
docker compose --profile tools run --rm elk-tools bash /lab/elk/ingest.sh
```

The ingest script creates the `ch18-breach` index and loads 14 synthetic events representing one compressed intrusion: initial web exploitation, brute-force logon, process execution (web shell), account creation, C2 beacon, privilege escalation, and log-clearing.

**Create a Kibana Data View:**
1. Open `http://localhost:5601`.
2. Navigate to **Stack Management -> Data Views -> Create data view**.
3. Name: `ch18-breach`, Index pattern: `ch18-breach*`, Time field: `@timestamp`.
4. Save and open **Discover**.

### Walkthrough

**Step 1 - Find the initial access.** In Discover, set the time range to cover the dataset (e.g., March 2026). Add a filter:

```
url.path : *../* OR user_agent.original : *sqlmap*
```

You should see two web hits. Note the earliest timestamp: this anchors your timeline.

**Step 2 - Pivot to the host.** Using the source IP (`203.0.113.42`) and the initial timestamp, search authentication events:

```
event.code : 4625
```

You will find three consecutive Event 4625 (failed logon, Logon Type 3 - network). Then:

```
event.code : 4624
```

One Event 4624 follows shortly after - a successful network logon from the same source IP. This is the brute-force-then-success pattern from section 18.2.3.

**Step 3 - Follow the kill chain.** Search for Sysmon events to trace post-access activity:

- `event.code : 1` -- process creation. Look for `w3wp.exe` spawning `cmd.exe`, then `cmd.exe` spawning `net.exe` with an account-creation command.
- `event.code : 3` -- network connection. Find the outbound connection to port 4444 (C2 beacon).
- `event.code : 11` -- file created. Beacon binary written to `Temp`.

Then search for:
- `event.code : 4720` -- new account created (`backdoor`).
- `event.code : 4728` -- account added to Domain Admins.
- `event.code : 4672` -- special privileges assigned.
- `event.code : 1102` -- audit log cleared (cover-up).

**Step 4 - Write the timeline.** Produce a short ordered table:

| Timestamp (UTC) | Source | Event ID | Interpretation |
|-----------------|--------|----------|----------------|
| 02:11:03 | web | - | SQLi probe via sqlmap |
| 02:14:01-03 | WKSTN-042 | 4625 x3 | Brute-force (network logon) |
| 02:14:09 | WKSTN-042 | 4624 | Successful logon after failures |
| 02:15:22 | WKSTN-042 | Sysmon 1 | Web shell: w3wp spawns cmd+whoami |
| ... | ... | ... | ... |

This timeline is the raw material for a post-incident review (section 18.6).

### Expected Output

- Kibana Discover shows 14 events across the `ch18-breach` index.
- Filtering by `event.code : 1102` returns exactly one document (the log-clear event at 03:04:58).
- The complete kill chain is traceable from the first sqlmap request to the 1102 cover-up event.

### Verify Completion

```bash
# Count events in the index:
curl -s "http://localhost:9200/ch18-breach/_count" | python3 -m json.tool
# Expect: {"count": 14, ...}

# Confirm the 1102 log-clear event is present:
curl -s "http://localhost:9200/ch18-breach/_search?q=event.code:1102" | \
  python3 -m json.tool | grep -c '"event.code": "1102"'
# Expect: 1
```

### Cleanup

```bash
docker compose down -v
# Removes containers, networks, and the es-data and kibana-data volumes.
```

---

## Lab 18.3 (Sigma Rule Authoring): Observe, Write, Validate, Convert

### Objective

Author a complete Sigma rule for a web-server process spawning `whoami.exe`, validate it with `sigma check`, convert it to Splunk SPL and Elasticsearch Lucene, and understand why the same logic produces different syntax across backends.

### Setup

This lab runs in the `sigma-tools` container (Python 3.12 with sigma-cli pre-installed) or on the host with a Python virtual environment. The container is simpler and keeps sigma-cli off the host system.

**Container method (preferred):**

```bash
# Build and enter the sigma-tools container
docker compose --profile tools run --rm sigma-tools bash
# You are now at /lab inside the container.
# The sample rule is at /lab/sigma/whoami_web.yml
# Process log samples are at /lab/sigma/process_log_sample.jsonl
```

**Host method (alternative):**

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install sigma-cli pysigma-backend-splunk pysigma-backend-elasticsearch
```

### Walkthrough

**Step 1 - Inspect sigma-cli:**

```bash
sigma version
sigma plugin list
# Expect: splunk and elasticsearch listed as installed
```

**Step 2 - Observe the TTP.** Read the process log sample:

```bash
cat /lab/sigma/process_log_sample.jsonl
# Find the line where ParentImage ends with w3wp.exe and Image ends with whoami.exe.
# These are the STABLE fields to match on.
# Do NOT hard-code the specific PID or timestamp - those are volatile.
```

**Step 3 - Read the pre-written rule and understand its structure:**

```bash
cat /lab/sigma/whoami_web.yml
```

The rule demonstrates all required anatomy: `title`, `id` (UUID), `status: experimental`, `description`, `logsource` (category + product), `detection` block (named `selection` + `condition`), `falsepositives`, `level`, and ATT&CK `tags`.

Write your own rule from scratch in a new file (`/lab/sigma/my_rule.yml`) using the sample as reference.

**Step 4 - Validate:**

```bash
sigma check /lab/sigma/whoami_web.yml
# A clean rule prints nothing (exit 0).
# Introduce a deliberate YAML error (e.g., remove "condition:") and re-run to see validation catch it.
```

**Step 5 - Convert to multiple backends:**

```bash
# Convert to Splunk SPL:
sigma convert -t splunk /lab/sigma/whoami_web.yml
# Expected output (approximately):
# ParentImage="*\\w3wp.exe" Image="*\\whoami.exe"

# Convert to Elasticsearch Lucene:
sigma convert -t elasticsearch -f lucene /lab/sigma/whoami_web.yml
# Expected output (approximately):
# winlog.event_data.ParentImage.keyword:*\\w3wp.exe AND winlog.event_data.Image.keyword:*\\whoami.exe

# Confirm the detection LOGIC is identical; only the syntax differs.
```

**Step 6 - Map coverage.** Note the ATT&CK technique ID (`t1033`, `t1505.003`) in the rule tags. Open the ATT&CK Navigator at <https://mitre-attack.github.io/attack-navigator/> and highlight those techniques to see what your rule covers and what nearby techniques remain undetected.

### Expected Output

- `sigma check whoami_web.yml` exits 0 with no errors.
- Splunk conversion produces a `ParentImage/Image` SPL query.
- Elasticsearch conversion produces an equivalent Lucene query.

### Verify Completion

```bash
# Inside the sigma-tools container:
sigma check /lab/sigma/whoami_web.yml && echo "PASS: rule is valid"
sigma convert -t splunk /lab/sigma/whoami_web.yml | grep -q "w3wp" && echo "PASS: Splunk conversion OK"
sigma convert -t elasticsearch -f lucene /lab/sigma/whoami_web.yml | grep -q "w3wp" && echo "PASS: ES conversion OK"
```

### Cleanup

```bash
# The sigma-tools container is ephemeral (--rm flag above).
# Any rules you wrote inside the container are lost unless you used a bind-mount
# or copied them out first:
docker cp ch18-sigma-tools:/lab/sigma/my_rule.yml ./my_rule.yml
```

---

## Lab 18.4: Tabletop Ransomware Exercise with Role Cards and an After-Action Report

**Host setup (not containerised) - Discussion-based exercise: no systems are touched.**

### Objective

Rehearse the organisation's incident-response decision-making against a realistic ransomware scenario using role cards and timed injects, then produce an after-action report (AAR) that feeds back into preparation.

### Setup

Materials required:
- A meeting room, a timer, and printed (or shared-screen) role cards.
- 5-8 participants.
- 60-90 minutes.
- A facilitator who will not participate as a role player.

Canonical exercise resource: CISA Tabletop Exercise Packages (CTEP), available free at <https://www.cisa.gov/resources-tools/services/cisa-tabletop-exercise-packages>. The ransomware CTEP is the closest match to this scenario.

### Walkthrough

**Step 1 - Build the scenario.** Adapt the HSE-style scenario: a phishing email arrives, a workstation is compromised, and eight weeks later Conti-style ransomware detonates at 01:00. The facilitator controls the "clock" (the simulated time of awareness).

**Step 2 - Write the role cards.** Produce one card per role. Each card states: the role's authority, starting knowledge, and one critical thing it does NOT know.

| Role | Authority | Knows at start | Does NOT know |
|------|-----------|----------------|---------------|
| Incident Commander | Owns the response; makes containment calls | SOC alert: anomalous beacons on 3 hosts | Whether other sites are affected |
| Communications Lead | Owns all external and internal messaging | Incident is real and confirmed | Scope of affected data |
| Legal/DPO | Owns breach-notification decisions; tracks regulatory clocks | GDPR, NIS2, DORA obligations | Whether personal data is confirmed affected |
| IT Operations | Executes containment; controls network segmentation | What shutting down systems will cost operationally | Attack vector |
| Executive/CEO | Owns business risk and external relationships | Board expects a briefing at 09:00 | Whether to pay the ransom |

**Step 3 - Run the injects.** The facilitator releases these in sequence:

1. (T+0) SOC reports anomalous Cobalt Strike-style beacons on 3 workstations. "Do you act now or wait for confirmation?"
2. (T+30 min simulated) Ransom note appears on 200 endpoints. Recovery time estimate: 2-4 weeks.
3. (T+45 min simulated) A journalist tweets that the organisation's systems are down. Comment requested.
4. (T+60 min simulated) A critical operational system reports it cannot access patient or customer data.
5. (T+8 h simulated awareness) "Has anyone started the GDPR 72-hour notification? The clock is running."

Capture every decision and its timestamp as the simulation progresses.

**Step 4 - Produce the AAR.** Within one week of the exercise, write:

- One-paragraph scenario summary.
- Timeline of decisions actually taken and by whom.
- What went well (roles that functioned, decisions made confidently).
- What went wrong (gaps in authority, missing information, notification clock missed).
- Numbered corrective actions, each with an owner and a due date.

The AAR is the only durable output of a tabletop. Feed its corrective actions into the incident-response plan and the next exercise scenario.

### Expected Output

- Completed role cards (5 cards).
- Recorded decision log from the exercise session.
- Written AAR with at least 3 corrective actions, each owned and dated.

### Verify Completion

Review: does the AAR specify who is responsible for starting the GDPR 72-hour clock, and is that person named? If not, that is corrective action #1.

### Cleanup

No infrastructure to tear down. Archive the role cards and AAR in the organisation's IR documentation.

---

## Lab 18.5: Notification Drill -- Draft a Supervisory-Authority Notification Inside the 72-Hour Window

**Host setup (not containerised) - Document-based exercise: no systems are touched.**

### Objective

Using the tabletop incident from Lab 18.4 as the scenario, draft a GDPR Article 33 notification to the Data Protection Commission (DPC) at the hour-12 mark, identify any additional NIS2 or DORA clocks also running, and review the draft against the single-source-of-truth discipline.

### Setup

Required materials:
- The decision log and scenario facts from Lab 18.4.
- A text editor or word processor.
- The DPC's guidance on Article 33 notifications: <https://www.dataprotection.ie/en/organisations/know-your-obligations/data-breach-notification> (accessed 2026).

### Walkthrough

**Step 1 - Identify the regime(s).** Confirm the scenario triggers GDPR Article 33 (personal data - customer names, email addresses, order histories - has been encrypted/exfiltrated). If the fictional entity is also an NIS2 essential entity, the 24-hour early warning to the NCSC is already overdue at hour 12. If it is a financial entity under DORA, the 4-hour classification clock may also have started.

| Regime | Authority | Clock | Status at hour 12 |
|--------|-----------|-------|-------------------|
| GDPR Art 33 | DPC | 72 h from awareness | 60 h remaining |
| NIS2 Art 23 | NCSC | 24 h early warning | OVERDUE if not sent |
| DORA Art 19 | Financial regulator | 4 h from classification | OVERDUE if applicable |

**Step 2 - Draft the Article 33 notification.** The notification must include:

1. **Nature of the breach** - categories of data (names, email, order history), approximate number of data subjects (use the scenario figure), and approximate number of records.
2. **Name and contact details of the DPO** - use the fictional DPO from the role card.
3. **Likely consequences** - risk to individuals: phishing, identity fraud, reputational harm.
4. **Measures taken or proposed** - containment actions, evidence preservation steps, planned remediation.

Where a fact is not yet known at hour 12 (e.g., exact exfiltration volume), state explicitly: "Under investigation; update will follow within the 72-hour window." GDPR permits phased notification.

**Step 3 - Apply the do-not-speculate rule.** Read back every sentence in the draft. Remove any assertion not confirmed by the decision log from Lab 18.4. Mark unknowns explicitly. Cross-check against any holding statement produced in Lab 18.4 so the two documents do not contradict each other.

**Step 4 - Time-box it.** Record the "became aware" timestamp from Lab 18.4 (the moment the ransom note appeared), compute the 72-hour deadline, and confirm the draft would reach the DPC before that deadline.

### Expected Output

- A one-page draft Article 33 notification (could be submitted to the DPC).
- A one-line note identifying any additional NIS2 or DORA clocks and their status at hour 12.

### Verify Completion

Ask: does the draft contain all four mandatory elements (nature, DPO contact, consequences, measures)? Does it flag any unknowns explicitly rather than omitting or speculating? Is the 72-hour deadline computed from the correct timestamp?

### Cleanup

Archive the draft as an IR documentation artefact. If used in a real organisation, treat the draft as legally privileged and store it securely.

---

## Teardown (All Container Labs)

```bash
# Stop all containers and remove volumes:
docker compose down -v

# Remove locally built images:
docker rmi ch18-elk-tools:local ch18-sigma-tools:local 2>/dev/null || true
```

---

## Safety and Authorisation

Every technique in this chapter is dual-use. Run these labs **only against the provided containers, the provided synthetic datasets, or systems you own with explicit written authorisation**. Pointing log collectors, scanners, or attack tooling at infrastructure you do not control may be a criminal offence under the Irish *Criminal Justice (Offences Relating to Information Systems) Act 2017*, the UK *Computer Misuse Act 1990*, or the US *Computer Fraud and Abuse Act*. "I was only learning" is not a defence.

Keep the container lab entirely on an isolated host network. Never expose Elasticsearch, Kibana, or the Wazuh dashboard to the public internet without authentication and TLS.

The synthetic breach dataset (`seed/elk/breach-dataset.ndjson`) and the Apache log sample (`seed/wazuh/sample-apache-access.log`) are fabricated training data. They contain no real victim IP addresses, no real credentials, and no actual malicious payloads. The EICAR test string convention is followed: these files are safe to store, transmit, and analyse.
