← Back to Feed CACHED · 2026-10-20 19:47:44 · CACHE_KEY CVE-2026-97205
CVE-2026-97205 · CWE-416 · Disclosed 2026-10-14

NGINX ngx_http_core_module use-after-free via AIO with SSI include virtual processing

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A 17-year-old booby trap that only fires if you flip two switches nobody uses anymore

CVE-2026-97205 is a use-after-free in NGINX's ngx_http_core_module that triggers when both the aio (asynchronous file I/O) directive and SSI (ssi on) are enabled and a page containing <!--#include virtual="..." --> is processed. The freed memory gets reused by the worker, leading to corruption and a crash. If ASLR is disabled — which it isn't on any modern Linux kernel — the corruption is theoretically chainable to code execution. The bug traces back to NGINX 0.8.11 (circa 2009) and affects every version through 1.31.6 / 1.30.5 on the OSS side, plus NGINX Plus R33–R36 and 37.0.0–37.0.4. Fixed in OSS 1.30.6 / 1.31.7 and Plus 37.0.5.1 / 37.1.0.1. NGINX Ingress Controller, Gateway Fabric, Instance Manager, and F5 WAF for NGINX also ship updated builds.

F5 rates this HIGH (CVSS v3.1 8.1) and even CRITICAL under CVSS v4.0 (9.2), but NGINX's own security advisories page rates it Medium — and NGINX is right. The v3.1 vector sets Attack Complexity to High, acknowledging the non-trivial prerequisites, yet the score still lands at 8.1 because CVSS max-impact triplets (C:H/I:H/A:H) inflate the result. In practice, aio is off by default and requires the --with-file-aio compile-time flag; SSI is a legacy content-composition technique from the 1990s that's off by default and virtually unused in modern reverse-proxy or Kubernetes-ingress deployments. The intersection of both being active on a public-facing NGINX instance is vanishingly small. The RCE angle requires ASLR to be disabled, which has been a non-default state on Linux since 2005. The realistic impact is a DoS via worker crash — and NGINX workers auto-restart, limiting the blast.

"Dual non-default configs (AIO+SSI) shrink the blast radius to a sliver of NGINX's fleet."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify an NGINX instance

Attacker scans the internet for NGINX servers. Shodan indexes ~58 million NGINX instances; the Server: nginx header is returned by default. Banner identification is trivial. No tooling beyond nmap or httpx is needed.
Conditions required:
  • Target runs NGINX in the affected version range (0.8.11–1.31.6)
Where this breaks in practice:
  • Version alone is necessary but not sufficient — two config-level prerequisites remain
Detection/coverage: Shodan, Censys, or internal asset inventory identifies NGINX hosts and versions.
STEP 02

Confirm AIO is compiled and enabled

NGINX must have been built with --with-file-aio (not a default build flag) and the operator must have added aio on; or aio threads; to an http, server, or location block. Without this, the aio directive is unknown and NGINX refuses to start. The attacker cannot detect this remotely — it requires config knowledge or blind exploitation.
Conditions required:
  • NGINX compiled with --with-file-aio
  • Config contains aio on or aio threads
Where this breaks in practice:
  • The aio directive is off by default. The compile-time flag is omitted in many distro-packaged builds. Operators must deliberately enable it for performance tuning of static file serving.
  • Attacker has no remote fingerprint for AIO status — this is blind.
Detection/coverage: Defenders can audit with nginx -V 2>&1 | grep file-aio and nginx -T | grep -E '^\s*aio'.
STEP 03

Confirm SSI is enabled and reachable

NGINX must have ssi on; in a server or location block, and the attacker must be able to request a resource that is processed through the SSI filter — typically static .shtml files or HTML with SSI directives. SSI is a legacy content-inclusion mechanism largely replaced by application-level templating. The attacker needs a URL path that triggers SSI processing containing an include virtual directive.
Conditions required:
  • Config contains ssi on in a reachable location
  • A served resource contains <!--#include virtual="..." -->
Where this breaks in practice:
  • SSI is off by default. Modern NGINX deployments (reverse proxy, API gateway, Kubernetes ingress) do not enable SSI because they proxy requests to backends rather than serving SSI-processed static content.
  • The attacker must find or cause a request to an SSI-enabled path with include virtual — not just any path on the server.
Detection/coverage: Config audit: nginx -T | grep -E '^\s*ssi\s+on'. Grep served content for SSI directives.
STEP 04

Trigger the use-after-free

