1-Day Analysis: CVE-2024-38477 — NULL Pointer Dereference in Apache mod_proxy
Table of Contents
Executive Summary#
CVE-2024-38477 is a NULL pointer dereference in Apache HTTP Server’s mod_proxy module,
affecting versions 2.4.0 through 2.4.59. The vulnerability resides in
ap_proxy_determine_connection(): under specific edge cases, apr_uri_parse() can return
APR_SUCCESS but leave uri->hostname as NULL. The function then dereferences that
pointer without validation, crashing the worker process.
The fix shipped in 2.4.60 (2024-07-01) — a single null-check. The patch is unambiguous. What is less straightforward is what input actually triggers the vulnerable state in a real deployment.
Despite confirming the crash path via GDB and validating that a NULL hostname reliably
produces a SIGSEGV, controlled exploitation through accessible input vectors —
CVE-2024-38476 chaining, ProxyPass manipulation, and fuzzing — was not achieved within
the available research window. The precise conditions under which apr_uri_parse() returns
success but leaves hostname NULL remain undetermined for standard request paths.
The investigation did not end there. While probing the HTTP/2 proxy backend path in
search of an alternative trigger, an unrelated but immediately reproducible crash was
observed in h2_proxy_util.c. That crash became a separate thread of research,
ultimately leading to a new CVE — documented in
CVE-2025-49630: Reachable Assertion in Apache mod_proxy_http2.
Who should care: operators running Apache HTTP Server with mod_proxy enabled. Upgrade
to 2.4.60 or later. The underlying concern about apr_uri_parse() output validation
extends beyond this single CVE.
Context & Threat Model#
Apache mod_proxy Architecture#
mod_proxy is Apache’s reverse proxy framework. It receives an inbound HTTP request,
determines the upstream backend to contact, and delegates the connection to a
sub-handler (mod_proxy_http, mod_proxy_http2, mod_proxy_fcgi, etc.) based on the
target URL scheme.
[Client]
|
| HTTP request
v
[Apache httpd — mod_proxy]
|
| URL scheme dispatch
+——> mod_proxy_http (http://, https://)
+——> mod_proxy_http2 (h2://, h2c://)
+——> mod_proxy_fcgi (fcgi://)
|
v
ap_proxy_determine_connection()
|
| apr_uri_parse(url, &uri)
v
[uri.hostname] ——> NULL? ——> SIGSEGV (CVE-2024-38477)
The central function ap_proxy_determine_connection() in proxy_util.c is responsible
for parsing the upstream URL and populating the connection parameters (hostname, port,
path). It is called regardless of which sub-handler is active.
Attack Surface#
CVE-2024-38477 is reachable through the HTTP request path with no authentication
required, provided mod_proxy is loaded and a matching ProxyPass or ProxyPassMatch
directive is configured. The attack surface is the inbound request line and headers on
any proxied path.
A minimal vulnerable configuration:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
ProxyPass "/app" "http://backend:8080/"
ProxyPassReverse "/app" "http://backend:8080/"
Trust Boundary#
The relevant boundary is between the raw TCP stream from an untrusted client and the
internal request routing logic in mod_proxy. The vulnerability does not require any
application-level session, cookie, or credential. Processing occurs during the
pre-authentication phase of request handling.
Research Methodology#
Starting Point: Patch Diffing#
The standard entry point for 1-day analysis is comparing the patched and vulnerable
versions to identify what changed. The fix for CVE-2024-38477 was shipped in Apache
HTTP Server 2.4.60 (released 2024-07-01). Comparing the relevant source file between
tags 2.4.59 and 2.4.60 immediately surfaces the change:
https://github.com/apache/httpd/blob/2.4.59/modules/proxy/proxy_util.c#L3076
https://github.com/apache/httpd/blob/2.4.60/modules/proxy/proxy_util.c#L1317
The patch inserts a single null-check on uri->hostname immediately after the
apr_uri_parse() call. The commit message is direct: a missing hostname should be
treated as a bad request, not silently passed to downstream code that assumes it is
non-null.

A secondary commit addresses a regression introduced by the fix in the HTTP/2 proxy
sub-handler (mod_proxy_http2.c):

Lab Setup#
To enable breakpoint-level debugging, Apache 2.4.59 was compiled from source with full debug symbols and all proxy modules enabled:
# APR must be compiled first with matching flags
wget http://archive.apache.org/dist/apr/apr-1.7.3.tar.gz
tar -xzf apr-1.7.3.tar.gz && cd apr-1.7.3
./configure --prefix=/usr/local/apr && make && sudo make install
# Build httpd 2.4.59 with debug and proxy modules
wget https://archive.apache.org/dist/httpd/httpd-2.4.59.tar.gz
tar -xzf httpd-2.4.59.tar.gz && cd httpd-2.4.59
./configure --prefix=/usr/local/apache2 \
--enable-debug --enable-debugger-mode \
--enable-mods-shared=all \
--enable-proxy-connect --enable-proxy-http \
--enable-proxy-http2 --enable-http2
make && sudo make install
The httpd.conf used for CVE-2024-38477 testing:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so
Listen 8080
ProxyPass "/test" "http://proxified:8081/redirect"
ProxyPassReverse "/test" "http://proxified:8081/redirect"
ErrorLog "logs/error_log"
LogLevel debug
Hypothesis and Dead Ends#
The vulnerability requires apr_uri_parse() to produce a NULL hostname. Testing
this required finding an input that reaches ap_proxy_determine_connection() with
a URL that the parser leaves with an unset hostname field.
Several vectors were evaluated:
CVE-2024-38476 chaining — A CGI script was deployed to reflect user-controlled
values via Location headers into the proxy: scheme handler. Multiple attempts to
inject a null byte (%00) or omit the hostname component were unsuccessful:
# Null byte injection attempt
curl -v 'http://127.0.0.1:8080/cgi-bin/test.pl?r=http://%0d%0aLocation:/u%0d%0aContent-Type:proxy:http:%2F%2F\x00%0d%0a%0d%0a'
# Empty hostname attempt
curl -v 'http://127.0.0.1:8080/cgi-bin/test.pl?r=http://%0d%0aLocation:/u%0d%0aContent-Type:proxy:http://%3F%0d%0a%0d%0a'


In all cases, the hostname was empty at best — never NULL. The distinction matters: an empty string still passes the null pointer check that causes the crash.
ProxyPass / ProxyRequests manipulation — Toggling ProxyPreserveHost, disabling
MergeSlashes, enabling AllowEncodedSlashes, and switching between ProxyPass and
ProxyPassMatch produced no exploitable condition. Malformed URIs were caught by
protocol.c or vhost.c before reaching the proxy layer.
Fuzzing — A BooFuzz session mutating the request URI, Host header, and proxy target URL generated no NULL hostname condition.
The conclusion: the specific conditions under which apr_uri_parse() leaves hostname
NULL are tightly coupled to internal URL routing logic not accessible through standard
request vectors in a typical ProxyPass configuration.
An Unexpected Turn#
At this point, the obvious move was to switch the backend scheme from http:// to
h2c:// and repeat the same tests against the HTTP/2 proxy path. If the normalisation
logic differed between mod_proxy_http and mod_proxy_http2, a different parsing
edge case might surface.
It did produce a different result — just not the one being looked for.
A specific malformed request sent while exploring h2c:// backend behaviour caused
a completely different crash, with a completely different log signature: an assertion
failure in h2_proxy_util.c, at a line that had nothing to do with hostname parsing.
The first instinct was that the test environment was misconfigured. After checking, it
was not.
That observation became a separate research thread. The crash was deterministic, independently reproducible, and affected every Apache version with HTTP/2 proxy support. The full analysis is documented in:
CVE-2025-49630: Reachable Assertion in Apache mod_proxy_http2 A CRLF sequence in the request URI causes
ap_assert(req->authority)to fire, crashing all httpd workers on servers proxying toh2://orh2c://backends.
The rest of this article focuses on CVE-2024-38477.
Root Cause Analysis#
CVE-2024-38477 — NULL hostname in ap_proxy_determine_connection()#
The vulnerable function in modules/proxy/proxy_util.c at tag 2.4.59:
int ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
proxy_server_conf *conf,
proxy_worker *worker,
proxy_conn_rec *conn,
apr_uri_t *uri,
char **url, ...)
{
/* Parse the upstream URL into uri components */
if (APR_SUCCESS != apr_uri_parse(p, *url, uri)) {
return ap_proxyerror(r, HTTP_BAD_REQUEST,
apr_pstrcat(p, "URI cannot be parsed: ", *url, NULL));
}
if (!uri->port) {
uri->port = ap_proxy_port_of_scheme(uri->scheme);
}
/* ← uri->hostname used here with no null check */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00944)
"connecting %s to %s:%d", *url, uri->hostname, uri->port);
/* ...
* uri->hostname is subsequently passed to ap_strchr_c(), which calls
* strchr() — dereferencing a NULL pointer → SIGSEGV
*/
The apr_uri_parse() return value is checked: if parsing fails outright, the function
returns an error. However, a parse that succeeds (returns APR_SUCCESS) can still
leave individual uri fields — including hostname — as NULL when the input URL is
incomplete or malformed in a specific way. The success return code only indicates that
the parser did not encounter a fatal format error, not that all fields were populated.
The apr_uri_t structure:
typedef struct {
char *scheme;
char *hostinfo;
char *user;
char *password;
char *hostname; /* ← can be NULL after a "successful" parse */
char *port_str;
char *path;
char *query;
char *fragment;
/* ... */
} apr_uri_t;
A GDB trace of a valid request confirms the structure is fully populated under normal conditions:
(gdb) break ap_proxy_determine_connection
(gdb) continue
Breakpoint 1, ap_proxy_determine_connection(...) at proxy_util.c:3077
(gdb) next /* after apr_uri_parse */
(gdb) print *uri
$42 = {scheme = "h2c", hostinfo = "proxified:8081",
hostname = "proxified", port_str = "8081",
path = "/redirect/a", query = 0x0, ...}
(gdb) print uri->hostname
$48 = 0x79f848f53838 "proxified" /* non-NULL, valid */
Forcing the hostname to NULL via GDB to simulate the vulnerable state confirms the crash path:

Once the NULL hostname propagates to proxy_http_handler, the segmentation fault
follows immediately:

The following diagram traces which functions in the request lifecycle consume
uri->hostname, showing the full crash path from the proxy handler to the fault:

The Fix#
Commit 1d98d4db186e708f059336fb9342d0adb6925e85 inserts a null-check between the
apr_uri_parse() call and all subsequent uses of uri->hostname:
+ if (!uri->hostname) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ apr_pstrcat(p, "URI has no hostname: ", *url, NULL));
+ }
+
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00944)
"connecting %s to %s:%d", *url, uri->hostname, uri->port);
A secondary regression fix (4d3a308014be26e5407113b4c827a1ea2882bf38) was needed
in mod_proxy_http2.c because the new error return path exposed an unhandled case in
the HTTP/2 sub-handler.
The underlying root cause — apr_uri_parse() returning success while leaving
hostname NULL — was not addressed in APR itself. The fix is purely defensive:
validate at the consumer, not the producer.
Proof of Concept#
Direct triggering via external input was not achieved. The crash path was validated by
attaching GDB to a running worker, setting a breakpoint at
ap_proxy_determine_connection(), and manually setting uri->hostname to NULL:
(gdb) attach <worker_pid>
(gdb) break ap_proxy_determine_connection
(gdb) continue
# send any normal proxied request to hit the breakpoint
(gdb) set *(char **)(&uri->hostname) = 0
(gdb) continue
# → Segmentation fault (SIGSEGV)
This confirms the crash mechanism. The missing piece is identifying the input that produces a NULL hostname without debugger intervention.
Exploitability Analysis#
| Property | Value |
|---|---|
| Authentication | None |
| Network access | Remote (if ProxyPass is on an internet-facing interface) |
| Trigger reliability | Not achieved — crash path confirmed via GDB only |
| Impact | DoS (worker crash) |
| CVSS 3.1 | 7.5 HIGH |
The gap between “confirmed crash via GDB” and “triggerable via external request” is
significant. The precise input that causes apr_uri_parse() to succeed but leave
hostname NULL is tied to internal URL normalisation and routing logic that varies by
configuration. Without a reliable trigger, real-world exploitation risk is contingent
on undiscovered request routing edge cases.
Detection & Mitigation#
Upgrade to Apache HTTP Server ≥ 2.4.60. The null-check patch is a one-line addition with no behavioural change for valid requests. There is no configuration workaround that addresses the root cause.
Monitoring: watch for SIGSEGV entries in error_log correlated with proxy requests.
The pattern is the same as CVE-2021-26690 — worker crash signals from the parent:
[core:notice] AH00051: child pid <N> exit signal Segmentation fault (11)
The underlying issue in how apr_uri_parse() handles malformed URIs has not been fixed
in APR itself. A library-level fix that ensures hostname is always populated or an
explicit error is returned would address this class of vulnerability at the source.
Responsible Disclosure Timeline#
| Date | Event |
|---|---|
| 2024-04-01 | CVE-2024-38477 reported to Apache Security Team |
| 2024-07-01 | Apache HTTP Server 2.4.60 released; CVE-2024-38477 patched and disclosed |
| 2025-06-04 | CVE-2025-49630 identified as a side effect of this research |
| 2025-06-09 | CVE-2025-49630 reported to Apache Security Team |
| 2025-07-10 | Apache HTTP Server 2.4.64 released; CVE-2025-49630 patched and disclosed |
Key Takeaways#
A successful parse is not a complete parse.
apr_uri_parse()returnsAPR_SUCCESSeven when it leaves fields likehostnameNULL. Callers must validate individual fields, not just the return code.GDB crash validation is not exploitation. Forcing a NULL pointer in a debugger confirms the crash mechanism but says nothing about reachability from external input. The gap between these two states is where most 1-day analysis ends.
Failed research produces real value. The inability to trigger CVE-2024-38477 directly forced broader exploration of mod_proxy’s HTTP/2 path. That exploration surfaced CVE-2025-49630 — a simpler and immediately exploitable DoS in the same subsystem.
Two CVEs, one codebase, same impact. CVE-2021-26690 (mod_session) and CVE-2024-38477 (mod_proxy) both result in unauthenticated remote DoS against Apache workers. The common thread: missing validation on a pointer that a prior function was trusted to populate.
References#
- CVE-2024-38477 — NVD
- CVE-2024-38477 — cve.org
- Fix commit: proxy_util.c — apache/httpd@1d98d4d
- Regression fix: mod_proxy_http2.c — apache/httpd@4d3a308
- proxy_util.c at 2.4.59 (vulnerable)
- proxy_util.c at 2.4.60 (patched)
- Debian security tracker — CVE-2024-38477
- Ubuntu bug #2072648
- Orange Tsai — Confusion Attacks (CVE-2024-38476)
- Apache HTTP Server Security Advisories
- CVE-2025-49630 — Reachable Assertion in Apache mod_proxy_http2 (this blog)