The previous lesson ended with an uncomfortable sentence: vulnerability analysis finds badly closed doors, but does not see anyone walking through them. If tomorrow somebody uses the consultancy's credential at 22:14, no scanner will notice: the access is legitimate and the credential is valid. This lesson builds what is missing and delivers on the promise outstanding since 04-03, where the analysis of the incident in 02-06 revealed that Nimbus's ransomware ran into no detective control at all in five of the six functions. We are going to set up the telemetry, centralise it, write detections that fire on what matters and stay silent on what does not, and measure whether they work.

Contents

  1. Why detection matters more than perfect prevention
  2. The telemetry pyramid: what sources Nimbus has
  3. Logging hygiene: what is kept, how and for how long
  4. Protecting the logs themselves
  5. Centralisation: SIEM and observability
  6. Rule-based detection versus behaviour-based detection
  7. Nimbus's minimum detection catalogue
  8. Writing detections: Sigma, SQL and thresholds
  9. Alert quality and fatigue
  10. Detection on the network and on the endpoint
  11. Automated response: what yes and what never
  12. Threat hunting and detection metrics

  1. Why detection matters more than perfect prevention

Prevention always fails somewhere, and not out of incompetence: it fails because the attacker chooses where to try and you have to get it right everywhere at once. A patch arrives late, an employee clicks, a third party suffers a breach, a credential leaks. A security model that only prevents is a model betting on never failing.

The metric that sums up the failure or success of detection is dwell time: the interval between the attacker's first access and the moment somebody finds out. It is the variable that most determines the damage, as 02-06 concluded.

Moment in the Nimbus incident Day What would have been detectable
Access with the consultancy's shared account 0 Third-party authentication outside the agreed window
Reconnaissance from the administration network 1-4 Internal connections towards the data zone
Access to PostgreSQL with a credential from a runbook 5 A database session from an unusual source
Exfiltration of 1.2 TB over seven days 6-15 Anomalous outbound volume and bulk access to bucket A-02
Anomalous cost alert, ignored among 200 e-mails 16 The signal existed and the channel buried it
Backup deletion and encryption 20 Bulk deletion of snapshots
Actual detection 20 Through customer phone calls

Two readings matter more than the rest. The first: there were seven detection opportunities and not one was instrumented. The second, subtler one: the day-16 opportunity did exist and failed all the same, because it arrived as one e-mail among two hundred. A signal with no recipient, no priority and no procedure is not a detection: it is well-intentioned noise. Cutting dwell time from 20 days to one day does not require a 24-hour SOC; it requires the twelve detections in section 7 and a channel where somebody looks at them.


  1. The telemetry pyramid: what sources Nimbus has

You cannot detect what you do not log. Before writing a single rule you have to know what data already exists — almost always more than the team believes — and what each source lets you see.

Source What it lets you detect Volume/day Cost to enable
FastAPI application logs Business abuse: bulk exports, attempted IDOR, per-tenant patterns 400 MB None (they already exist)
Nginx access logs Brute force, path scanning, anomalous agents, 4xx/5xx error spikes 300 MB None
PostgreSQL logs Connections from unexpected sources, authentication errors, slow or bulk queries 80 MB Low (log_connections, pgaudit)
Append-only audit table (C-07) Who accessed which piece of which customer's data. Nimbus's most valuable source 60 MB Already implemented
System auditd Process execution, changes to sensitive files, sudo use 150 MB Low (05-06)
Authentication (SSO, SSH, VPN) Password spraying, access from an unusual country, failed MFA 20 MB None
Cloud provider activity log Policy changes, user creation, logging being disabled 200 MB Low (05-07)
Access logs for bucket A-02 Bulk download of clinical attachments 100 MB Low, disabled today
DNS Command and control, newly registered domains, DNS exfiltration 250 MB Low (05-04)
E-mail Inbound phishing, forwarding rules created by an attacker 10 MB None
EDR / Wazuh on endpoints Malware, persistence, lateral movement across the 40 laptops 500 MB Medium (§10)

The decisive observation: nine of the eleven sources already exist or cost next to nothing. What Nimbus lacked on the day of the incident was not a telemetry budget; it was collecting it in one place and looking at it. And the two absences that weighed most — bucket access logs and a reviewed cloud activity log — are configuration checkboxes, not products.


  1. Logging hygiene: what is kept, how and for how long

What is logged and what never is

