Detecting NGINX CVE Exploitation via Logs and Runtime Signatures

Detecting NGINX CVE Exploitation via Logs and Runtime Signatures

Problem

NGINX CVEs span different attack surfaces: memory corruption in media-processing modules (CVE-2024-7347, ngx_http_mp4_module), protocol-level crashes in the QUIC module (CVE-2024-24989, CVE-2024-24990), mTLS bypass via session resumption (CVE-2025-23419), and configuration injection via ingress annotations (CVE-2025-1097, CVE-2025-1098, CVE-2025-1974). Each leaves a different signature in logs and runtime behaviour.

The detection challenge is that NGINX does not have built-in intrusion detection. It logs HTTP requests and errors, but these logs are not automatically correlated against exploitation patterns. A heap overflow attempt against the mp4 module may produce an NGINX worker crash (visible in error logs) or may succeed silently — and the success is only visible in subsequent attacker activity.

What detection is possible. Some CVEs produce observable log signatures before or during exploitation:

  • Memory corruption attempts often generate malformed request patterns — unusual file paths, oversized headers, or protocol violations that appear in NGINX access logs as 400/500 errors with distinctive request characteristics
  • Worker process crashes (SIGSEGV, SIGABRT) appear in NGINX error logs and systemd journal as “worker process exited on signal 11” — repeated crashes against specific endpoints are a strong signal
  • QUIC module attacks may produce a flood of malformed QUIC packets logged at DEBUG level or cause UDP socket errors
  • Annotation injection CVEs require an kubectl apply action — Kubernetes audit logs record this
  • Post-exploitation activity (shell spawning from an NGINX worker, outbound connections to C2) is detectable via Falco and network monitoring

The critical window. Between CVE disclosure and patch deployment (often 7–30 days), detection is the only active defence. These rules need to be in place before an exploitation attempt, not after.

Target systems: any production NGINX deployment; Kubernetes clusters with ingress-nginx; any environment with a SIEM or log aggregation pipeline; teams using Falco for runtime security.


Threat Model

Adversary 1 — Automated CVE scanning. Automated scanners probe for known NGINX vulnerabilities within hours of CVE publication. They send crafted requests targeting specific modules and endpoints. These requests have distinctive patterns in NGINX access logs even when the attempt fails.

Adversary 2 — Targeted mp4 module exploitation. An attacker sends specially crafted MP4 files to an endpoint that uses ngx_http_mp4_module. The crafted file triggers the heap buffer overflow (CVE-2024-7347). Observable: unusual MP4 requests to non-video endpoints, or repeated worker crashes against video endpoints.

Adversary 3 — Post-exploitation lateral movement. After achieving RCE in an NGINX worker via a memory corruption CVE, the attacker executes shell commands. Falco detects unexpected process spawning from the NGINX worker process — execve of /bin/sh, curl, or other binaries from a process with NGINX’s user context.

Adversary 4 — ingress-nginx annotation injection. An attacker with CREATE/UPDATE Ingress permission deploys a malicious Ingress object with a configuration-snippet annotation designed to exfiltrate secrets. Kubernetes audit logs record the Ingress creation with the annotation.


Configuration / Implementation

Step 1 — Configure NGINX structured logging for SIEM ingestion

# /etc/nginx/nginx.conf
http {
    # Structured JSON logging for SIEM/log aggregation
    log_format json_security escape=json
        '{'
        '"time_iso8601":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"request_method":"$request_method",'
        '"request_uri":"$request_uri",'
        '"server_protocol":"$server_protocol",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"http_referer":"$http_referer",'
        '"http_user_agent":"$http_user_agent",'
        '"ssl_protocol":"$ssl_protocol",'
        '"ssl_cipher":"$ssl_cipher",'
        '"request_length":$request_length,'
        '"request_time":$request_time,'
        '"upstream_addr":"$upstream_addr",'
        '"upstream_status":"$upstream_status",'
        '"upstream_response_time":"$upstream_response_time"'
        '}';
    
    access_log /var/log/nginx/access.json json_security;
    error_log /var/log/nginx/error.log warn;
    
    # Log 4xx and 5xx separately for easier alerting
    map $status $loggable_error {
        ~^[45] 1;
        default 0;
    }
    access_log /var/log/nginx/errors.json json_security if=$loggable_error;
}

