Back to Cheat Sheets

๐Ÿ”” Logging & Alerting FailuresOWASP 2025

OWASP Web Top 10 2025 ยท A09

MEDIUM RISK

๐Ÿ“‹ What Is It?

Logging & Alerting Failures occur when an application does not record security-relevant events with enough detail, does not turn them into timely, actionable alerts, or does not act on the alerts it produces. Unlike other Top 10 entries, this is not a flaw an attacker exploits to break in โ€” it is a detection-and-response gap that lets every other attack proceed unnoticed, for longer, with a worse outcome. The 2025 rename to "Alerting" is deliberate: across a decade of breach retrospectives, the recurring lesson was not that organisations lacked logs โ€” it was that the signal existed and nobody acted on it in time. The deliverable is not a log file; it is a timely response.

A09OWASP 2025 Rank
CWE-778Key: Insufficient Logging
Since '17In Top 10 (was A10:2017)

โš ๏ธ Top Attack Vectors

  • Confirm blindness: probe for a reaction; no lockout/CAPTCHA/slowdown means nothing is watching, so operate at leisure.
  • Low-and-slow attacks: credential stuffing paced under per-account thresholds; enumeration that looks like normal failed logins.
  • Unlogged authz denials: 403s treated as "working as intended" discard the strongest pre-breach indicator.
  • Exfil below the volume radar: paginated data pulls with no per-user baseline alert.
  • Log injection / forging (CWE-117): unescaped newlines write forged log lines and poison SIEM parsers.
  • Killing the pipeline: stop the shipping agent, fill the disk, or delete on-host logs โ€” silence isn't alarmed.

๐Ÿ”ด Attack Flow

1. Probe quietly โ€” watch for ANY reaction
โ†“
2. Confirm blindness โ€” no lockout / alert / slowdown
โ†“
3. Operate at leisure โ€” slow stuffing, privilege abuse, lateral movement
โ†“
4. Exfiltrate under the (absent) volume threshold
โ†“
5. COVER TRACKS: forge/delete logs; breach found 90 days later by a third party

โŒ Vulnerable Code

# Python/Flask โ€” the failed login is never recorded @app.route('/login', methods=['POST']) def login(): user = authenticate(request.form['username'], request.form['password']) if user: return {'status': 'ok', 'token': issue_token(user)} # FAILURE: no username, no IP, no count -> credential stuffing is invisible return {'status': 'invalid'}, 401 # CWE-117 + CWE-532 at once: forgeable line + secrets dumped to disk logger.info("Login for " + username + " body=" + str(request.json))

โœ… Secure Code

# Structured JSON security event: sanitised, contextual, no secrets, off-host import re SENSITIVE = {'password', 'token', 'authorization', 'cookie', 'ssn', 'card'} def clean(v): # neutralise log injection (CWE-117) return re.sub(r'[\r\n\t\x00-\x1f\x7f]', ' ', str(v))[:256] @app.route('/login', methods=['POST']) def login(): username = clean(request.form.get('username', '')) user = authenticate(username, request.form.get('password', '')) outcome = 'success' if user else 'failure' slog.info('authn_login', extra={'event': 'authn_login', 'outcome': outcome, 'actor': username, 'source_ip': request.remote_addr, # no secrets logged 'correlation_id': request.headers.get('X-Correlation-ID')}) return ({'token': issue_token(user)} if user else ({'status': 'invalid'}, 401))

โœ“ Prevention Checklist

  • Log failures & denials, not just the happy path (authn, authz, high-value actions)
  • Structure logs (JSON) with a correlation ID propagated across services
  • Redact secrets/PII (CWE-532); neutralise untrusted data (CWE-117)
  • Centralise off-host in near real time; log to stdout, let a collector ship it
  • Synchronise clocks to UTC (NTP); the server stamps every event
  • Protect logs: append-only/WORM storage, least privilege, integrity checks
  • Detection-as-code: version-controlled correlation rules (stuffing, authz spike, exfil volume)
  • Alert on correlations, deduplicated & severity-routed; on-call + runbook + escalation
  • Alert on the absence of logs โ€” a dead-man's-switch heartbeat

๐Ÿ” Detection & Tools

Sigma rules Fluent Bit ELK / OpenSearch Splunk Prometheus Alertmanager PagerDuty

Core Correlations to Ship:

  • Horizontal stuffing: one IP/ASN vs. many distinct accounts in a window
  • Authz-denial spike per actor โ†’ IDOR / privilege probing
  • Exfil volume: data read vs. per-user baseline; impossible travel
Key Takeaway: This category is best understood as an end-to-end loop โ€” Log โ†’ Structure โ†’ Sanitise โ†’ Centralise โ†’ Protect โ†’ Detect โ†’ Alert โ†’ Respond. A failure at any stage neutralises the whole chain: perfect logs on a host the attacker controls are worthless, and perfect rules alerting into an unowned channel are worthless.
Myth to drop: "We have logging, so we're covered." Producing logs is the easy 20%. Without correlation, a firing alert, and someone on call, you have a write-only archive you'll read after the breach โ€” never during.