Neutron, our AI engine, scored 97.1% on UC Berkeley's CyberGym benchmark. Learn more

Deep Dive

Deep Dive

GoAhead mTLS Accepts Revoked Client Certificates (CVE-2026-115881)

GoAhead loads the CRL but never checks it, so revoked client certificates pass mTLS; fixed in 6.1.1, shipped to device builders on August 2.

TL;DR

GoAhead, an embedded web server its maker says runs in hundreds of millions of devices, silently ignores the certificate revocation list it is configured to enforce.

  • GoAhead's OpenSSL TLS backend loads the configured CRL into the certificate store but never sets X509_V_FLAG_CRL_CHECK, the flag OpenSSL requires before it consults the list. A revoked client certificate completes the mutual-TLS handshake exactly as if it were valid. CVE-2026-115881, CVSS v3.1 9.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N), rated by EmbedThis; affects GoAhead 3.4.5 through 6.1.0.
  • The same analysis covers two more TLS bugs. On LibreSSL legacy-verifier builds, a client certificate chain longer than the 10-certificate limit is accepted when issuer checking is off (CVE-2026-115884, 7.4).
  • On Mbed TLS builds, a one-byte Diffie-Hellman parameter makes TLS 1.2 DHE_RSA sessions decryptable by a passive observer (5.9, no CVE).
  • The 6.1.1 release also fixes a login bypass where HTTP Basic and form auth accept any password for users with Blowfish-format hashes (CVE-2026-115888, 9.8, only 6.1.0).
  • GoAhead 6.1.1, released to device builders on August 2, 2026, fixes all of these; its eleven advisories were published on October 12. No attacks are reported as of October 27, and whether any device maker has shipped the fix is not known.
  • Device owners get the fix only when their manufacturer ships a firmware update. Until then: identify your GoAhead devices, and on affected builds restrict management access to trusted networks.

Am I affected?

