Skip to content
GOAHEAD-SA-2026-0006 / CVE-2026-115881

GOAHEAD-SA-2026-0006 / CVE-2026-115881

github.com • October 27, 2026

Important — mbedtls builds are NOT vulnerable. This defect is in GoAhead's OpenSSL TLS backend ( src/openssl.c ). Builds linked against mbedtls ( ME_COM_MBEDTLS=1 ME_COM_OPENSSL=0 ) handle Certificate Revocation Lists via a separate code path ( mbedtls_ssl_conf_ca_chain ) and are not exposed.

Important — mbedtls builds are NOT vulnerable. This defect is in GoAhead's OpenSSL TLS backend ( src/openssl.c ). Builds linked against mbedtls ( ME_COM_MBEDTLS=1 ME_COM_OPENSSL=0 ) handle Certificate Revocation Lists via a separate code path ( mbedtls_ssl_conf_ca_chain ) and are not exposed.

GoAhead's OpenSSL TLS backend loads a configured Certificate Revocation List ( ME_GOAHEAD_SSL_REVOKE ) into the OpenSSL certificate store but does not call X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK) — the flag OpenSSL requires before its check_revocation() function will consult the loaded list. Without the flag, check_revocation() early-returns success on every TLS handshake without opening the CRL, and revoked client certificates are silently accepted as if they were valid.

The X509_STORE_load_locations call returns success and the operator-facing path emits no error; an operator who configured ME_GOAHEAD_SSL_REVOKE and saw no startup warning has every reason to believe revocation is enforced. It is not. A client whose certificate has been revoked and listed in the CRL — for example a stolen, decommissioned, or otherwise compromised device — completes the mutual-TLS handshake exactly as if it had never been revoked, and is granted whatever access the original certificate's identity authorises.

The defect is OpenSSL-version-independent: every supported OpenSSL release requires X509_V_FLAG_CRL_CHECK for leaf-certificate revocation checking, and LibreSSL, BoringSSL, and AWS-LC all that API contract.

Scope — exploitation requires all of the following:

The server is built against the OpenSSL TLS backend ( ME_COM_OPENSSL=1 , the project's preferred and default TLS backend). mbedtls builds are not affected.

Mutual TLS is enabled : ME_GOAHEAD_SSL_VERIFY_PEER=1 .

A CA bundle is configured : ME_GOAHEAD_SSL_AUTHORITY non-empty.

Issuer trust is enabled : ME_GOAHEAD_SSL_VERIFY_ISSUER=1 .

A CRL is configured : ME_GOAHEAD_SSL_REVOKE non-empty, pointing at a CRL file.

The attacker possesses a client certificate signed by a CA in the deployment's bundle and listed as revoked in the deployment's CRL — typically a stolen, decommissioned, or otherwise compromised device certificate the operator already revoked.

The default GoAhead build does not enable mTLS and does not load a CRL. The vulnerable configuration requires deliberate operator opt-in; CRL-configured deployments are the explicit target — operators chose CRL because they have certificates to revoke. Within that population, every deployment is fully vulnerable on every handshake until upgrade.

The defect is a missing call inside the existing CRL load block in sslOpen() :

The block correctly retrieves the certificate store, correctly loads the CRL, and correctly aborts initialisation if the load fails. What it does not do is enable CRL checking on the resulting store.

OpenSSL's check_revocation() is structured as:

Because GoAhead never sets either flag, this function returns success on every handshake without opening the loaded CRL. The administrator-facing CRL load succeeded ( X509_STORE_load_locations returns 1), so the operator receives no error or warning. The verify callback at verifyClientCertificate() is correct — its default: branch rejects unknown error codes including X509_V_ERR_CERT_REVOKED (error 23) — but the defect is upstream of the callback: OpenSSL never reaches the point of producing error 23 because revocation is silently disabled at the library level.

A reporter-supplied verify-callback wire trace for a revoked cert against a vulnerable build shows:

X509_V_ERR_CERT_REVOKED never fires.

The mbedtls backend is structurally different and does not have this defect:

mbedtls accepts the CRL as a positional argument and enforces revocation as part of standard chain verification.

Add X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK) immediately after the successful X509_STORE_load_locations call. The invariant restored is "when ME_GOAHEAD_SSL_REVOKE is configured, OpenSSL's revocation check runs on every handshake and rejects revoked certificates with X509_V_ERR_CERT_REVOKED ". The verify callback's existing default: branch already rejects that error code, so handshake failure is automatic with no callback edits. No public API change, no new configuration option, no new build flag.

We deliberately do not enable X509_V_FLAG_CRL_CHECK_ALL (chain-wide CRL enforcement) in this fix. Chain-wide checking requires CRLs for every issuer in the trust chain, which most deployments do not ship. Restoring leaf-only enforcement matches the operator's documented expectation for ME_GOAHEAD_SSL_REVOKE .

Applied to src/openssl.c in sslOpen() :

Reported by Sébastien Féry.

Extracted Entities