Like a mailroom accidentally stuffing part of one letter into the next envelope, but only on a locked internal corridor
CVE-2026-103119 is a buffer under-read (CWE-127) in Apache Tomcat 11's AJP (Apache JServ Protocol) connector. When a large non-blocking write occurs, the response body can be incorrectly split: part of one response leaks into the next request on the same persistent AJP connection. The second request typically fails outright, and there is a chance that sensitive data from the first response is exposed to the second requester. Affected versions are Tomcat 11.0.0-M25 through 11.0.26 only — Tomcat 9.x and 10.x branches are *not* affected. The fix landed in 11.0.27 (commit f13748ba).
The vendor CVSS of 9.1 CRITICAL (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H) dramatically overstates the real-world risk. Apache's own qualitative severity rating is Important, not Critical — and they are right. The score assumes unauthenticated network access to the AJP port, but since the Ghostcat fix in 2020 (CVE-2020-1938), Tomcat's AJP connector defaults to localhost-only binding with secretRequired=true. In Tomcat 11, the AJP connector is commented out entirely in the default server.xml. An administrator must actively reconfigure Tomcat to expose AJP externally for this to be remotely exploitable. The actual impact — probabilistic leakage of adjacent response data on a single connection, not arbitrary memory read — is also narrower than C:H suggests. With no PoC, no KEV listing, an EPSS of 0.006, and zero in-the-wild exploitation, this is a medium-priority scheduled patch for the ~31% of Tomcat installations running version 11.
4 steps from start to impact.
Reach the AJP port (8009/tcp)
localhost (127.0.0.1 / ::1) with secretRequired=true, this requires either local access to the Tomcat host, adjacent network access where the admin explicitly re-bound AJP to 0.0.0.0, or compromise of the front-end reverse proxy (httpd/nginx) that proxies AJP. Direct internet exposure of AJP is extremely rare post-Ghostcat.- Network path to AJP port 8009
- AJP connector must be enabled (commented out by default in Tomcat 11)
- If secretRequired=true (default), attacker needs the configured shared secret
- AJP binds to localhost by default since Tomcat 8.5.51 / 9.0.31 (March 2020)
- AJP connector is commented out in default Tomcat 11 server.xml
- Enterprise firewall rules typically block port 8009 from untrusted zones
- Secret requirement adds an authentication barrier before any AJP communication
Maintain persistent AJP connection with sequential requests
- Authenticated AJP session (secret validated)
- Persistent connection maintained across multiple requests
- Target application must produce large response bodies using non-blocking I/O (async servlets)
- Most AJP connections are owned by the reverse proxy, not direct attacker connections
- The non-blocking write path is only triggered for async servlet responses or large streamed payloads
- Traditional synchronous servlets do not exercise the vulnerable code path
Trigger buffer under-read via large non-blocking write
- Application performs large non-blocking writes (async servlets, streaming responses)
- Response size must cross a specific buffer boundary threshold that triggers the under-read
- Not all Tomcat applications use non-blocking I/O — many enterprise apps use synchronous servlets
- The exact buffer boundary condition depends on runtime state and response payload size
- Attacker has no control over which data leaks or when the condition triggers
Harvest leaked response data from subsequent request
- Buffer under-read successfully triggered in step 3
- Leaked data contains something exploitable (session tokens, PII, credentials)
- Attacker is the party making the follow-up request, not the reverse proxy
- In a standard reverse-proxy architecture, the proxy owns the AJP connection — the proxy's next request gets the leaked data, not an external attacker
- Leakage is partial and non-deterministic — attacker cannot select target data or target victim
- Failed second request generates HTTP 5xx errors that are logged and often alerted
<Connector> element in server.xml if your architecture uses HTTP/HTTPS proxying (mod_proxy_http, nginx upstream) instead of AJP. This is already the default in Tomcat 11. Verify across all Tomcat 11 hosts within the 365-day noisgate remediation SLA for MEDIUM severity. If AJP is genuinely unused, this single change eliminates 100% of the attack surface.address="127.0.0.1" and secret="<strong-random-value>" are explicitly set on the AJP Connector in server.xml. These are the defaults in Tomcat 11, but confirm they have not been overridden. With localhost binding, only processes on the same host can reach the AJP port, reducing the attack to a local-only post-compromise scenario. Apply within the 365-day remediation window.- WAF rules — WAFs inspect HTTP traffic, not the binary AJP protocol on port 8009. No WAF product can detect or block exploitation of this vulnerability.
- TLS/mTLS on AJP — AJP does not natively support TLS encryption. Wrapping it in a TLS tunnel (e.g., stunnel) adds transport encryption but does not fix the buffer under-read; the response data still leaks within the encrypted channel between Tomcat and the proxy.
- Switching NIO/NIO2/APR connector type — The I/O connector implementation (BIO, NIO, NIO2, APR) does not change the AJP protocol handler code path where the buffer under-read occurs. The fix is in the AJP request processor, not the transport I/O layer.
The supporting signals.
| In-the-Wild Exploitation | None observed. Not listed on CISA KEV. No reports of active exploitation campaigns as of 2026-10-22. Apache's advisory contains no exploitation warning. |
|---|---|
| Proof of Concept | None public. No PoC found on pocindex.io, GitHub (no repos named CVE-2026-103119), ExploitDB, or Nuclei templates. The SecureWithUmer/CVE-2026-PoCs collection does not include this CVE. |
| EPSS Score | 0.00609 (~0.6%) — bottom quartile for network-accessible vulnerabilities. Well below any actionable exploitation probability threshold. |
| KEV Status | Not listed on CISA Known Exploited Vulnerabilities catalog as of 2026-10-22. No due date assigned. |
| CVSS Vector Analysis | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H = 9.1. AV:N is misleading: AJP defaults to localhost binding since Ghostcat (2020). AC:L is overstated: the trigger requires non-blocking I/O and specific buffer boundary conditions. A realistic adjusted vector: AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:L (~5.3). |
| Affected Versions | Apache Tomcat 11.0.0-M25 through 11.0.26 only. Tomcat 9.x and 10.x are not affected. This limits the exposed population to ~31% of the Tomcat ecosystem (per OpenLogic Java Trends 2026). |
| Fixed Versions | Tomcat 11.0.27 (commit f13748ba). No backports needed for 9.x or 10.x since those branches are unaffected. Distro packages (RHEL, Ubuntu, Debian) shipping Tomcat 11 should receive the fix in their next update cycle. |
| Attack Surface / Exposure | AJP port 8009 is rarely internet-exposed in modern deployments. Post-Ghostcat defaults changed to localhost-only. Shodan historically showed ~800K-1M Tomcat instances with AJP exposed, but Tomcat 11 AJP exposure is a tiny fraction given the connector is commented out by default. |
| Disclosure Timeline | Reported to Apache security team on 2026-09-16. Public advisory released 2026-10-20. Fix available same day in Tomcat 11.0.27. ~34-day coordinated disclosure. |
| Apache's Own Severity | Important — notably one tier below the CVSS-derived Critical. Apache's qualitative assessment correctly reflects the AJP-specific attack surface and default-safe configuration that the CVSS vector ignores. |
Sources.
Why this verdict
- AJP localhost-only default eliminates the remote attack vector for >95% of deployments. Since Tomcat 9.0.31 (March 2020), the AJP connector binds to localhost with
secretRequired=true. Tomcat 11 inherits this and goes further by commenting out the AJP connector entirely in the defaultserver.xml. The CVSS AV:N assumption is factually wrong for the vast majority of real installs. This alone drops the realistic attack vector to AV:L or AV:A, shaving ~2 points off the base score. - Non-blocking write trigger adds attack complexity the CVSS ignores. The vulnerability only fires when the application performs a large non-blocking write through the AJP connector using async servlets or streaming responses. Traditional synchronous servlets — still the majority of enterprise Java workloads — are unaffected. This is a runtime-conditional trigger, not a simple packet-to-impact exploit. AC:H is more appropriate than AC:L, reducing the score by ~1 point.
- Tomcat 11 only — limited installed base. Only ~31% of Tomcat deployments run version 11 (OpenLogic 2026 data). Versions 9.x and 10.x are unaffected. The reachable population is roughly one-third of the Tomcat ecosystem, which itself is ~0.18% of the web server market.
- No PoC, no exploitation, negligible EPSS. Zero proof-of-concept code exists. EPSS of 0.006 places this in the bottom quartile. No CISA KEV listing. No GreyNoise scanning tags. No threat intel reports. The threat is entirely theoretical as of October 2026.
- Role multiplier: Tomcat is a typical-role application server, not a canonically high-value component. Tomcat is not a domain controller, hypervisor, identity provider, PAM tool, backup system, or network edge appliance. In its most common high-value deployment (application tier hosting banking/healthcare/PII data), the AJP response leakage *could* expose sensitive per-request data — but the chain requires AJP to be non-default-configured AND reachable AND the app to use async I/O AND the attacker to be the party on the AJP connection (not the reverse proxy). The blast radius is per-connection partial data leakage (host-scope), not domain/fleet/supply-chain compromise. No high-value role scenario produces domain takeover or fleet-scale impact from this flaw alone. The MEDIUM floor holds — no floor override is triggered.
Why not higher?
For this to merit HIGH, the attack path would need to be reliably exploitable over the network without significant preconditions. It is not: AJP defaults to localhost, the connector is off by default in Tomcat 11, a shared secret is required, and the trigger demands non-blocking I/O conditions that most Java applications do not use. Even in a worst-case deployment where AJP is externally exposed with async servlets, the leakage is non-deterministic and per-connection — it does not scale to mass exploitation or fleet compromise. There are zero indicators (no PoC, no KEV, no in-the-wild activity) that would override the friction analysis upward.
Why not lower?
The theoretical impact is real: if an organization has explicitly exposed AJP externally and runs async servlets producing large responses, an attacker on the AJP connection could harvest partial response data including session tokens, PII, or API credentials. The confidentiality impact in that rare scenario is genuine. Apache rated this Important (not Moderate), acknowledging the data leakage potential beyond simple availability disruption. LOW would undercount the risk for the minority of deployments with non-default AJP exposure and would not adequately communicate the need to verify AJP configuration across the fleet.
Crowdsourced verification payload.
Run on each Tomcat host as any user with read access to $CATALINA_HOME. Invoke: CATALINA_HOME=/opt/tomcat bash check_cve2026103119.sh. No root privileges required. Outputs VULNERABLE (exit 1), PATCHED (exit 0), or UNKNOWN (exit 2). Also reports AJP connector status and bind address if detectable.
#!/bin/bash
# CVE-2026-103119 Vulnerability Checker
# Detects vulnerable Apache Tomcat 11 versions and AJP connector status
# Usage: CATALINA_HOME=/opt/tomcat bash check_cve2026103119.sh
set -euo pipefail
CH="${CATALINA_HOME:-/opt/tomcat}"
if [ -x "$CH/bin/version.sh" ]; then
VOUT=$("$CH/bin/version.sh" 2>/dev/null)
elif command -v catalina.sh >/dev/null 2>&1; then
VOUT=$(catalina.sh version 2>/dev/null)
else
echo "[!] Tomcat not found. Set CATALINA_HOME."
echo "UNKNOWN"; exit 2
fi
TV=$(echo "$VOUT" | grep -oP 'Apache Tomcat/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [ -z "$TV" ]; then
echo "[!] Could not parse Tomcat version."
echo "UNKNOWN"; exit 2
fi
echo "[*] Detected Tomcat version: $TV"
IFS='.' read -r MAJ MIN PAT <<< "$TV"
if [ "$MAJ" -ne 11 ]; then
echo "[+] Tomcat $TV is not version 11. Not affected by CVE-2026-103119."
echo "PATCHED"; exit 0
fi
if [ "$MIN" -eq 0 ] && [ "$PAT" -ge 27 ]; then
echo "[+] Tomcat $TV >= 11.0.27. Patched."
echo "PATCHED"; exit 0
fi
echo "[!] Tomcat $TV is in the vulnerable range (11.0.0-M25 through 11.0.26)."
SX="$CH/conf/server.xml"
if [ -f "$SX" ]; then
if grep -qE '^[[:space:]]*<Connector[^>]*protocol[^>]*AJP' "$SX" 2>/dev/null; then
echo "[!] AJP connector is ENABLED in server.xml."
BIND=$(grep -A5 'protocol.*AJP' "$SX" | grep -oP 'address="\K[^"]+' | head -1)
if [ -n "$BIND" ]; then
echo "[*] AJP bind address: $BIND"
if echo "$BIND" | grep -qE '^(127\.|::1|localhost)'; then
echo "[*] AJP bound to loopback. Remote exploitation unlikely."
else
echo "[!!] AJP bound to $BIND -- may be externally reachable!"
fi
else
echo "[*] No explicit bind address found. Check defaults."
fi
else
echo "[*] AJP connector not active in server.xml. Code vulnerable but AJP not reachable."
fi
else
echo "[*] Cannot read $SX to verify AJP connector status."
fi
echo "VULNERABLE"; exit 1