The exposure depends on the TLS backend and the settings compiled into the device, which the owner cannot read from the outside. You are affected by the CRL flaw if all of these hold:

  • The firmware embeds GoAhead 3.4.5 through 6.1.0, built against the OpenSSL backend (ME_COM_OPENSSL=1, the project's default and preferred backend). Builds against Mbed TLS are not affected; they check revocation through a separate code path.
  • Mutual TLS is enabled: ME_GOAHEAD_SSL_VERIFY_PEER=1, a trusted CA bundle (ME_GOAHEAD_SSL_AUTHORITY), issuer verification (ME_GOAHEAD_SSL_VERIFY_ISSUER=1), and a revocation list configured (ME_GOAHEAD_SSL_REVOKE).
  • The operator relies on the CRL to deny revoked devices, which is the entire point of configuring it.
TLS build and flaw Affected versions Required configuration Fix
OpenSSL backend, CRL not enforced (CVE-2026-115881, 9.1) 3.4.5 to 6.1.0 mTLS with issuer verification and a CRL configured 6.1.1; no interim option in the advisory
LibreSSL legacy verifier, chain-depth bypass (CVE-2026-115884, 7.4) 6.1.0 and earlier, on LibreSSL 3.2.x or 3.3.2-3.3.6 VERIFY_PEER=1, VERIFY_ISSUER=0 6.1.1; or enable VERIFY_ISSUER=1 with a trusted CA bundle, or use OpenSSL or LibreSSL 4.x
Mbed TLS, DH parameter truncated (5.9, no CVE) 4.0.2 to 6.1.0 TLS 1.2 DHE_RSA negotiated with a client that accepts the broken parameters 6.1.1; or disable finite-field DHE suites, or use the OpenSSL backend
BF1 password check bypass (CVE-2026-115888, 9.8) only 6.1.0 HTTP Basic or form auth, a known username whose password is stored in BF1 format 6.1.1

To find GoAhead servers in your estate, use the JA4Scan-TLS fingerprints FoxIO published for GoAhead 6.1.x:

TLS backend Library version JA4Scan-TLS fingerprint
OpenSSL 3.6.2 03100h0s00sn_1fe27f6025fb_d06dc4648d2f_sr00ss537461
LibreSSL 3.3.6 03100h0s00s0_9d52559f55bc_c64486648d2f_sr00sc24603c
LibreSSL 4.3.1 03100h0r00s0_9d52559f55bc_c64486206461_sr00scc98ba1

A match points to GoAhead 6.1.x on those library versions; no match does not rule out older GoAhead versions or other builds.

For the Mbed TLS DH flaw, check whether your TLS session logs (JA4S) contain any DHE_RSA cipher suite. FoxIO's example is t120100_009f_1ca028f07214; the command below matches the 25 DHE_RSA suite codes listed in its write-up, in either letter case:

# Find DHE_RSA handshakes against a host in JA4S session records
grep -iE "_(001[456]|003[39]|0045|006[7b]|0088|009[aef]|00be|00c4|c04[45]|c05[23]|c07[cd]|c09[ef]|c0a[23]|ccaa)_" ja4s.log

For device firmware you own or build, the authoritative check is the firmware's build configuration and the GoAhead version in it, not the outside view.

How do I fix it?

Upgrade to GoAhead 6.1.1, which fixes all eleven advisories and leaves no Critical or High finding open, per its release notes. Device makers rebuild their firmware against it and ship the update; an owner receives it only through that firmware update. After upgrading, EmbedThis recommends ending active sessions or restarting the server, and rotating the passwords of BF1 users.

If you cannot rebuild yet, each flaw has a partial interim measure:

  • For the LibreSSL chain-depth flaw, enable issuer verification (ME_GOAHEAD_SSL_VERIFY_ISSUER=1) with a trusted CA bundle, or build against OpenSSL or LibreSSL 4.x.
  • For the Mbed TLS DH flaw, disable finite-field DHE cipher suites so only ECDHE and TLS 1.3 are negotiated, or switch to the OpenSSL backend.
  • For the CRL flaw there is no interim option in the advisory: until you rebuild, treat every formerly trusted, now-revoked device credential as still valid, and compensate at the network layer by restricting which sources can reach the management interface at all.
  • For the BF1 login bypass, an attacker needs only a valid username, so rotate BF1-format passwords to another format or scheme where you can, and watch authentication logs for those users.

What is the root cause?

All three TLS bugs are integration defects: GoAhead calls the TLS library's API in a way that looks configured but is not enforced.

The CRL flaw (CVE-2026-115881). In sslOpen() in src/openssl.c, GoAhead loads the configured revocation list into the OpenSSL certificate store:

if (ME_GOAHEAD_SSL_REVOKE && *ME_GOAHEAD_SSL_REVOKE) {
    store = SSL_CTX_get_cert_store(sslctx);
    if (!X509_STORE_load_locations(store, ME_GOAHEAD_SSL_REVOKE, 0)) {
        error("Cannot load certificate revoke list: %s", ME_GOAHEAD_SSL_REVOKE);
        sslClose();
        return -1;
    }
}

X509_STORE_load_locations() succeeds and startup emits no error, so an operator who configured a CRL has every reason to believe revocation is enforced. But OpenSSL requires the X509_V_FLAG_CRL_CHECK flag before its check_revocation() consults the list, and GoAhead never sets it. Without the flag, OpenSSL's own check returns success before looking at the list:

// OpenSSL 3.6.2 crypto/x509/x509_vfy.c, check_revocation()
int crl_check_enabled = (ctx->param->flags & X509_V_FLAG_CRL_CHECK) != 0;
...
if (!crl_check_enabled && !ocsp_check_enabled)
    return 1;

FoxIO demonstrated the result on GoAhead 6.1.0 with OpenSSL 3.6.2: a revoked, CA-signed client certificate completed the mTLS handshake with issuer verification enabled, and the verification callback reported error 0 throughout the chain.

The Mbed TLS DH flaw. In src/mbedtls.c, GoAhead configures the RFC 3526 2048-bit Diffie-Hellman group, but passes the generator's length as the modulus length:

cuchar dhm_p[] = MBEDTLS_DHM_RFC3526_MODP_2048_P_BIN;  // 256 bytes
cuchar dhm_g[] = MBEDTLS_DHM_RFC3526_MODP_2048_G_BIN; // 1 byte: 0x02
...
if ((rc = mbedtls_ssl_conf_dh_param_bin(conf, dhm_p, sizeof(dhm_g), dhm_g, sizeof(dhm_g))) < 0) {

sizeof(dhm_g) is one byte, so Mbed TLS reads only the first byte of the modulus, 0xFF. The ServerKeyExchange advertises a one-byte DH parameter with the value 255. Only eight shared-secret values are possible, and an observer can try each against recorded traffic; FoxIO reproduced the shared secret after five attempts and decrypted the session out of band. The typo entered in 2018, during migration to the binary-parameter API, and survived the April 2024 TLS refactor. The tested OpenSSL client rejected the parameters with DH_check_params_ex: modulus too small; permissive clients without a minimum-size check accept them.

The LibreSSL chain-depth flaw (CVE-2026-115884). GoAhead sets a limit of 10 certificates with SSL_CTX_set_verify_depth(), and its verification callback assigns the too-long error only when the verifier has not already reported another error:

// src/openssl.c
if (ok && VERIFY_DEPTH < depth) {
    if (error == 0) {
        error = X509_V_ERR_CERT_CHAIN_TOO_LONG;
    }
}

The callback rejects error 22, CERT_CHAIN_TOO_LONG, but tolerates error 20, UNABLE_TO_GET_ISSUER_CERT_LOCALLY, when issuer checking is disabled. The legacy verifier in LibreSSL 3.2.x and 3.3.2-3.3.6 stops building an over-depth chain without recording the depth error, and reports the tolerated error 20 instead. On GoAhead 6.1.0 with LibreSSL 3.3.6, a 17-certificate chain rooted in an attacker-controlled self-signed CA completed the handshake and got HTTP 200; the same source against LibreSSL 4.3.1 reported error 22 and was rejected.

The BF1 login bypass (CVE-2026-115888), a companion fixed in the same release. In websVerifyPasswordFromFile(), the BF1 branch computes the bcrypt result correctly, stores it in a local variable, then overwrites the submitted credential with the stored hash so the function's shared constant-time comparator compares the stored hash against itself. Any password is accepted for a user whose password is stored in the newer Blowfish (BF1:) format; form-based login is the recommended scheme and the default for GoAhead's built-in administration pages.

How is it exploited?

The CRL flaw needs no new tooling: the attacker presents a certificate that was validly issued and is now revoked, for example the credential of a stolen, retired or compromised device, and completes the mTLS handshake as that identity. Nothing about the attack is visible, because the server's own verification reports success.

The Mbed TLS flaw is passive. An observer who recorded a TLS 1.2 DHE_RSA session, where the client accepted the one-byte parameter, tries the eight possible shared-secret values against the recorded handshake and application records, identifies the one that decrypts, and reads the session. No interaction with the server is needed after the capture.

The LibreSSL flaw needs a crafted certificate chain: more than 10 certificates, rooted anywhere, presented to a build with client certificates required and issuer checking disabled. The chain is accepted as an authenticated client.

The BF1 bypass needs only a known or guessed username whose password is in BF1 format, and any password string, against a Basic-auth URL or a form login endpoint.

How does the attack flow?

The CRL flaw, from a revoked device credential to authorized access, in four steps:

flowchart LR A["Attacker presents a
revoked client certificate"] --> B["GoAhead loads the CRL into
the OpenSSL store at startup"] B --> C["X509_V_FLAG_CRL_CHECK
is never set"] C --> D["check_revocation() returns
success without reading the list"] D --> E["mTLS handshake completes
with the revoked identity"] style C fill:#f8d7da

What can an attacker do?

The CRL flaw restores access to whatever a revoked device credential was entitled to: the management pages, APIs or device controls that certificate protected. It matters most to deployments that revoke deliberately, such as after device theft or key compromise, and the gap is invisible on the server, which logs a successful verification.

The Mbed TLS flaw makes recorded sessions readable, including any credentials or commands sent inside them, but only where the client accepted the broken parameters: mainstream clients refuse a one-byte modulus and never complete such a session. Custom and embedded clients without a minimum-size check are the exposed population. ECDHE and TLS 1.3 sessions are unaffected.

The LibreSSL flaw authenticates an attacker as a client without any trusted key, but only on the listed legacy LibreSSL builds with issuer checking off, a configuration that already accepts self-signed clients; deployments requiring a trusted client CA reject it.

The BF1 bypass logs an attacker into the device's administration interface with the privileges of the targeted user. It affects only GoAhead 6.1.0, released November 3, 2025, and only users whose passwords were migrated to the BF1 format.

Why does the fix work?

The CRL and DH fixes are one-line corrections, and each 6.1.1 fix closes its bug completely rather than narrowing it. For the CRL flaw, the store is now flagged right after the list is loaded:

     if (!X509_STORE_load_locations(store, ME_GOAHEAD_SSL_REVOKE, 0)) {
         error("Cannot load certificate revoke list: %s", ME_GOAHEAD_SSL_REVOKE);
         sslClose();
         return -1;
     }
+    /*
+        Enable CRL checking on the leaf certificate. Without this flag OpenSSL loads the CRL but
+        never consults it, silently accepting revoked client certificates.
+     */
+    X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK);

For the DH flaw, the modulus length is now the modulus's own length:

- if ((rc = mbedtls_ssl_conf_dh_param_bin(conf, dhm_p, sizeof(dhm_g), dhm_g, sizeof(dhm_g))) < 0) {
+ if ((rc = mbedtls_ssl_conf_dh_param_bin(conf, dhm_p, sizeof(dhm_p), dhm_g, sizeof(dhm_g))) < 0) {

With the full 2048-bit modulus transmitted, the shared secret is no longer one of eight candidates, and recorded sessions are not brute-forceable. For the LibreSSL chain-depth flaw, 6.1.1 enforces the depth limit unconditionally, before any error code is checked, so an over-depth chain is rejected whatever error the verifier reports; FoxIO verified that the same source against LibreSSL 4.3.1 already reported error 22 and rejected the 17-certificate chain.

For the BF1 bypass, the advisory's patch splits websVerifyPasswordFromFile() into three separate branches, each setting the result once, and no longer overwrites the submitted password with the stored hash:

  • For BF1 users, the bcrypt result is final.
  • For Digest authentication, the client's digest is compared against the stored hash.
  • For legacy MD5 users, the encoded password is compared in constant time.

The release notes add related hardening from the same in-house audit, which resolved eleven advisories and 31 further internal findings: BF1 stored hashes combined with Digest authentication are now rejected explicitly, and session identifiers rotate at every login.

How do I check if I was compromised?

No attack indicators are published, because no attacks are reported. Check for the consequences instead:

  • For the CRL flaw, compare the client certificates that authenticated to each GoAhead server since its deployment against the current CRL contents. Any certificate on the revocation list that still authenticated after its revocation date means the flaw bit you, on the server's side or in your access logs.
  • For the Mbed TLS flaw, search recorded traffic for TLS 1.2 DHE_RSA handshakes carrying a one-byte DH parameter, the ServerKeyExchange bytes 00 01 ff, and treat those sessions as decryptable; FoxIO's JA4S suite list narrows the search.
  • For the BF1 bypass, review authentication logs on 6.1.0 devices for successful logins of BF1-format users from unexpected sources, and treat those credentials as captured.

Timeline

  • 2018: the Mbed TLS DH length typo enters during migration to the binary-parameter API, per FoxIO's code history; it survives the April 2024 TLS refactor.
  • 2025-11-03: GoAhead 6.1.0 is released; the BF1 login bypass is introduced with its new password format.
  • 2026-08-02: GoAhead 6.1.1, a security release closing eleven advisories and 31 further audit findings, ships to device builders.
  • 2026-10-12: EmbedThis publishes the eleven advisories, with CVE ids requested; as of October 27 they were not yet live in the public CVE and NVD databases.
  • 2026-10-26: FoxIO publishes the three-TLS-bugs analysis with proof-of-concept code and JA4Scan fingerprints.
  • 2026-10-27: Alan Coopersmith relays FoxIO's three TLS bugs and their advisories to oss-security; The Hacker News reports no known attacks, consistent with EmbedThis's policy of publishing early if a flaw is being exploited.
  • 2026-10-29: this post.

Device builders received the fix on August 2; the advisories only became public on October 12
Figure 1: GoAhead 6.1.1 reached device builders on August 2, 2026, but the eleven advisories only became public on October 12, and the FoxIO analysis and fingerprints on October 26. Source: EmbedThis GoAhead v6.1.1 release notes and advisories, FoxIO and oss-security, checked October 29, 2026

FAQ

My device has not received a firmware update. What can I do today?

You cannot rebuild someone else's firmware. Identify your GoAhead devices, confirm with the manufacturer whether their build is affected and when they will ship 6.1.1, and until then restrict management access to trusted networks so the flawed TLS checks are not the only barrier. For the Mbed TLS flaw, disabling DHE suites is only possible in your own build.

Are mainstream clients affected by the Diffie-Hellman flaw?

No. FoxIO's tested OpenSSL client refused the one-byte modulus outright, and current Go, Java and mainstream browsers refuse it as well, so the session never completes. Custom and embedded clients without a minimum-size check are the exposed population, and recording their traffic is enough to decrypt it later.

How do I know which TLS backend a device uses?

From the outside, you cannot; the backend and verification settings are compile-time choices. FoxIO's JA4Scan fingerprints identify the backend of internet-facing GoAhead servers it can reach, and your own firmware build configuration is the authoritative answer.

Was anyone attacked?

No source reported attacks as of October 27, 2026. EmbedThis's disclosure policy is to end an embargo and publish early if a flaw is being exploited or disclosed by someone else, so the coordinated October 12 publication is consistent with no known attacks; it does not prove there are none, and the affected population is not established.

Does 6.1.1 fix anything else I should care about?

Yes. The release closes eleven advisories in total, including a Windows CGI command injection through .bat and .cmd targets (9.8, no CVE yet), CGI ISINDEX argument injection (CVE-2026-115880, 8.1), an httpoxy-style HTTP_PROXY injection (CVE-2026-115882, 8.1), two HTTP request-smuggling vectors, a digest-nonce masterSecret disclosure and session fixation, plus 31 internally found defects, each with a regression test.

Find GoAhead devices with Ostorlab

Use Ostorlab Attack Surface Discovery to inventory the internet-facing hosts you own, so the GoAhead devices among them, their management interfaces and their TLS fingerprints are known before you wait on the manufacturer's firmware update.

References

  • FoxIO, Sébastien Féry, "Choose Your Backend: Three TLS Bugs in GoAhead", October 26, 2026: the three root causes with source quotes, the verification callback traces, the ServerKeyExchange bytes, the JA4Scan fingerprints and the DHE_RSA suite list: https://foxio.io/blog/choose-your-backend-three-tls-bugs-in-goahead (checked October 29, 2026)
  • EmbedThis GoAhead security advisories, published October 12, 2026: GHSA-5m75-xq69-c3c5 (OpenSSL CRL not enforced, CVE-2026-115881, 9.1, affecting GoAhead 3.4.5 to 6.1.0): https://github.com/embedthis/goahead/security/advisories/GHSA-5m75-xq69-c3c5 (checked October 29, 2026)
  • GHSA-r392-vqwx-3r4g, mTLS client-certificate chain-depth bypass on LibreSSL legacy-verifier builds, CVE-2026-115884, 7.4: https://github.com/embedthis/goahead/security/advisories/GHSA-r392-vqwx-3r4g (checked October 29, 2026)
  • GHSA-fmvx-2xfx-2gq4, MbedTLS DH parameter truncation, 5.9, no CVE assigned: https://github.com/embedthis/goahead/security/advisories/GHSA-fmvx-2xfx-2gq4 (checked October 29, 2026)
  • GHSA-qj3g-66fp-rhhr, authentication bypass for BF1-hashed users, CVE-2026-115888, 9.8, affecting only GoAhead 6.1.0, fixed in 6.1.1: https://github.com/embedthis/goahead/security/advisories/GHSA-qj3g-66fp-rhhr (checked October 29, 2026)
  • The GoAhead v6.1.1 release notes: released to device builders on August 2, 2026, advisories published October 12, 2026; eleven advisories and 31 further fixes resolved, no Critical or High finding left open: https://github.com/embedthis/goahead/releases/tag/v6.1.1 (checked October 29, 2026)
  • The remaining 6.1.1 advisories, including the Windows CGI command injection (GHSA-584w-ph2x-4gfq, 9.8), the ISINDEX CGI injection (GHSA-c7w3-26wq-mjc3, CVE-2026-115880, 8.1) and the HTTP_PROXY injection (GHSA-w3p3-6x8p-vrjr, CVE-2026-115882, 8.1): https://github.com/embedthis/goahead/security/advisories (checked October 29, 2026)
  • Alan Coopersmith on oss-security, "Three TLS Bugs in GoAhead web server prior to 6.1.1", October 27, 2026 (message dated October 27, 2026 17:17 UTC-7, archived under October 28): https://www.openwall.com/lists/oss-security/2026/10/28/1 (checked October 29, 2026)
  • The Hacker News, "GoAhead TLS Flaws Allow Revoked Client Certificates and Enable Session Decryption", October 27, 2026: the no-known-attacks status, EmbedThis's disclosure policy, the CVE database lag and the post-upgrade recommendations: https://thehackernews.com/2026/10/goahead-tls-flaws-allow-revoked-client.html (checked October 29, 2026)