Step 2 — SIEM detection rules for CVE exploitation patterns

# Sigma rules for NGINX CVE exploitation detection
# Compatible with: Splunk, Elastic SIEM, Chronicle, QRadar

title: NGINX mp4 Module Exploit Attempt (CVE-2024-7347)
id: a7d9f3e2-1234-4abc-8765-fedcba987654
status: experimental
description: Detects potential exploitation of CVE-2024-7347 — malformed MP4 requests to NGINX
logsource:
  product: nginx
  service: access
detection:
  selection:
    # Requests to mp4-serving paths with unusual characteristics
    request_uri|contains:
      - '.mp4'
      - '.m4v'
      - '.m4a'
    status:
      - 500
      - 502
      - 503
  filter_legitimate:
    # Normal video requests don't generate 5xx
    status: 200
  timeframe: 5m
  condition: selection and not filter_legitimate | count() by remote_addr > 5
level: high
tags:
  - attack.initial_access
  - cve.2024-7347

---
title: NGINX Worker Process Crash (Possible Memory Corruption Exploit)
id: b8e0f4d3-5678-4def-9876-abcdef012345
status: experimental
description: Detects repeated NGINX worker crashes indicating possible memory corruption exploitation
logsource:
  product: nginx
  service: error
detection:
  selection:
    message|contains:
      - "worker process exited on signal 11"
      - "worker process exited on signal 6"
      - "SIGSEGV"
      - "SIGABRT"
  timeframe: 10m
  condition: selection | count() > 3
level: critical
tags:
  - attack.execution
  - attack.t1203

---
title: NGINX Ingress Annotation Injection (CVE-2025-1097 Class)
id: c9f1e5d4-9abc-4ghi-0123-bcdef6789012
status: stable
description: Detects creation or modification of Kubernetes Ingress objects with snippet annotations
logsource:
  product: kubernetes
  service: audit
detection:
  selection:
    verb:
      - create
      - update
      - patch
    objectRef.resource: ingresses
    requestObject.metadata.annotations|contains:
      - "nginx.ingress.kubernetes.io/configuration-snippet"
      - "nginx.ingress.kubernetes.io/server-snippet"
      - "nginx.ingress.kubernetes.io/stream-snippet"
  level: critical
tags:
  - attack.privilege_escalation
  - cve.2025-1097

Step 3 — Falco rules for NGINX post-exploitation detection

# /etc/falco/rules.d/nginx-exploit-detection.yaml

- macro: nginx_worker_process
  condition: >
    proc.name = "nginx" and
    (proc.cmdline contains "worker process" or 
     proc.pname = "nginx")

# Alert: NGINX worker spawning a shell (post-RCE indicator)
- rule: NGINX Worker Spawns Shell
  desc: An NGINX worker process executed a shell — strong indicator of RCE exploitation
  condition: >
    spawned_process and
    nginx_worker_process and
    proc.name in (shell_binaries)
  output: >
    NGINX worker spawned shell process
    (user=%user.name pid=%proc.pid pcmdline=%proc.pcmdline
     cmdline=%proc.cmdline container=%container.name
     image=%container.image.repository)
  priority: CRITICAL
  tags: [nginx, exploitation, rce, shell]

# Alert: NGINX worker making outbound network connection to unexpected destinations
- rule: NGINX Worker Unexpected Outbound Connection
  desc: NGINX worker process making an outbound connection to an unexpected destination
  condition: >
    outbound and
    nginx_worker_process and
    not fd.sport in (80, 443, 8080, 8443) and
    not fd.rip_name in (known_internal_ips)
  output: >
    NGINX worker making unexpected outbound connection
    (user=%user.name pid=%proc.pid
     connection=%fd.name cmdline=%proc.cmdline)
  priority: HIGH
  tags: [nginx, exploitation, c2]

