GoAhead TLS Vulnerabilities Explained: CVE-2026-115881 and CVE-2026-115884

The CyberSec Guru

GoAhead TLS Vulnerabilities Explained

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

Embedded firmware teams usually pick a well-known TLS library such as OpenSSL, LibreSSL or Mbed TLS and assume its security controls come along for free. Research on the GoAhead embedded web server shows how wrong that can be. The library can be fine while the application fails to turn its protections on, or turns them on with the wrong arguments.

GoAhead is a compact HTTP server built for products with limited resources and long service lives. You find it in network appliances, industrial equipment, firewalls and plenty of other connected devices. In late October 2026, FoxIO published research led by security researcher Sébastien Féry describing three GoAhead vulnerabilities. Together they show that TLS security depends on how the application integrates the library, whatever the library’s reputation.

Why the GoAhead TLS bugs matter for embedded security

None of the three findings is a broken TLS protocol or a flaw in the underlying cryptography. In each case GoAhead misreads or misapplies the configuration of its TLS backend. A revocation list can sit in memory and never be consulted. A certificate-chain limit can be configured and still fail to reject an overlong chain. A nominally strong Diffie-Hellman group can shrink to a single byte because of a wrong length argument.

Embedded devices often stay in service for years on firmware that may never get an update, so these gaps last. Nobody knows exactly how many devices are affected. The total number of GoAhead installations is not the same as the number of vulnerable configurations, but the footprint across the embedded ecosystem is large.

The sections below cover CVE-2026-115881, CVE-2026-115884 and the Mbed TLS parameter truncation, including the X.509 validation, error-code mapping and finite-field math behind each.

CVE-2026-115881: GoAhead OpenSSL CRL check bypass (CVSS 9.1)

CVE-2026-115881 is the most severe of the three, with a vendor-assigned CVSS score of 9.1. It affects GoAhead 3.4.5 through 6.1.0 when the application uses an OpenSSL-compatible TLS backend with specific mutual TLS (mTLS) and certificate-revocation settings enabled.

How mTLS and certificate revocation work

In a correct mTLS deployment, the client must prove it holds the private key for a certificate the server accepts. That lets an organization limit access to devices with approved credentials. Certificates stop being trustworthy at some point, though. A device gets stolen, an employee leaves, or a private key leaks. Revocation lets the issuer say a credential should no longer be accepted, usually through a Certificate Revocation List (CRL): a signed structure listing the serial numbers of invalidated certificates.

The missing X509_V_FLAG_CRL_CHECK flag

GoAhead’s vulnerable initialization path loads the CRL named in the ME_GOAHEAD_SSL_REVOKE directive into OpenSSL’s certificate store with X509_STORE_load_locations(). That makes the revocation data available, but loading it does not enforce it. The code never calls X509_STORE_set_flags() with X509_V_FLAG_CRL_CHECK. Without that flag, OpenSSL verifies the client certificate’s signature, checks its expiry date and confirms its chain of trust, but never looks at the loaded CRL.

c

/* Vulnerable GoAhead OpenSSL Initialization Pattern */
X509_STORE *store = SSL_CTX_get_cert_store(ctx);
/* The CRL is successfully loaded into the store's memory */
if (!X509_STORE_load_locations(store, revoke_file, NULL)) {
/* Handle configuration failure */
}
/* CRITICAL OMISSION: The flag to enforce the CRL check is never set */
/* X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK); */

The result is that a compromised or decommissioned device can keep access to protected management endpoints indefinitely.

📬 Stay Ahead of Cyber Threats

Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.

Subscribe to the Newsletter →

Issuer verification is not revocation checking

Some people assume that enabling issuer verification is enough to reject an untrusted client certificate. It answers a different question. Issuer verification asks whether the certificate chains to an acceptable authority under the configured trust policy. Revocation checking asks whether a certificate that would otherwise pass has since been invalidated. A revoked certificate still has a valid signature and still chains to a trusted CA. Only the CRL’s status information gets it rejected.

The fix in GoAhead 6.1.1

GoAhead 6.1.1 adds the missing flag and enables CRL checking for the leaf certificate. It does not use X509_V_FLAG_CRL_CHECK_ALL, which would demand CRLs for every intermediate issuer in the chain and would likely break legitimate deployments that lack that revocation data.

CVE-2026-115884: LibreSSL certificate chain depth bypass (CVSS 7.4)

CVE-2026-115884 has a CVSS score of 7.4. It comes from an interaction between GoAhead’s application-level policy and the legacy X.509 verification path in older LibreSSL builds: the 3.2.x series and versions 3.3.2 through 3.3.6. This one is not a missing flag. It comes from how GoAhead interprets the error codes the TLS library returns while building the certificate chain.

Why chain-depth limits exist

A chain-depth limit bounds the verification process and guards against denial-of-service attacks and logic flaws tied to very long certificate chains. GoAhead sets a limit of ten and intends to reject any client chain that goes past it. Enforcement depends on the error codes the backend produces.

When the depth check becomes the only check

The bug is exploitable when mutual TLS is on but issuer trust checking is deliberately off. That setup is common in machine-to-machine environments where the server expects a certificate without insisting on a specific root of trust. There, the depth limit is the main restriction on what the client presents.

GoAhead enforces the limit inside its certificate-verification callback. According to the vendor advisory, the depth guard only substitutes X509_V_ERR_CERT_CHAIN_TOO_LONG when the underlying verifier has not already recorded an earlier error.

Error 20 instead of Error 22

When an attacker presents a chain that exceeds the limit, the affected LibreSSL verifier hits an issuer-related failure before it can flag the chain as too long. It returns X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (Error 20) instead of X509_V_ERR_CERT_CHAIN_TOO_LONG (Error 22). GoAhead’s callback tolerates Error 20 when issuer verification is disabled, so it never rejects the chain on depth alone and the TLS handshake succeeds.