Picking up from 02-04: a log is an asset containing personal data (A-18, classified confidential). Over-logging creates a second sensitive database, worse protected than the first.

Always log Never log
Timestamp with time zone, request identifier Passwords, not even failed or "truncated" ones
Actor identity (user, service) and tenant_id Tokens, session cookies, API keys
Action, resource affected and result (success/failure) Health data, clinical notes, full response bodies
Source IP and user agent Card numbers or payment data
Permission, configuration and policy changes Attachments or their contents

Structured format

A free-text log forces you to write brittle regular expressions. A JSON log is queried like data:

# app/core/logging.py - structured logger for the Nimbus API
import json, logging, time, uuid
from contextvars import ContextVar

# request_id travels through the whole request without being passed as a parameter:
# it is what later lets you reconstruct the 40 events generated by a single call.
request_id: ContextVar[str] = ContextVar("request_id", default="-")

class JsonFormatter(logging.Formatter):
    def format(self, record):
        event = {
            "ts": time.strftime("%Y-%m-%dT%H:%M:%S%z", time.gmtime(record.created)),
            "level": record.levelname,
            "event": record.getMessage(),       # stable name: "booking.exported"
            "request_id": request_id.get(),
            "service": "nimbus-api",
            "host": record.name,
        }
        # Business fields arrive via `extra` and NEVER include clinical data:
        # tenant_id and actor_id are identifiers, not content.
        event.update(getattr(record, "fields", {}))
        return json.dumps(event, ensure_ascii=False)

# Use in the export endpoint, which is the one detection D-05 watches:
log.info("booking.exported", extra={"fields": {
    "actor_id": user.id, "actor_role": user.role,
    "tenant_id": tenant.id, "record_count": len(rows),
    "ip": request.client.host,
}})

Three decisions matter more than the code: the event name is stable (booking.exported, not a sentence that changes with every refactoring, because rules break when the text changes); tenant_id is always present, which makes it possible to detect that a user from one customer touched another's data; and content never goes in, only its count (record_count).

Time and retention

Without synchronised clocks, correlation is impossible. If the API server runs 40 seconds ahead of the database server, the sequence "request first, query afterwards" appears inverted and the investigation builds a false story. Every Nimbus system synchronises over NTP, logs in UTC and adds the time zone only at presentation time. It is free and it is the prerequisite for everything else.

Retention is decided on cost and obligation: Nimbus keeps 90 days hot (queryable in seconds, ~2 TB) and 12 months cold (cheap storage, restorable in hours), aligned with the 12-month retention of the C-07 audit table. The underlying criterion: if typical dwell time is around 20 days, a 7-day retention guarantees the investigation starts blind.


  1. Protecting the logs themselves

The first target of a privileged attacker is not the data: it is the record that shows what they did. It is the Kill Chain phase 02-06 documented as covering tracks, and that is why logs need three properties:

  • Leave the host immediately. A log that only lives on the compromised machine is deleted with one command. The agent sends every event to the collector within seconds, not in overnight batches.
  • Be stored immutably. A bucket with locked retention and versioning (05-07), in a separate account from production and with different credentials. It is the same lesson as the backups in 04-06: if the same credentials that administer production can delete the log, there is no log.
  • Be written one way only. The service that generates logs has write permission, never delete or modify.

And an indicator many forget: the absence of logs is an alert. If a source that emits 400 MB a day stops emitting for an hour, that is a detection (D-09 in the catalogue), not a monitoring glitch to be looked at the next day.


  1. Centralisation: SIEM and observability

SIEM Observability stack
Question it answers Has something bad happened? Why is it slow or failing?
Designed for Correlation, detection, long retention, chain of custody Metrics, traces and fast log queries
Free examples Wazuh, OpenSearch + rules Loki + Grafana, Prometheus, Tempo
What the other one lacks Can query logs, but with no performance metrics Can alert, but with no security or compliance rules
flowchart LR
    subgraph Sources
      A["FastAPI API\nstructured JSON"]
      B["Nginx / PostgreSQL"]
      C["auditd / SSH\n40 laptops"]
      D["Cloud: activity,\nbucket A-02, DNS"]
    end
    A & B & C --> AG["AGENT\nwazuh-agent / promtail\nsends within seconds"]
    D --> AG2["Cloud collector\n(pull over API)"]
    AG & AG2 --> CO["COLLECTOR\nnormalises and enriches:\ncommon fields, geoIP, asset"]
    CO --> AL["STORE\n90 days hot +\n12 months cold immutable"]
    AL --> DE["DETECTION ENGINE\nSigma rules, thresholds,\nbaseline"]
    DE --> AC["ALERT\nsecurity channel\n-> runbook RB-01 (04-05)"]
    AL --> CZ["QUERY\ninvestigation and\nthreat hunting"]