# Alert: NGINX worker reading sensitive files
- rule: NGINX Worker Reads Sensitive Files
  desc: NGINX worker process reading files outside its expected paths
  condition: >
    open_read and
    nginx_worker_process and
    not fd.name startswith "/etc/nginx" and
    not fd.name startswith "/var/www" and
    not fd.name startswith "/var/log/nginx" and
    not fd.name startswith "/etc/ssl/certs" and
    fd.name in (sensitive_file_names)
  output: >
    NGINX worker reading sensitive file
    (user=%user.name pid=%proc.pid file=%fd.name cmdline=%proc.cmdline)
  priority: HIGH
  tags: [nginx, exploitation, credential_access]

# Alert: NGINX worker executes curl/wget (data exfiltration indicator)
- rule: NGINX Worker Executes Network Tool
  desc: NGINX worker executing curl, wget, or other network tools — possible exfiltration
  condition: >
    spawned_process and
    nginx_worker_process and
    proc.name in (network_tool_binaries)
  output: >
    NGINX worker executed network tool
    (user=%user.name tool=%proc.name pid=%proc.pid
     cmdline=%proc.cmdline container=%container.name)
  priority: CRITICAL
  tags: [nginx, exploitation, exfiltration]

- list: network_tool_binaries
  items: [curl, wget, nc, ncat, python, python3, perl, ruby, php]

Step 4 — Suricata network signatures for NGINX CVE probing

# /etc/suricata/rules/nginx-cve.rules

# Detect scanning for NGINX QUIC vulnerabilities (CVE-2024-24989/24990)
# QUIC Initial packets with malformed payloads targeting NGINX
alert udp any any -> $HTTP_SERVERS 443 (
    msg:"ET EXPLOIT Possible NGINX QUIC Module CVE-2024-24989/24990 Probe";
    content:"|ff 00 00 1d|";  # QUIC version negotiation pattern from PoC
    depth: 4;
    threshold: type both, track by_src, count 10, seconds 60;
    classtype:attempted-admin;
    sid:9000001;
    rev:1;
    metadata:affected_product NGINX, cve CVE-2024-24989;
)

# Detect malformed MP4 requests targeting CVE-2024-7347
alert http any any -> $HTTP_SERVERS any (
    msg:"ET EXPLOIT Possible NGINX MP4 Module CVE-2024-7347 Probe";
    flow:established,to_server;
    http.method; content:"GET";
    http.uri; content:".mp4";
    http.uri; pcre:"/\?start=\d{10,}/";  # Oversized start parameter
    classtype:attempted-admin;
    sid:9000002;
    rev:1;
    metadata:affected_product NGINX, cve CVE-2024-7347;
)

# Detect repeated 400s from same source against NGINX (automated CVE scanning)
# This rule uses threshold to catch scanning behaviour, not single requests
alert http any any -> $HTTP_SERVERS any (
    msg:"ET SCAN Automated NGINX CVE Scanner Activity";
    flow:established,to_server;
    http.stat_code; content:"400";
    threshold: type both, track by_src, count 20, seconds 60;
    classtype:web-application-attack;
    sid:9000003;
    rev:1;
)

Step 5 — Set up log-based alerting for NGINX worker crashes

#!/bin/bash
# monitor-nginx-crashes.sh
# Run as a systemd service or in a monitoring loop

CRASH_THRESHOLD=3
WINDOW_SECONDS=600  # 10 minutes
ALERT_WEBHOOK=${ALERT_WEBHOOK:-""}

