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.

Patch commit fixing CVE-2024-38477 in proxy_util.c

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

Regression fix commit in 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'

Attempt to inject null byte in hostname via CVE-2024-38476 chain

Attempt to set hostname to empty string

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 to h2:// or h2c:// 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:

GDB: forcing uri->hostname to NULL

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

Segmentation fault with NULL hostname

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:

Apache mod_proxy request lifecycle — hostname NULL dereference path

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#

PropertyValue
AuthenticationNone
Network accessRemote (if ProxyPass is on an internet-facing interface)
Trigger reliabilityNot achieved — crash path confirmed via GDB only
ImpactDoS (worker crash)
CVSS 3.17.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#

DateEvent
2024-04-01CVE-2024-38477 reported to Apache Security Team
2024-07-01Apache HTTP Server 2.4.60 released; CVE-2024-38477 patched and disclosed
2025-06-04CVE-2025-49630 identified as a side effect of this research
2025-06-09CVE-2025-49630 reported to Apache Security Team
2025-07-10Apache 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() returns APR_SUCCESS even when it leaves fields like hostname NULL. 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#