The step that decides whether the system is any use is normalisation at the collector: turning usuario, user, account_name and principalId into a single actor.id field. Without it, every rule has to be written five times and cross-source correlation does not exist.

Option for an SME Cost/year Set-up effort When to choose it
Wazuh (self-hosted) ~600 € of infrastructure 40-60 h initially + 4 h/month Recommended for Nimbus: SIEM + endpoint agent + FIM + compliance in a single product
Loki + Grafana ~400 € 20 h + 2 h/month If Grafana is already in place; very good at querying, poorer at detecting
OpenSearch + your own rules ~1,200 € 80 h + 8 h/month High volumes and a need for total flexibility
Managed SIEM (MDR) 6,000-25,000 € 10 h When what you are buying is the human eye 24/7, not the tool
Commercial Elastic/Splunk From 15,000 € High Outside Nimbus's 18,000 €/year budget

Nimbus's decision: self-hosted Wazuh. It covers all eleven sources, includes an agent for the 40 laptops and leaves budget over. Honesty requires stating what it does not cover: nobody will look at the alerts at 3 in the morning. With 440 h/year of Lucía's time, the realistic goal is not a SOC: it is for critical alerts to reach an on-call channel and for the rest to be reviewed every morning.


  1. Rule-based detection versus behaviour-based detection

Approach How it works Strong at Typical false positive
Signature Looks for an exact pattern (command, hash, domain) Known threats; zero ambiguity Almost none, but it is evaded by changing one byte
Threshold Counts events per time window Brute force, exfiltration by volume A customer migration that exports 50,000 legitimate records
List Allowlists or blocklists (IPs, countries, processes) Reducing noise and bounding the expected A salesperson travelling in an unlisted country
Anomaly / UEBA Compares against the user's or service's baseline Unknown threats, stolen credentials, insiders Many: everything new looks anomalous in the first weeks

The practical rule: start with thresholds and lists, which are cheap and explainable; add signatures for what is known; leave anomaly detection for when you have a baseline and time to tune. An anomaly detection without three months of clean data generates so much noise that it switches itself off. And an underlying warning: rules detect what somebody already imagined; that is why section 12 introduces threat hunting, which looks for what was not imagined.


  1. Nimbus's minimum detection catalogue

These twelve detections cover the seven missed opportunities in section 1 and are built on sources that already exist. It is the central deliverable of the lesson.

id Detection Source Logic Severity Action
D-01 Brute force / password spraying SSO, Nginx > 10 failures from one source in 5 min, or > 5 distinct accounts failed from one IP in 15 min S3 Block the IP (fail2ban) and notify
D-02 Log-in from an unusual country SSO + geoIP Success from a country outside the allowlist, or impossible travel S2 Verify with the person; revoke the session
D-03 Service credential outside working hours Cloud, PostgreSQL Use of nimbus_api or of the consultancy's account (A-19) outside the agreed window S1 Runbook RB-01; suspend access
D-04 Bulk access to the attachments bucket A-02 access logs > 500 objects downloaded by one principal in 10 min S1 Cut off the credential; activate 04-05
D-05 Anomalous export by support C-07 audit A support user accesses > 3 tenants or exports > 1,000 records in 1 h S2 Contact Rubén; freeze the session
D-06 Change to the bucket policy Cloud activity PutBucketPolicy, PutBucketAcl or public-access block disabled S1 Revert and verify who did it
D-07 Creation of a privileged user or role Cloud, SSO, PostgreSQL An account created with administrative permissions S2 Confirm against a change ticket
D-08 Logging or alerting disabled Cloud activity, auditd StopLogging, deletion of the audit configuration S1 Treat as a confirmed compromise
D-09 A source falls silent Collector metadata An active source stops emitting for > 30 min S2 Check whether it is a failure or sabotage
D-10 Deletion of backups or snapshots Cloud activity DeleteBackup, DeleteSnapshot or bulk deletion of versions S1 This is day 20 of 02-06. Crisis
D-11 Use of a revoked or expired token API A request with a jti on the revocation list (03-07) S2 Investigate the source
D-12 Change to the domain's DNS records Registrar, external monitor Alteration of the A, MX, NS or TXT/SPF records of nimbusreservas.example S1 Verify; possible takeover (05-04)