With both AIO and SSI active, the attacker sends a request to the SSI-enabled resource. During processing of the include virtual sub-request, the AIO completion callback frees a buffer that the SSI filter still references. The worker accesses freed memory, causing corruption or a segfault. No special payload crafting beyond a normal HTTP GET is needed — the race condition is inherent in the code path.
Conditions required:
  • Steps 2 and 3 both satisfied on the same server/location block
Where this breaks in practice:
  • The compound probability of both AIO and SSI being active on the same location is extremely low.
  • Worker crashes are logged and auto-restarted, limiting sustained DoS impact.
Detection/coverage: NGINX error logs will show worker process exited on signal 11 (SIGSEGV). Core dumps will reference ngx_http_core_module.
STEP 05

(Theoretical) Achieve RCE if ASLR is disabled

If the target system has ASLR disabled (/proc/sys/kernel/randomize_va_space = 0), the attacker could theoretically leverage the UAF to control a freed heap object and redirect execution. This requires stable heap layout knowledge and is not weaponizable against ASLR-enabled systems. No public exploit achieves RCE.
Conditions required:
  • ASLR disabled on the target system
  • Attacker has heap layout knowledge for the specific NGINX build
Where this breaks in practice:
  • ASLR has been enabled by default on Linux since kernel 2.6.12 (2005). Disabling it requires explicit root action (sysctl kernel.randomize_va_space=0). This condition is virtually non-existent in production.
  • No public weaponized exploit exists for this UAF-to-RCE chain.
Detection/coverage: Check ASLR: cat /proc/sys/kernel/randomize_va_space (should be 2).
03 · Compensating Control

1
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Disable SSI in NGINX configuration — Remove or comment out every ssi on; directive across all server and location blocks, then nginx -t && nginx -s reload. This eliminates the trigger path entirely — no SSI processing means the UAF code path is never reached. If SSI is genuinely needed, migrate to application-layer templating (Jinja2, Handlebars, etc.). This is the highest-leverage control because SSI is the rarer and more replaceable prerequisite. As a MEDIUM-severity finding with no mitigation SLA, go straight to the 365-day remediation window, but this config change can be deployed in minutes and should be prioritized in any audit pass.
2
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Disable AIO or remove the compile-time flag — Set aio off; in all relevant blocks (or remove the directive entirely — off is the default) and reload. If rebuilding NGINX, omit --with-file-aio. This breaks the other half of the prerequisite chain. Note that disabling AIO may have a minor performance impact on high-throughput static file serving workloads, so test under load before deploying fleet-wide. Deploy within the 365-day remediation window per noisgate SLA for MEDIUM findings.
3
MEDIUM 5.3→MEDIUM 4.0
Verify ASLR is enabled on all NGINX hosts — Confirm /proc/sys/kernel/randomize_va_space is set to 2 (full randomization) across your fleet. This blocks the theoretical RCE escalation even if the UAF fires. This should already be true on any modern Linux, but a compliance scan validates it. Use your configuration management (Ansible, Chef, Puppet) to enforce kernel.randomize_va_space = 2 in /etc/sysctl.conf.
4
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Upgrade to NGINX 1.30.6 / 1.31.7 or patched Plus build — The definitive fix. For NGINX Plus, upgrade to 37.0.5.1 or 37.1.0.1. For Ingress Controller, upgrade to 5.7.0; Gateway Fabric to 2.8.0; Instance Manager to 2.24.0. Distro backports are pending for Debian stable and Ubuntu — monitor apt-cache policy nginx for updates. The noisgate remediation SLA for MEDIUM is 365 days; given the low exploitability, schedule this in your next regular patching cycle.
What doesn't work
  • WAF rules — The trigger is a normal HTTP GET to an SSI-enabled path. There is no malicious payload signature to match; the vulnerability is in NGINX's internal SSI+AIO code path, not in request content. A WAF sitting in front of or on NGINX cannot distinguish a triggering request from a legitimate one.
  • Rate limiting — A single well-placed request is sufficient to crash the worker. Rate limiting slows brute-force but does not prevent the initial trigger.
  • NGINX worker process limits (worker_processes, worker_rlimit_nofile) — These control concurrency, not memory safety. They don't prevent the UAF from firing; they only affect how many workers can crash simultaneously.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone observed. Not listed in CISA KEV. No confirmed campaigns. F5, BleepingComputer, and eSecurity Planet all report no active exploitation as of 2026-10-20.
