This is a bad loading dock, not a broken front door
CVE-2026-28780 is a heap overflow in mod_proxy_ajp that is triggered when Apache HTTP Server parses a malicious reply from an AJP backend. The key qualifier is the one the scary 9.8 summary hides: the attacker does not hit the public web listener directly and win; Apache must already be configured to use mod_proxy_ajp, and it must connect to a malicious or compromised AJP server that sends a crafted response. Apache says affected versions are through 2.4.66, fixed in 2.4.67 released on 2026-05-04.
The severity inflation comes from scoring this like a generic unauthenticated network RCE. Reality is narrower. Apache itself labeled it low on 2026-05-04/05, because the exploit path requires a trusted backend relationship, which usually means internal network reachability, a compromised app server, a rogue service registration, or a misrouted proxy target. That is still worth fixing because frontends often sit in higher-trust zones, but it is a post-initial-access or trust-boundary-pivot flaw, not an internet spray-and-pray emergency.
4 steps from start to impact.
Land behind the reverse proxy
- Apache is configured with
mod_proxy+mod_proxy_ajp - At least one
ajp://backend route is configured - The attacker can make Apache connect to a malicious AJP server
- Many modern deployments use HTTP/HTTPS or
mod_proxy_http, not AJP - AJP backends are usually internal-only and not attacker-controlled
- Backend allowlists, fixed IPs, and network segmentation reduce rogue-backend scenarios
proxy_ajp_module and ajp:// routes reliably; exposure scanners on the public internet are poor predictors because the vulnerable parser is on the frontend's backend connection.Send a crafted AJP reply
ajp_msg_check_header() and writes 4 attacker-controlled bytes past a heap buffer. That is memory corruption, but it is a very small overwrite and occurs in a constrained parsing path, so 'RCE' is possible in theory but not the default expectation in hardened builds.- AJP session established from Apache to attacker-controlled backend
- Backend can speak enough AJP to keep the proxy transaction alive
- A 4-byte overwrite is materially harder to weaponize than a large arbitrary write
- Allocator behavior, build flags, and platform hardening can turn this into a crash instead of code execution
- No credible public exploit chain was identified in reviewed sources
Corrupt the Apache worker
- The crafted overwrite lands in an exploitable heap state
- The target build and runtime make the corruption useful
- Prefork/worker/event model differences change exploit stability
- Modern Linux hardening and service supervision often collapse impact to DoS
- Apache child-process isolation limits one-shot blast radius
httpd/apache2 crashes or restarts. Standard web WAFs will not see the backend-originated malicious AJP response.Pivot from backend to frontend trust
- Successful memory-corruption exploitation or repeated crash condition
- Frontend process has access to sensitive configs, keys, or routing decisions
- Requires a prior foothold or trust abuse on the backend side
- Impact is usually limited to hosts that actually proxy over AJP
- No KEV listing and no reviewed evidence of in-the-wild exploitation
The supporting signals.
| In the wild | No reviewed source shows active exploitation. Not in CISA KEV as of this assessment. |
|---|---|
| Proof-of-concept availability | I found no authoritative public PoC referenced by Apache, NVD, or CISA-adjacent sources. Treat weaponization as *possible but not demonstrated* in the open sources reviewed. |
| EPSS | 0.00026 from the user-supplied intel block, which is consistent with a very low near-term exploitation probability. |
| KEV status | No. Absence from KEV matters here because this is exactly the kind of bug that looks terrifying in CVSS but has a much narrower operational path. |
| CVSS vector reality check | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H assumes a generic network attacker. That overstates reachability because the attacker must control or compromise the AJP backend relationship, not merely send a web request to Apache. |
| Affected versions | Apache says through 2.4.66 are affected; fixed in 2.4.67. |
| Fixed versions and backports | Upstream fix is 2.4.67. Distros are backporting independently: Ubuntu shows fixes for 26.04/24.04/22.04, Debian tracks the issue, and SUSE shows released updates for several maintained package lines. |
| Exposure population | Internet scanning is a weak signal for this CVE. The vulnerable parser lives on Apache's backend AJP client path, so risk is concentrated in environments that still use mod_proxy_ajp and ajp:// routes. |
| Disclosure timeline | Published 2026-05-05; Apache 2.4.67 released 2026-05-04; NVD/CISA ADP attached the 9.8 vector on 2026-05-06. |
| Researchers | Credited by Apache to Andrew Lacambra, Elhanan Haenel, Tianshuo Han, and Tristan Madani. |
noisgate verdict.
The decisive factor is the attacker-position requirement: this bug needs Apache to trust and connect to a malicious or already-compromised AJP backend. That sharply narrows exposed population and makes this a backend-trust pivot problem, not a broad unauthenticated internet edge compromise problem.
Why this verdict
- Requires backend control: the exploit path starts only if Apache connects to a malicious AJP server, which implies internal position, prior compromise, or trust abuse.
- Reachable population is small: only hosts using
mod_proxy_ajpwithajp://backends are really in scope; plenty of Apache fleets never enable AJP at all. - Small overwrite, uncertain weaponization: the issue is a 4-byte attacker-controlled heap overwrite, which is serious but materially harder to convert into dependable RCE than the raw CVSS suggests.
Why not higher?
It is not higher because the public HTTP surface is not the real attack surface. The attacker must already be in a position to impersonate or compromise an AJP backend, which is compounding friction and a major reduction in exposed population. No KEV listing, no reviewed in-the-wild reports, and no authoritative public exploit chain push it further down.
Why not lower?
It is not lower because memory corruption inside a reverse proxy is never free. If you do run AJP, a compromised backend can attack the frontend process and potentially cross a trust boundary toward keys, routing logic, or adjacent applications. That keeps it out of pure backlog-hygiene territory.
What to do — in priority order.
- Disable
mod_proxy_ajpwhere unused — If the business does not require AJP, remove the module and eliminate the vulnerable code path entirely. For a MEDIUM verdict there is noisgate mitigation SLA: no mitigation SLA — go straight to the 365-day remediation window, but unused AJP should still be removed during normal config hygiene. - Pin backend targets tightly — Restrict Apache to known AJP backend IPs/hostnames and prevent dynamic or user-influenced backend selection. This reduces the chance that service-discovery poisoning, DNS drift, or a rogue internal host can become the malicious AJP peer before patching; deploy as part of normal hardening while you work the remediation window.
- Block rogue AJP traffic — Use host firewall rules, security groups, or ACLs so Apache can reach only sanctioned AJP backends on
8009or your chosen port. This is the most practical containment if you must keep AJP and cannot remediate immediately. - Prefer HTTP/HTTPS proxying for new deployments — Where architecture allows, move new reverse-proxy integrations off AJP and onto
mod_proxy_http/HTTPS. That removes this parser family from future exposure and simplifies inspection and policy enforcement. - Monitor Apache child crashes — Alert on repeated
httpd/apache2worker exits, core dumps, and backend-specific request failures on AJP-connected frontends. It will not stop exploitation, but it gives you the best chance to catch unsuccessful or DoS-style attempts while patches are pending.
- A public-edge WAF does not solve this, because the malicious payload comes back from the backend AJP server to Apache.
- MFA is irrelevant; there is no user authentication step in the exploit chain.
- Tomcat AJP
secrethelps authenticate intended peers, but it does not make a compromised legitimate backend harmless.
Crowdsourced verification payload.
Run this on the target Apache host with local shell access. Invoke it as bash check_cve_2026_28780.sh or bash check_cve_2026_28780.sh /etc/apache2 if configs live in a nonstandard path; no root is required for version/module checks, but reading all config files may need elevated privileges.
#!/usr/bin/env bash
# check_cve_2026_28780.sh
# Conservative detector for CVE-2026-28780 on Apache HTTP Server.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -u
CFG_ROOT="${1:-}"
STATUS="UNKNOWN"
REASON=""
find_httpd_bin() {
local c
for c in apache2ctl apachectl httpd; do
if command -v "$c" >/dev/null 2>&1; then
command -v "$c"
return 0
fi
done
return 1
}
extract_version() {
local bin="$1"
local out ver
out="$($bin -v 2>/dev/null || true)"
ver="$(printf '%s\n' "$out" | sed -n 's/^Server version: Apache\/\([0-9][0-9.]*\).*$/\1/p' | head -n1)"
if [ -z "$ver" ]; then
ver="$(printf '%s\n' "$out" | sed -n 's/^Server version:[[:space:]]*Apache\/\([0-9][0-9.]*\).*$/\1/p' | head -n1)"
fi
printf '%s' "$ver"
}
ver_ge() {
# returns 0 if $1 >= $2
[ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ]
}
ver_lt() {
# returns 0 if $1 < $2
! ver_ge "$1" "$2"
}
guess_cfg_roots() {
if [ -n "$CFG_ROOT" ] && [ -d "$CFG_ROOT" ]; then
printf '%s\n' "$CFG_ROOT"
fi
for d in /etc/apache2 /etc/httpd /usr/local/apache2/conf; do
[ -d "$d" ] && printf '%s\n' "$d"
done
}
module_loaded=0
ajp_configured=0
httpd_bin="$(find_httpd_bin || true)"
version=""
if [ -n "$httpd_bin" ]; then
version="$(extract_version "$httpd_bin")"
fi
# Check loaded modules if possible
if [ -n "$httpd_bin" ]; then
if "$httpd_bin" -M 2>/dev/null | grep -Eq 'proxy_ajp_module|mod_proxy_ajp'; then
module_loaded=1
fi
fi
# Fall back to config grep for module/config presence
while IFS= read -r root; do
if grep -R -Eqs 'LoadModule[[:space:]]+proxy_ajp_module|proxy_ajp_module' "$root" 2>/dev/null; then
module_loaded=1
fi
if grep -R -Eqs 'ajp://|ProxyPass[[:space:]].*ajp:|BalancerMember[[:space:]].*ajp:' "$root" 2>/dev/null; then
ajp_configured=1
fi
done < <(guess_cfg_roots)
if [ -z "$version" ]; then
echo "UNKNOWN - Could not determine Apache httpd version"
exit 2
fi
# Upstream fixed version
if ver_ge "$version" "2.4.67"; then
echo "PATCHED - Apache version $version is >= 2.4.67"
exit 0
fi
# Vulnerable code present in old version, but practical exposure depends on AJP use
if ver_lt "$version" "2.4.67"; then
if [ "$module_loaded" -eq 1 ] && [ "$ajp_configured" -eq 1 ]; then
echo "VULNERABLE - Apache version $version is < 2.4.67 and mod_proxy_ajp with ajp:// backend config was found"
exit 1
fi
if [ "$module_loaded" -eq 1 ] || [ "$ajp_configured" -eq 1 ]; then
echo "UNKNOWN - Apache version $version is < 2.4.67 and partial AJP indicators were found; verify distro backports and active config"
exit 2
fi
echo "UNKNOWN - Apache version $version is < 2.4.67 but no active AJP module/config was detected; package may still contain vulnerable code"
exit 2
fi
echo "UNKNOWN - Unable to classify"
exit 2
If you remember one thing.
mod_proxy_ajp and hosts that do not. For AJP users, validate the backend trust path, restrict AJP egress to approved backends, and plan the upgrade to 2.4.67 or vendor backports within the noisgate remediation SLA of ≤365 days for this MEDIUM finding; there is noisgate mitigation SLA — go straight to the 365-day remediation window. If a host does not use AJP, document the low practical exposure and let normal Apache update cycles pick it up.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.