Three observations. First: six are severity S1 and every one of them indicates a compromise in progress, not a suspicion. Second: D-03, D-04 and D-10 would have detected the incident on days 0, 6 and 20 respectively — the first would have cut it off before it started. Third: none of them requires buying anything. They are queries over data Nimbus already generates or that a checkbox switches on.


  1. Writing detections: Sigma, SQL and thresholds

Sigma is the open format for writing detection rules independently of the SIEM: you write once and convert to Wazuh, OpenSearch or Loki. This is D-03, field by field:

title: Third-party access outside the agreed window
id: 8f3c1a90-0c31-4c0e-9c8f-nimbus-d03
status: stable
description: >
  Detects a successful authentication of the consultancy account (A-19) outside
  the window agreed in the contract (Mon-Fri 09:00-18:00 CET). It is exactly
  day 0 of the incident in 02-06, which went unnoticed for 20 days.
references:
  - "POL-02 5.4 Third-party access"
  - "04-04 Third-party risk"
author: Lucia (Nimbus Reservas)
date: 2026/04/12
logsource:
  product: linux          # source product: bounds where the rule applies
  service: sshd           # specific service within the product
detection:
  selection:              # what MUST hold
    event: "authentication_success"
    user|startswith: "svc-consultora"
  working_hours:          # what, if it holds, EXCLUDES the event
    hour_utc|gte: 8
    hour_utc|lt: 17
    day_of_week|lte: 5
  condition: selection and not working_hours
falsepositives:
  - "Authorised emergency intervention with an open ticket"
  - "Daylight saving change applied incorrectly on the agent"
level: critical
tags:
  - attack.initial_access
  - attack.t1078.003        # Valid Accounts: Local Accounts

Five fields do the work. logsource bounds where it applies, and getting it wrong is the number-one cause of rules that never fire. detection defines named blocks that are then combined in condition, and the selection and not exception pattern is the most useful of all: it describes the suspicious and subtracts the legitimate. falsepositives documents what you already know will fire, so that whoever receives the alert at 3 in the morning does not have to work it out alone. And tags with the ATT&CK technique makes it possible to measure coverage (§12).

D-05 is not a log rule: it is a query over the append-only audit table from C-07.

-- D-05: anomalous export by a support user.
-- Runs every 10 minutes over the append-only table (C-07).
WITH activity AS (
    SELECT actor_id,
           COUNT(*)                          AS accesses,
           COUNT(DISTINCT tenant_id)         AS tenants_touched,
           SUM(record_count)                 AS records_read,
           MIN(ts) AS date_from, MAX(ts) AS date_to
    FROM access_audit
    WHERE ts > now() - interval '1 hour'
      AND action IN ('booking.exported', 'customer.listed', 'attachment.downloaded')
    GROUP BY actor_id
)
SELECT a.actor_id, u.name, u.role,
       a.accesses, a.tenants_touched, a.records_read, a.date_from, a.date_to
FROM activity a
JOIN users u ON u.id = a.actor_id
WHERE u.role = 'support'                      -- only the profile under watch
  AND (a.tenants_touched > 3                  -- 1) touches too many customers
       OR a.records_read > 1000)              -- 2) or extracts too much volume
ORDER BY a.records_read DESC;

The query expresses a business idea, not a technical one: a legitimate support agent serves one customer at a time. Touching four tenants in an hour is not illegal under the permission model — the WHERE tenant_id still applies — but it is odd, and what is odd is what gets investigated. It is also the only kind of detection that would have spotted a disgruntled employee, a scenario no signature covers.

And D-04 as a declarative threshold, exactly as it is defined in the alerting engine:

- id: D-04
  name: "Bulk download from the A-02 attachments bucket"
  source: cloud.s3.access_log
  filter: 'operation == "GET_OBJECT" and bucket == "nimbus-adjuntos-prod"'
  group_by: [principal_id]
  threshold: { events: 500, window_min: 10 }
  exceptions:
    - principal_id: "svc-backup"     # the backup process reads everything each night
      only_if_window: "02:00-04:00"
  severity: S1
  runbook: RB-04
  destination: [security-channel, on-call-mobile]