Proof of ConceptA PoC reference was tracked on ~Oct 15, 2026, but no weaponized exploit code was found on GitHub, pocindex.io, ExploitDB, or Nuclei templates. No repository named CVE-2026-97205 exists on GitHub. The researcher Cyberlanc3r is credited with discovery but has not published an exploit.
EPSS0.00484 (0.484%), ~37th percentile as of 2026-10-16. This places it well below the exploitation-likely threshold, consistent with the non-default config requirements.
CISA KEV StatusNot listed. No due date set.
CVSS v3.1 VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — Network-accessible, unauthenticated, no user interaction, but Attack Complexity: High. The max-impact triplet (C/I/A all High) inflates the score; the AC:H correctly signals that exploitation is not straightforward.
CVSS v4.09.2 (Critical) per F5. NGINX project's own advisories rate this Medium — a significant discrepancy that favors the NGINX assessment given the non-default prerequisites.
Affected VersionsNGINX OSS 0.8.11–1.31.6 (stable branch: 1.0.0–1.30.5). NGINX Plus R33–R36, 37.0.0–37.0.4. Also: Ingress Controller <5.7.0, Gateway Fabric <2.8.0, Instance Manager <2.24.0, F5 WAF for NGINX <5.16.0.
Fixed VersionsNGINX OSS 1.30.6 (stable), 1.31.7 (mainline). NGINX Plus 37.0.5.1, 37.1.0.1. Distro backports: Debian unstable has the fix; Ubuntu marked 'needs evaluation'; RHEL status pending.
Shodan / Internet ExposureShodan indexes ~58.5 million NGINX instances globally. However, the vulnerable configuration subset (AIO + SSI both enabled) is undetectable remotely and estimated at well under 1% of the installed base. SSI is a legacy feature; AIO requires explicit compile-time and runtime enablement.
Researcher / ReporterCyberlanc3r (credited by F5). Disclosed as part of a 5-CVE batch on 2026-10-14 alongside CVE-2026-15176, CVE-2026-93479, CVE-2026-95101, and CVE-2026-97629.

Sources.

  1. F5 Security Advisory K000162608
  2. NGINX Official Security Advisories
  3. SecurityOnline — F5 Patches Three Critical NGINX Vulnerabilities
  4. BleepingComputer — F5 Releases Out-of-Band Patches for NGINX
  5. Ionix Threat Center — CVE-2026-97205
  6. Upwind — CVE-2026-97205 Analysis
  7. NGINX ngx_http_core_module Documentation (aio directive)
  8. Debian Security Tracker — nginx
05 · The Call

Final Verdict
↓ DOWNGRADED to MEDIUM (5.3/10)

Why this verdict

  • Dual non-default prerequisites kill reachability. Both aio (off by default, requires compile-time flag) and ssi on (off by default, legacy tech) must be active on the same location block. Each alone is uncommon; the intersection is vanishingly rare. This is the single largest downward pressure — the F5 CVSS v3.1 AC:H acknowledges complexity but cannot model the compound configuration requirement.
  • RCE is theoretical, not practical. The UAF's RCE path requires ASLR to be disabled, which has been the non-default state on Linux since 2005. No public exploit achieves code execution. The realistic ceiling is DoS via worker crash — and workers auto-restart within milliseconds, capping availability impact.
  • NGINX's own project rates this Medium. The vendor (F5) assigns 8.1/9.2, but NGINX's security advisories page — the upstream authority — labels it Medium. The discrepancy arises because F5 scores the theoretical maximum impact; NGINX scores the practical exploitability. We side with NGINX's assessment.
  • EPSS and threat landscape confirm low urgency. EPSS at 0.484% (37th percentile), no KEV listing, no in-the-wild exploitation, no weaponized PoC. The threat signal is flat one week post-disclosure.
  • Role multiplier: network edge. NGINX is canonically a network-edge component (reverse proxy, load balancer, ingress controller) — a high-value role. However, the chain does not succeed in the vast majority of high-value deployments because reverse proxies and ingress controllers do not enable SSI (they forward requests to backends, not process static HTML). The only scenario where AIO+SSI are both active is a legacy static-content server pattern that represents far less than 1% of the NGINX installed base. The blast radius when it does succeed is a worker crash (auto-restarted), not domain takeover, fleet compromise, or supply-chain pivot. The HIGH floor does not apply because neither the ≥1% population threshold nor the catastrophic-outcome threshold is met.

Why not higher?