c

/* Conceptual representation of the vulnerable callback logic */
int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
int depth = X509_STORE_CTX_get_error_depth(ctx);
int err = X509_STORE_CTX_get_error(ctx);
if (depth > configured_max_depth) {
/* GoAhead expects Error 22 to trigger the rejection */
if (err == X509_V_ERR_CERT_CHAIN_TOO_LONG) {
return 0; /* Reject the connection */
}
/* If LibreSSL returns Error 20, the check falls through */
/* and the connection may be accepted if issuer verification is off */
}
return preverify_ok;
}

Proof of concept and patch

FoxIO authenticated to a GoAhead 6.1.0 server linked with LibreSSL 3.3.6 using a seventeen-certificate chain against a configured limit of ten, and reached a protected resource that returned HTTP 200 OK. The same test against LibreSSL 4.3.1 returned Error 22 and was rejected, so the flaw depends on the backend.

An application should never rely on the underlying library to report every policy violation through one particular error code. GoAhead 6.1.1 makes the depth check unconditional, so an over-limit chain is rejected before the callback reaches its error-specific acceptance logic, whatever error state the verifier is in.

GOAHEAD-SA-2026-0005: Mbed TLS one-byte prime breaks forward secrecy (CVSS 5.9)

The third finding has nothing to do with certificate validation. It hits the math behind the TLS 1.2 key exchange in GoAhead builds that use the Mbed TLS backend. Tracked as GOAHEAD-SA-2026-0005 with a CVSS score of 5.9, it affects GoAhead 4.0.2 through 6.1.0. A small typo in a buffer length argument is enough to undermine finite-field Diffie-Hellman ephemeral (FFDHE) key exchange.

How DHE_RSA key exchange is supposed to work

In a TLS 1.2 DHE_RSA handshake, the server and client agree on public parameters: a large prime modulus and a generator. They then derive a shared secret over a public channel without exposing their private exponents. The security rests on the difficulty of the discrete logarithm problem, which only holds if the prime is large enough.

The sizeof() mistake in mbedtls_ssl_conf_dh_param_bin()

GoAhead’s source declares a 2048-bit prime, which takes 256 bytes, and a one-byte generator with the value 0x02. When it passes these to mbedtls_ssl_conf_dh_param_bin(), the code applies sizeof() to the generator array for both the prime length and the generator length. The generator array is one byte long, so Mbed TLS receives a prime length of one byte instead of 256. It treats only the first byte of the prime buffer as the whole modulus.

c

/* The Vulnerable Mbed TLS Configuration */
static const unsigned char dhm_p[256] = { /* 2048-bit prime data */ };
static const unsigned char dhm_g[] = { 0x02 };
/* The prime length is incorrectly set to sizeof(dhm_g), which is 1 */
mbedtls_ssl_conf_dh_param_bin(
&conf,
dhm_p,
sizeof(dhm_g), /* Evaluates to 1, truncating the 256-byte prime */
dhm_g,
sizeof(dhm_g)
);
/* The Corrected Configuration */
mbedtls_ssl_conf_dh_param_bin(
&conf,
dhm_p,
sizeof(dhm_p), /* Correctly evaluates to 256 */
dhm_g,
sizeof(dhm_g)
);

What a 255 modulus means for TLS session security

The truncated value is typically 0xFF, or 255. That is far smaller than a 2048-bit number, and it is also composite, which breaks the assumptions Diffie-Hellman needs. Forward secrecy is gone for the affected DHE sessions. Anyone who captures the handshake can enumerate the tiny set of possible shared-secret values and derive the TLS session keys.

That does not make every session on an Mbed TLS build decryptable. Mainstream modern clients reject undersized parameters and negotiate a different cipher suite. Permissive embedded clients, custom telemetry collectors and legacy implementations may accept them, and in those cases an attacker can silently decrypt recorded traffic. GoAhead 6.1.1 fixes the bug by passing the size of the prime buffer, which restores the intended 2048-bit strength.

How to check if your devices are affected by the GoAhead vulnerabilities

The three defects hit different primitives under different conditions, but they share one cause: the security property the application means to provide differs from what it enforces at runtime. A CRL in the certificate store does not prove revoked certificates get rejected. A configured maximum depth does not prove every overlong chain is refused. A 2048-bit prime in the source does not prove the TLS library receives a 2048-bit prime.

Determining exposure is hard. A device’s marketing name or HTTPS banner rarely reveals the GoAhead version, the linked TLS library or the compile-time options in the firmware. External TLS fingerprinting can narrow the search. FoxIO’s JA4Scan-TLS fingerprints distinguish tested GoAhead builds linked against different TLS implementations, which helps you decide which devices to investigate first.

How to fix and mitigate the GoAhead TLS vulnerabilities

Upgrade affected GoAhead builds to version 6.1.1 or later, which fixes all three findings. Manufacturers and operators both have work to do here.

Firmware developers should follow the upgrade with regression tests that check the security properties end to end, not just that the build compiles:

  • OpenSSL builds: test with revoked client certificates to confirm the CRL flag is active.
  • LibreSSL builds: send over-depth chains to confirm they are always rejected.
  • Mbed TLS builds: capture packets to confirm the full 256-byte prime modulus is negotiated during the handshake.

Operators who cannot patch right away should keep administrative interfaces off untrusted networks. They should also review any certificates revoked in the past, since the server may never have enforced its CRL policy.

A TLS implementation has to refuse the connections its policy says must never be trusted. Test that first, before you test whether it can set up an encrypted session.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

News

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.