Note the exceptions: without it, the nightly backup would trigger the alert every night and within two weeks nobody would look at the channel. The exception is bounded by a time window, so if svc-backup downloads 40,000 objects at 15:00 the alert fires all the same. An exception without a condition is a permanent hole; with a condition, it is tuning.


  1. Alert quality and fatigue

Alert fatigue is the most common cause of failure in a detection system, and it is not a people problem: it is a design problem. With 200 alerts a day of which 195 are noise, the brain learns — correctly — that the probability of the next one mattering is 2.5 %. That is exactly what happened on day 16 of the incident.

How it is tuned, in order:

  1. Establish a baseline before alerting. Every new detection starts in silent mode for two weeks: you record how many times it would have fired and against what. If it is 90 a day, the rule is not ready.
  2. Document bounded exceptions, never global ones: per service and window, with an owner and a review date (the svc-backup above).
  3. Aggregate instead of repeating. A hundred authentication failures from the same source are one alert with a counter, not a hundred.
  4. Enrich with context in the alert itself: who the user is, what asset it is, whether there is an open change ticket. An alert that forces you to open five tabs to understand it gets postponed.
  5. Review monthly those that never fire. A rule that has been silent for a year is either perfectly tuned or broken, and you need to know which of the two.

An alert and a ticket are not the same thing, and confusing them saturates the process: the alert is the automatic signal and there can be hundreds; the ticket is the commitment that a person investigates it and closes it with a conclusion. Nimbus opens a ticket for every S1 and S2, and aggregates S3 and S4 into a ten-minute daily review. Declared target: no more than five actionable alerts a day, because that is what fits into 440 hours a year.


  1. Detection on the network and on the endpoint

On the network, two free tools with different philosophies: Suricata is a signature-based IDS/IPS that inspects traffic and alerts (or blocks, in IPS mode) on known patterns; Zeek does not alert, it turns traffic into rich logs — connections, DNS, TLS, files transferred — that feed your own detections. For Nimbus, Zeek contributes more: the conn.log and the dns.log would have shown 1.2 TB leaving towards an unknown destination over seven days. Where they are placed physically and what is lost when all traffic is encrypted belongs to 05-04.

On the endpoint and in containers:

Tool What it contributes Where it fits at Nimbus
Wazuh (agent) Log collection, file integrity monitoring (FIM), rootkit detection, configuration assessment The 40 laptops and the servers. It is the main agent
osquery Query the estate as if it were an SQL database Inventory and continuous verification (developed in 05-06)
Falco Runtime detection inside containers: unexpected shell, writes to sensitive paths The containerised API (deployed in 05-07)
Commercial EDR Behaviour-based detection and remote response: isolate the machine A paid alternative; compared in 05-06

The conceptual difference between antivirus and EDR — signature versus behaviour with a response capability — is covered in 05-06; here the consequence for detection is enough: without an agent on the endpoint there is a blind spot across half the staff, who work remotely, outside any network sensor.


  1. Automated response: what yes and what never

SOAR (orchestration and automated response) sounds like an expensive product, but in an SME it starts with three or four well-chosen automations. The criterion for deciding is simple: what happens if the action runs against a false positive?

Safe to automate Why Never automate Why
Block an IP with fail2ban after N failures, with an expiry Reversible in minutes; the cost of a mistake is minimal Shutting down production A false positive causes the outage the attacker was after
Revoke a suspicious session or token The user logs in again; a small inconvenience Deleting files or "cleaning up" It destroys the evidence 04-05 requires you to preserve
Network-isolate a laptop with a critical detection Real containment, reversible by Lucía Restoring backups automatically It can overwrite the state that needs to be analysed
Open a ticket, enrich it and notify No risk and it saves 80 % of the manual work Hacking back Illegal, as well as useless (covered in 06-06)

Golden rule: automate what is reversible and cheap to undo; leave to a person whatever destroys, shuts down or deletes. And every automatic action is recorded in the incident log itself, because the post-mortem in 04-05 needs to know what the machine did and not just what the team did.


  1. Threat hunting and detection metrics

Threat hunting starts from a different idea than alerting: instead of waiting for a rule to fire, you assume the attacker is already inside and go looking. It requires no new tools, only the data from section 5 and a concrete hypothesis.