A HIGH or CRITICAL rating would require either a realistic RCE path or a large exposed population. The RCE path is blocked by ASLR on every modern Linux system, and no exploit bypasses it. The exposed population is gated by two independent non-default configuration switches, both of which are legacy features with negligible adoption in modern NGINX deployment patterns. Even NGINX's upstream project only rates this Medium.

Why not lower?

The bug IS remotely triggerable without authentication, and in the rare deployment that has both AIO and SSI enabled, a single GET request crashes the worker — that's a real, if narrow, DoS vector. The 17-year latency of the bug means some legacy deployments with ossified configs may still carry both directives. A LOW rating would undercount the risk to those long-tail environments. MEDIUM correctly captures 'patch in your normal cycle, but audit your configs now.'

06 · Verification

Crowdsourced verification payload.

Run this script on each NGINX host as root (or a user that can execute nginx -V and nginx -T). Invoke with: sudo bash check_cve_2026_97205.sh. It checks the NGINX version, compile-time AIO flag, and runtime AIO+SSI configuration. Exits 0 for PATCHED, 1 for VULNERABLE, 2 for UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/bin/bash
# check_cve_2026_97205.sh — CVE-2026-97205 detector
# Use-after-free in NGINX ngx_http_core_module (AIO + SSI)
# Run as root on the NGINX host.
# Exit codes: 0=PATCHED  1=VULNERABLE  2=UNKNOWN
set -u

if ! command -v nginx &>/dev/null; then
  echo "UNKNOWN — nginx binary not found"
  exit 2
fi

VER=$(nginx -v 2>&1 | grep -oP 'nginx/\K[\d.]+')
if [ -z "$VER" ]; then
  echo "UNKNOWN — could not parse nginx version"
  exit 2
fi
echo "[*] nginx version: $VER"

# Version comparison helper (returns 0 if $1 >= $2)
ver_gte() { [ "$(printf '%s\n' "$1" "$2" | sort -V | head -1)" = "$2" ]; }

# Determine if version is patched
PATCHED=false
if [[ "$VER" =~ ^1\.30\. ]] && ver_gte "$VER" "1.30.6"; then
  PATCHED=true
elif ver_gte "$VER" "1.31.7"; then
  PATCHED=true
elif ! ver_gte "$VER" "0.8.11"; then
  PATCHED=true  # older than affected range
fi

if $PATCHED; then
  echo "PATCHED — nginx $VER is not in the affected range (0.8.11–1.31.6)"
  exit 0
fi

echo "[!] Version $VER is in affected range"

# Check compile-time AIO support
AIO_BUILT=false
if nginx -V 2>&1 | grep -q -- '--with-file-aio'; then
  AIO_BUILT=true
  echo "[!] Compiled with --with-file-aio"
else
  echo "[+] NOT compiled with --with-file-aio — aio directive unavailable"
fi

# Check runtime config for aio and ssi
CONF=$(nginx -T 2>/dev/null || echo "")
AIO_ON=false
SSI_ON=false

if echo "$CONF" | grep -Pq '^\s*aio\s+(on|threads)'; then
  AIO_ON=true
  echo "[!] 'aio on/threads' is ENABLED in config"
else
  echo "[+] 'aio' is not enabled in config"
fi

if echo "$CONF" | grep -Pq '^\s*ssi\s+on'; then
  SSI_ON=true
  echo "[!] 'ssi on' is ENABLED in config"
else
  echo "[+] 'ssi' is not enabled in config"
fi

# Check ASLR
ASLR=$(cat /proc/sys/kernel/randomize_va_space 2>/dev/null || echo "unknown")
if [ "$ASLR" = "0" ]; then
  echo "[!!!] ASLR is DISABLED — RCE risk elevated"
elif [ "$ASLR" != "unknown" ]; then
  echo "[+] ASLR enabled (value=$ASLR)"
fi

# Verdict
echo ""
if $AIO_BUILT && $AIO_ON && $SSI_ON; then
  echo "VULNERABLE — nginx $VER with AIO and SSI both active. Exploit conditions fully met."
  exit 1
elif $AIO_ON && $SSI_ON; then
  echo "VULNERABLE — nginx $VER: aio and ssi both in config (aio compile flag unconfirmed)."
  exit 1
else
  echo "VULNERABLE (version-only) — nginx $VER is in affected range but exploit requires both aio AND ssi enabled. Current config does NOT meet trigger conditions."
  exit 1
fi