check_nginx_crashes() {
    # Count worker crashes in the last 10 minutes via journalctl
    CRASH_COUNT=$(journalctl -u nginx --since "10 minutes ago" --no-pager 2>/dev/null | \
        grep -c "worker process.*signal\|SIGSEGV\|SIGABRT")
    
    if [[ "$CRASH_COUNT" -ge "$CRASH_THRESHOLD" ]]; then
        echo "CRITICAL: $CRASH_COUNT NGINX worker crashes in last 10 minutes"
        
        # Get the last crash details
        LAST_CRASH=$(journalctl -u nginx --since "10 minutes ago" --no-pager 2>/dev/null | \
            grep "worker process.*signal\|SIGSEGV" | tail -1)
        
        echo "Last crash: $LAST_CRASH"
        
        # Send alert if webhook configured
        if [[ -n "$ALERT_WEBHOOK" ]]; then
            curl -s -X POST "$ALERT_WEBHOOK" \
                -H "Content-Type: application/json" \
                -d "{\"text\": \"NGINX WORKER CRASH ALERT: $CRASH_COUNT crashes in 10m. Last: $LAST_CRASH\"}"
        fi
        
        return 1
    fi
    
    echo "OK: $CRASH_COUNT NGINX worker crashes in last 10 minutes (threshold: $CRASH_THRESHOLD)"
    return 0
}

check_nginx_crashes
# /etc/systemd/system/nginx-crash-monitor.service
[Unit]
Description=NGINX Worker Crash Monitor
After=nginx.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/monitor-nginx-crashes.sh
Environment=ALERT_WEBHOOK=https://hooks.slack.com/...

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/nginx-crash-monitor.timer
[Unit]
Description=Run NGINX crash monitor every 5 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=5min

[Install]
WantedBy=timers.target

Expected Behaviour

Signal Without detection With detection
Automated CVE scanner probing NGINX Not detected; may succeed silently Suricata alert fires within 60 seconds of scanning start
3+ NGINX worker crashes in 10 minutes Observed via systemctl status nginx; no alert Crash monitor fires alert; incident response begins
NGINX worker process spawns /bin/sh Post-RCE shell active; undetected Falco fires CRITICAL alert within 1 second of execve
Ingress annotation injection applied to cluster New CVE-2025-1097 vector active Kubernetes audit log alert fires on Ingress creation with snippet annotation
NGINX worker reads /etc/shadow Undetected credential access Falco fires HIGH alert on unexpected sensitive file access

Trade-offs

Aspect Benefit Cost Mitigation
Falco on every NGINX host Real-time post-exploitation detection Falco adds ~5% CPU overhead; requires kernel module or eBPF Use eBPF driver for lower overhead; evaluate on representative load before fleet deployment
Sigma rules in SIEM Centralized detection without per-host agents Detection latency = log shipping delay (typically 30–120s) Use for correlation rules; use Falco for real-time host alerts
Crash monitor via journalctl Catches memory corruption exploitation Crashes can be from bugs, not only exploits — false positives possible Alert on crash bursts, not single crashes; correlate with incoming request patterns
Suricata for QUIC scanning Network-level detection before request reaches NGINX QUIC is encrypted; some patterns only visible in metadata Use TLS inspection or rely on Falco for post-exploitation detection

Failure Modes

Failure Symptom Detection Recovery
NGINX error log rotated before crash analysis Crash pattern not captured in historical logs Log rotation removes evidence Configure log rotation with 30-day retention; ship logs to SIEM before rotation
Falco kernel module not loaded after kernel update No Falco alerts; exploitation undetected Falco service fails silently Monitor Falco service health; alert on Falco service exit; use eBPF driver (survives kernel updates better)
SIEM Sigma rule matches too broadly Alert fatigue; real exploits missed in noise High alert volume; security team ignores alerts Tune threshold and filter criteria; add suppression for known scanners
Kubernetes audit log not capturing Ingress mutations Annotation injection not logged Ingress deployed with snippet annotation; not caught Enable Kubernetes audit policy for ingresses resource at RequestResponse level