A complete example at Nimbus. The hypothesis, formulated from ATT&CK: "If an attacker had compromised a consultancy account (T1078.003, valid accounts), they would have used the administrative access to enumerate the database from a source other than the usual one".

  1. Data to query: PostgreSQL connections over the last 90 days, grouped by user, source IP and hour.
  2. What you are looking for: (user, IP) pairs that appear very few times. Not the most frequent ones: the rare is the signal, because the usual is, by definition, the legitimate.
  3. Real result of Lucía's first hunt: three sources with a single connection each. Two were Iván debugging from home — noted and added as an exception. The third was a preproduction container (A-22) connecting to the production database, something nobody knew was happening.
  4. What is done with the finding: it is fixed (separate credentials per environment), turned into a permanent detection (connection to A-01 from a subnet other than 10.30.10.0/24 → S2) and the risk is recorded in 04-01.

That fourth step is what makes hunting pay: every finding becomes an automatic detection, so you never have to look for it by hand again. With two hours a month, Lucía can run one hypothesis a month.

Metric What it measures Realistic target for Nimbus
MTTD (mean time to detect) From the first event to the alert being handled < 24 h in the first year (against the 20 days of 02-06)
MTTR (mean time to respond) From the alert to containment < 4 h for S1, in working hours
ATT&CK coverage Relevant techniques with at least one detection 12-15 techniques of initial access and exfiltration
False positive ratio Alerts dismissed / total alerts < 30 % for S1-S2; if it rises, tune or downgrade the severity
Detections tested Rules verified with a deliberate test 100 % of the S1s, quarterly

The last one is what links back to 04-03 and closes the circle: a detection that has never been tested is in the "planned" state, however well written it is — just like C-08, with its last_verified: null. Testing D-04 consists of downloading 600 test objects from the bucket and checking that the alert reaches the on-call mobile in under five minutes. When it does, that is the moment the RB-01 runbook from 04-05 stops being a document and becomes a living procedure.


Common Mistakes and Tips

  • Collecting everything "just in case". It multiplies the cost, buries the signal and creates a second database of personal information. You collect what answers a specific detection or an obligation.
  • Alerting without a baseline. Every new rule spends two weeks in silent mode. Publishing it straight away is the fast route to nobody looking at the channel.
  • Leaving logs only on the host that generates them. The attacker deletes them in the first minute. Immediate shipping, separate account and write-only.
  • Not synchronising the clocks. Without NTP and UTC, the incident timeline is fiction and cross-source correlation does not work.
  • Confusing an alert with a ticket. Hundreds of alerts and zero tickets means nobody has concluded anything.
  • Global, permanent exceptions. "Exclude the service account" is a hole; "exclude it only between 02:00 and 04:00" is tuning.
  • Automating destructive actions. Shutting down, deleting or restoring without a person turns a false positive into an incident of your own and destroys evidence.
  • Tip: start with six detections, not twelve. D-03, D-04, D-06, D-08, D-10 and D-01 cover the entire incident of 02-06 and can be built in a week's work.
  • Tip: test every S1 before trusting it. A rule without a deliberate test is not an implemented control, and the test takes ten minutes.

Exercises

Exercise 1 — Design the detection that was missing

In the incident in 02-06 there was an exfiltration of 1.2 TB over seven days (days 6-15) without anything firing. Design the detection that would have seen it:

  1. State the source, the logic and a concrete threshold, justifying the number chosen.
  2. Write the rule in Sigma format with at least one exception block.
  3. Explain how you would test it without exfiltrating real data and what evidence you would keep.

Exercise 2 — Diagnose a sick alerting system

Nimbus has had Wazuh for three months and this is last month's summary:

Alerts generated ............... 4,812
  of which S1 ................... 310
  investigated .................. 26
  confirmed as an incident ....... 1
Noisiest rule: "SSH authentication failure"  3,980 alerts (82%)
Rules that never fired: 14 of 22
MTTD of the only real incident: 6 days
Configured retention: 7 days

Identify at least five problems and propose a concrete fix for each one.

Exercise 3 — Formulate and run a hunting hypothesis

Rubén, from support, is leaving the company in a month and has asked for access to reports he did not use before. Marta wants to know whether there is cause for concern, without accusing anyone and without infringing his rights.

  1. Formulate the hunting hypothesis and the associated ATT&CK technique.
  2. State what data you would query and with what query, using the C-07 audit table.
  3. Explain what you would do with three possible outcomes: nothing anomalous, anomalous but explainable activity, and clearly improper activity. Mention what legal and employment limits this investigation has.

Solutions

Exercise 1

(1) Source and logic. The exfiltration of 1.2 TB touched two sources: the access logs for bucket A-02 (attachment downloads) and the outbound volume of the cloud account. The best detection combines both, but the cheapest and most precise is the first. Logic: number of distinct objects downloaded by the same principal in a short window. Why objects and not bytes: the volume in bytes is distorted by a single large legitimate attachment, whereas downloading 500 different files belonging to different customers has no operational explanation.

Threshold: 500 objects in 10 minutes. Justified with data, not intuition: the largest legitimate use observed is the nightly backup (svc-backup, ~40,000 objects between 02:00 and 04:00) and, after that, a support user reaches at most 30 objects in ten minutes. A threshold of 500 leaves a factor of 16 of headroom over the maximum human use and would still have fired on day 6, in the first hours of the exfiltration.

(2) Sigma rule:

title: Bulk download of clinical attachments from bucket A-02
id: 2b7d5e10-9a44-4f11-b3aa-nimbus-d04
description: >
  A principal downloads more than 500 objects in 10 minutes. Covers days 6-15
  of the incident in 02-06, which went completely unnoticed.
logsource:
  product: cloud
  service: s3_access
detection:
  selection:
    operation: "GET_OBJECT"
    bucket: "nimbus-adjuntos-prod"
  nightly_backup:                 # BOUNDED exception, not global
    principal_id: "svc-backup"
    hour_utc|gte: 1
    hour_utc|lt: 3
  condition: selection and not nightly_backup | count(object) by principal_id > 500
  timeframe: 10m
falsepositives:
  - "Bulk migration of a clinic, always with a ticket raised beforehand"
level: critical
tags: [attack.exfiltration, attack.t1530]   # Data from Cloud Storage Object

(3) Testing without real data. A test tenant is created with 600 fictitious attachments and they are downloaded in bulk with a test credential, during working hours and warning the on-call person that it is a drill. The time from the first download to arrival on the mobile is measured. Evidence to keep: the timestamp of the start, that of the alert, a capture of the notification and the updated last_verified on the control's record, which is exactly what 04-03 requires and what C-08 was missing.

Exercise 2

Problem Diagnosis Fix
310 S1s a month and only 26 investigated Severity is inflated: if 92 % of the criticals are never looked at, S1 has stopped meaning "critical". It is alert fatigue in its purest form Reclassify: S1 only for the six compromise-in-progress detections in §7. The rest drop to S2/S3 and are reviewed in the daily round
One rule generates 82 % of the volume "SSH authentication failure" is counting events from the Internet against a port that should not be open. The alert is correct; what is wrong is the exposure Close SSH down to the bastion host (05-04) and, in the meantime, aggregate by IP and apply fail2ban. The volume drops to dozens
14 of 22 rules never fired They are either broken (wrong logsource, misnamed field) or irrelevant. Nobody knows which, and that is the serious part Deliberately test each one; retire the irrelevant ones and fix the broken ones. No untested rule counts as a control
MTTD of 6 days with a SIEM already installed The system collects and does not notify: there is no on-call channel and no priority. It is the day-16 mistake repeated Route S1 to a channel with a mobile notification and S2 to daily review, with acknowledgement of receipt
7-day retention It guarantees every investigation starts blind: when something is detected 6 days late, 24 hours of data remain 90 days hot and 12 months cold and immutable, consistent with C-07
1 confirmed incident out of 4,812 alerts A false positive ratio above 99 %: the system is measuring its own noise Apply the tuning from §9: two weeks of baseline per rule, aggregation and bounded exceptions

Exercise 3

(1) Hypothesis: "If Rubén were preparing his exit with customer data, his access pattern would show unusual breadth (many tenants) and unusual volume (large exports) relative to his own baseline over the last six months". ATT&CK technique: T1213 / T1530, collection from information repositories. Note that the hypothesis is formulated against the behaviour, not against the person, and compares Rubén with himself: that is what makes it defensible.

(2) Query over the append-only table from C-07, comparing two windows:

SELECT date_trunc('week', ts) AS week,
       COUNT(*) AS accesses,
       COUNT(DISTINCT tenant_id) AS tenants,
       SUM(record_count) AS records,
       COUNT(*) FILTER (WHERE action = 'booking.exported') AS exports
FROM access_audit
WHERE actor_id = :ruben AND ts > now() - interval '6 months'
GROUP BY 1 ORDER BY 1;

What you are looking for is a change of trend in the last few weeks against his historical average, not an absolute value. It is also worth comparing against the baseline of the rest of the support team over the same period, because a general spike may be down to a campaign or a migration.

(3) The three outcomes. If there is nothing anomalous, the exercise ends, it is documented that a review took place and it is closed without leaving a trace in the person's file; the negative result is as valuable as the positive one. If there is anomalous but explainable activity — the new reports coincide with a task Marta assigned him — the explanation is documented and, if the access is no longer needed, it is withdrawn: it is an access management finding (04-02), not an incident. If the activity is clearly improper (bulk exports outside working hours of customers he does not serve), the plan from 04-05 is activated as a security incident, evidence is preserved with its chain of custody and the conversation moves to HR and legal counsel before it moves to the technical team.

Limits that must be respected in all three cases. Monitoring must be notified in advance in the acceptable use policy (POL-04) and in the employment information given to the employee; it must be proportionate — reviewing logs of access to customer data is proportionate; reading their personal e-mail or installing covert surveillance is not; and it must be limited to professional activity data. In Spain, the Workers' Statute and data protection law require prior notice and proportionality, and a well-founded investigation can be invalidated if the monitoring had not been communicated. This is developed in 06-03 and 06-06. (Note: before any investigation with disciplinary consequences, prior legal validation is not optional.)


Conclusion

You have built the detective controls Nimbus lacked and that 04-03 flagged as its biggest gap. You know why prevention always fails somewhere and why dwell time is the metric that determines the damage: in the incident in 02-06 there were seven uninstrumented detection opportunities and an eighth — the day-16 cost alert — that existed and failed all the same because it arrived as one e-mail among two hundred. A signal with no recipient, no priority and no procedure is not a detection.

You know Nimbus's eleven telemetry sources and the fact that changes the conversation: nine already exist or cost next to nothing, so what was missing was not budget but collecting them and looking at them. You know what to log and what never to log, how to emit structured JSON logs with request_id, tenant_id and stable event names, why without NTP and UTC correlation is fiction, and how to set retention (90 days hot, 12 months cold) knowing that a 7-day retention guarantees blind investigations. You know how to protect the logs themselves — immediate shipping off the host, immutable storage in a separate account, write-only — and that the absence of logs is itself an alert.

You can distinguish a SIEM from observability, you know the agent → collector → store → detection → alert architecture with normalisation as the step that decides whether the system is any use, and you have chosen self-hosted Wazuh for Nimbus with a declared limitation: nobody will be looking at 3 in the morning. You know when to use signatures, thresholds, lists or anomaly detection, and why to start with the cheap and explainable. You take away the central deliverable: the catalogue of twelve detections with source, logic, severity and action, of which six are S1 and three would have detected the incident on days 0, 6 and 20. You know how to write them in Sigma field by field with the selection and not exception pattern, in SQL over the append-only audit to catch the internal abuse no signature sees, and as a declarative threshold with exceptions bounded by time window. You know how to fight alert fatigue with a silent baseline, aggregation, enrichment and a review of the mute rules, how to tell an alert from a ticket, what Zeek, Suricata, Wazuh, osquery and Falco each contribute, what to automate (the reversible) and what never (whatever shuts down, deletes or destroys evidence). And you know how to hunt threats starting from an ATT&CK hypothesis, with the real finding of a preproduction container connected to the production database and the rule that makes the exercise pay: every finding becomes a permanent detection.

Nimbus now finds its vulnerabilities (05-01) and sees the attacker move (05-02). What remains is the question neither of them answers: would it really hold up? A scanner checks versions and a detection observes what happens, but neither tries to chain three small weaknesses together into a real compromise, which is exactly what an attacker does. In Penetration Testing (05-03) that figure takes the stage, with written authorisation first: what a pentest is and how it differs from a scan, how the scope and the rules of engagement are agreed, what happens in each phase, what an authorised test found on Nimbus's preproduction environment and how each finding was fixed.

Fundamentals of Information Security Course

Module 1: Introduction to Information Security

Module 2: Cybersecurity

Module 3: Cryptography

Module 4: Risk Management and Protection Measures

Module 5: Security Tools and Techniques

Module 6: Best Practices and Regulations

Module 7: Final Project

© Copyright 2026. All rights reserved