CWE-299
AllowedImproper Check for Certificate Revocation
Abstraction: Base · Status: Draft
The product does not check or incorrectly checks the revocation status of a certificate, which may cause it to use a certificate that has been compromised.
35 vulnerabilities reference this CWE, most recent first.
GHSA-G7HG-VRCF-MVMR
Vulnerability from github – Published: 2026-07-22 21:46 – Updated: 2026-07-22 21:46Summary
OcspServerCertificateValidator flags an out-of-date OCSP response but does not stop processing it, so an expired GOOD response is still reported as VALID, letting an on-path attacker replay a stale GOOD response to bypass revocation of a since-revoked certificate.
Details
In io.netty.handler.ssl.ocsp.OcspServerCertificateValidator#userEventTriggered the freshness check has no return, so execution falls through and a VALID OcspValidationEvent is still fired:
if (!(current.after(response.getThisUpdate()) &&
current.before(response.getNextUpdate()))) {
ctx.fireExceptionCaught(new IllegalStateException("OCSP Response is out-of-date"));
}
Nonce validation is optional and off by default, so freshness is the only replay defense — and it is not enforced. Additionally getNextUpdate() may be null, making current.before(null) throw NullPointerException.
https://datatracker.ietf.org/doc/html/rfc6960#section-3.2
5. The time at which the status being indicated is known to be
correct (thisUpdate) is sufficiently recent;
6. When available, the time at or before which newer information will
be available about the status of the certificate (nextUpdate) is
greater than the current time.
PoC
Add the test below to io.netty.handler.ssl.ocsp.OcspServerCertificateValidatorTest
@Test
void staleOcspResponseIsRejected() throws Exception {
X509Bundle caRoot = new CertificateBuilder()
.algorithm(CertificateBuilder.Algorithm.rsa2048)
.subject("CN=TrustedRootCA")
.setIsCertificateAuthority(true)
.buildSelfSigned();
GeneralName ocspName = new GeneralName(GeneralName.uniformResourceIdentifier, "http://localhost/");
AuthorityInformationAccess aia = new AuthorityInformationAccess(
new AccessDescription(AccessDescription.id_ad_ocsp, ocspName));
X509Bundle targetCert = new CertificateBuilder()
.algorithm(CertificateBuilder.Algorithm.rsa2048)
.subject("CN=TargetServer")
.addExtensionOctetString("1.3.6.1.5.5.7.1.1", false, aia.getEncoded())
.buildIssuedBy(caRoot);
Date past = new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7));
CertificateID certId = new CertificateID(
new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),
new JcaX509CertificateHolder(caRoot.getCertificate()),
targetCert.getCertificate().getSerialNumber());
BasicOCSPRespBuilder respBuilder = new BasicOCSPRespBuilder(
new RespID(new JcaX509CertificateHolder(caRoot.getCertificate()).getSubject()));
respBuilder.addResponse(certId, CertificateStatus.GOOD, past, past);
BasicOCSPResp expiredBasicResp = respBuilder.build(
new JcaContentSignerBuilder("SHA256withRSA").build(caRoot.getKeyPair().getPrivate()),
new X509CertificateHolder[0],
past);
final byte[] responseEncoded = new OCSPRespBuilder()
.build(OCSPRespBuilder.SUCCESSFUL, expiredBasicResp).getEncoded();
IoTransport defaultTransport = createDefaultTransport();
IoTransport mockTransport = IoTransport.create(defaultTransport.eventLoop(), () -> {
NioSocketChannel channel = new NioSocketChannel();
channel.pipeline().addFirst(new ChannelOutboundHandlerAdapter() {
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) {
promise.setSuccess();
ctx.executor().execute(() -> {
ctx.pipeline().fireChannelActive();
DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(responseEncoded));
httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/ocsp-response");
httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,
httpResponse.content().readableBytes());
ctx.pipeline().fireChannelRead(httpResponse);
});
}
});
return channel;
}, defaultTransport.datagramChannel());
SslContext serverSslCtx = SslContextBuilder
.forServer(targetCert.getKeyPair().getPrivate(),
targetCert.getCertificate(), caRoot.getCertificate())
.build();
Channel serverChannel = new ServerBootstrap()
.group(defaultTransport.eventLoop())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(serverSslCtx.newHandler(ch.alloc()));
}
})
.bind(0).sync().channel();
int serverPort = ((InetSocketAddress) serverChannel.localAddress()).getPort();
AtomicBoolean validEventFired = new AtomicBoolean();
AtomicReference<Throwable> caughtException = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
DnsNameResolver resolver = OcspServerCertificateValidator.createDefaultResolver(mockTransport);
SslContext clientSslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
new Bootstrap()
.group(defaultTransport.eventLoop())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(clientSslCtx.newHandler(ch.alloc(), "127.0.0.1", serverPort));
ch.pipeline().addLast(
new OcspServerCertificateValidator(true, false, mockTransport, resolver));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof OcspValidationEvent &&
((OcspValidationEvent) evt).response().status() ==
OcspResponse.Status.VALID) {
validEventFired.set(true);
}
ctx.fireUserEventTriggered(evt);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
caughtException.compareAndSet(null, cause);
ctx.channel().close();
latch.countDown();
}
});
}
})
.connect("127.0.0.1", serverPort).sync();
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertFalse(validEventFired.get(),
"OcspValidationEvent(VALID) must not be emitted for a stale OCSP response");
assertNotNull(caughtException.get());
assertInstanceOf(IllegalStateException.class, caughtException.get());
serverChannel.close().sync();
resolver.close();
}
Impact
Certificate revocation bypass via replay of an expired OCSP response. Any application using OcspServerCertificateValidator is affected; a revoked certificate can be accepted.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-handler-ssl-ocsp"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.16.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-handler-ssl-ocsp"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.136.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-56821"
],
"database_specific": {
"cwe_ids": [
"CWE-299"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-22T21:46:39Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n`OcspServerCertificateValidator` flags an out-of-date OCSP response but does not stop processing it, so an expired GOOD response is still reported as `VALID`, letting an on-path attacker replay a stale GOOD response to bypass revocation of a since-revoked certificate.\n\n### Details\nIn `io.netty.handler.ssl.ocsp.OcspServerCertificateValidator#userEventTriggered` the freshness check has no `return`, so execution falls through and a `VALID` `OcspValidationEvent` is still fired:\n\n```java\n if (!(current.after(response.getThisUpdate()) \u0026\u0026\n current.before(response.getNextUpdate()))) {\n ctx.fireExceptionCaught(new IllegalStateException(\"OCSP Response is out-of-date\"));\n }\n```\n\nNonce validation is optional and off by default, so freshness is the only replay defense \u2014 and it is not enforced. Additionally `getNextUpdate()` may be `null`, making `current.before(null)` throw `NullPointerException`.\n\nhttps://datatracker.ietf.org/doc/html/rfc6960#section-3.2\n\n```\n 5. The time at which the status being indicated is known to be\n correct (thisUpdate) is sufficiently recent;\n\n 6. When available, the time at or before which newer information will\n be available about the status of the certificate (nextUpdate) is\n greater than the current time.\n```\n\n### PoC\n\nAdd the test below to `io.netty.handler.ssl.ocsp.OcspServerCertificateValidatorTest`\n\n```java\n @Test\n void staleOcspResponseIsRejected() throws Exception {\n X509Bundle caRoot = new CertificateBuilder()\n .algorithm(CertificateBuilder.Algorithm.rsa2048)\n .subject(\"CN=TrustedRootCA\")\n .setIsCertificateAuthority(true)\n .buildSelfSigned();\n\n GeneralName ocspName = new GeneralName(GeneralName.uniformResourceIdentifier, \"http://localhost/\");\n AuthorityInformationAccess aia = new AuthorityInformationAccess(\n new AccessDescription(AccessDescription.id_ad_ocsp, ocspName));\n X509Bundle targetCert = new CertificateBuilder()\n .algorithm(CertificateBuilder.Algorithm.rsa2048)\n .subject(\"CN=TargetServer\")\n .addExtensionOctetString(\"1.3.6.1.5.5.7.1.1\", false, aia.getEncoded())\n .buildIssuedBy(caRoot);\n\n Date past = new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7));\n CertificateID certId = new CertificateID(\n new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),\n new JcaX509CertificateHolder(caRoot.getCertificate()),\n targetCert.getCertificate().getSerialNumber());\n BasicOCSPRespBuilder respBuilder = new BasicOCSPRespBuilder(\n new RespID(new JcaX509CertificateHolder(caRoot.getCertificate()).getSubject()));\n respBuilder.addResponse(certId, CertificateStatus.GOOD, past, past);\n BasicOCSPResp expiredBasicResp = respBuilder.build(\n new JcaContentSignerBuilder(\"SHA256withRSA\").build(caRoot.getKeyPair().getPrivate()),\n new X509CertificateHolder[0],\n past);\n final byte[] responseEncoded = new OCSPRespBuilder()\n .build(OCSPRespBuilder.SUCCESSFUL, expiredBasicResp).getEncoded();\n\n IoTransport defaultTransport = createDefaultTransport();\n IoTransport mockTransport = IoTransport.create(defaultTransport.eventLoop(), () -\u003e {\n NioSocketChannel channel = new NioSocketChannel();\n channel.pipeline().addFirst(new ChannelOutboundHandlerAdapter() {\n @Override\n public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,\n SocketAddress localAddress, ChannelPromise promise) {\n promise.setSuccess();\n ctx.executor().execute(() -\u003e {\n ctx.pipeline().fireChannelActive();\n DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(\n HttpVersion.HTTP_1_1, HttpResponseStatus.OK,\n Unpooled.wrappedBuffer(responseEncoded));\n httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, \"application/ocsp-response\");\n httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,\n httpResponse.content().readableBytes());\n ctx.pipeline().fireChannelRead(httpResponse);\n });\n }\n });\n return channel;\n }, defaultTransport.datagramChannel());\n\n SslContext serverSslCtx = SslContextBuilder\n .forServer(targetCert.getKeyPair().getPrivate(),\n targetCert.getCertificate(), caRoot.getCertificate())\n .build();\n Channel serverChannel = new ServerBootstrap()\n .group(defaultTransport.eventLoop())\n .channel(NioServerSocketChannel.class)\n .childHandler(new ChannelInitializer\u003cSocketChannel\u003e() {\n @Override\n protected void initChannel(SocketChannel ch) {\n ch.pipeline().addLast(serverSslCtx.newHandler(ch.alloc()));\n }\n })\n .bind(0).sync().channel();\n\n int serverPort = ((InetSocketAddress) serverChannel.localAddress()).getPort();\n\n AtomicBoolean validEventFired = new AtomicBoolean();\n AtomicReference\u003cThrowable\u003e caughtException = new AtomicReference\u003c\u003e();\n CountDownLatch latch = new CountDownLatch(1);\n\n DnsNameResolver resolver = OcspServerCertificateValidator.createDefaultResolver(mockTransport);\n SslContext clientSslCtx = SslContextBuilder.forClient()\n .trustManager(InsecureTrustManagerFactory.INSTANCE)\n .build();\n new Bootstrap()\n .group(defaultTransport.eventLoop())\n .channel(NioSocketChannel.class)\n .handler(new ChannelInitializer\u003cSocketChannel\u003e() {\n @Override\n protected void initChannel(SocketChannel ch) {\n ch.pipeline().addLast(clientSslCtx.newHandler(ch.alloc(), \"127.0.0.1\", serverPort));\n ch.pipeline().addLast(\n new OcspServerCertificateValidator(true, false, mockTransport, resolver));\n ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {\n @Override\n public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {\n if (evt instanceof OcspValidationEvent \u0026\u0026\n ((OcspValidationEvent) evt).response().status() ==\n OcspResponse.Status.VALID) {\n validEventFired.set(true);\n }\n ctx.fireUserEventTriggered(evt);\n }\n\n @Override\n public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n caughtException.compareAndSet(null, cause);\n ctx.channel().close();\n latch.countDown();\n }\n });\n }\n })\n .connect(\"127.0.0.1\", serverPort).sync();\n\n assertTrue(latch.await(5, TimeUnit.SECONDS));\n assertFalse(validEventFired.get(),\n \"OcspValidationEvent(VALID) must not be emitted for a stale OCSP response\");\n assertNotNull(caughtException.get());\n assertInstanceOf(IllegalStateException.class, caughtException.get());\n\n serverChannel.close().sync();\n resolver.close();\n }\n```\n### Impact\nCertificate revocation bypass via replay of an expired OCSP response. Any application using `OcspServerCertificateValidator` is affected; a revoked certificate can be accepted.",
"id": "GHSA-g7hg-vrcf-mvmr",
"modified": "2026-07-22T21:46:39Z",
"published": "2026-07-22T21:46:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-g7hg-vrcf-mvmr"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.1.136.Final"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.2.16.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Netty: Out-of-date OCSP Responses Accepted by OcspServerCertificateValidator"
}
GHSA-J893-VCPR-RGFC
Vulnerability from github – Published: 2026-06-09 09:32 – Updated: 2026-06-09 09:32Check for certificate revocation only considers the first matching CRL and ignores other valid CRLs of the same CA in the CycloneCrypto cryptographic wrapper of S2OPC library. It might allow connection between an OPC UA client and server using a revoked certificate.
{
"affected": [],
"aliases": [
"CVE-2026-6899"
],
"database_specific": {
"cwe_ids": [
"CWE-299"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-06-09T09:16:30Z",
"severity": "MODERATE"
},
"details": "Check for certificate revocation only considers the first matching CRL and ignores other valid CRLs of the same CA in the CycloneCrypto cryptographic wrapper of S2OPC library. It might allow connection between an OPC UA client and server using a revoked certificate.",
"id": "GHSA-j893-vcpr-rgfc",
"modified": "2026-06-09T09:32:07Z",
"published": "2026-06-09T09:32:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-6899"
},
{
"type": "WEB",
"url": "https://gitlab.com/systerel/S2OPC/-/work_items/1739"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
}
]
}
GHSA-PWJX-QHCG-RVJ4
Vulnerability from github – Published: 2026-03-20 21:51 – Updated: 2026-03-25 19:56If a certificate had more than one distributionPoint, then only the first distributionPoint would be considered against each CRL's IssuingDistributionPoint distributionPoint, and then the certificate's subsequent distributionPoints would be ignored.
The impact was that correct provided CRLs would not be consulted to check revocation. With UnknownStatusPolicy::Deny (the default) this would lead to incorrect but safe Error::UnknownRevocationStatus. With UnknownStatusPolicy::Allow this would lead to inappropriate acceptance of revoked certificates.
This vulnerability is thought to be of limited impact. This is because both the certificate and CRL are signed -- an attacker would need to compromise a trusted issuing authority to trigger this bug. An attacker with such capabilities could likely bypass revocation checking through other more impactful means (such as publishing a valid, empty CRL.)
More likely, this bug would be latent in normal use, and an attacker could leverage faulty revocation checking to continue using a revoked credential.
{
"affected": [
{
"package": {
"ecosystem": "crates.io",
"name": "rustls-webpki"
},
"ranges": [
{
"events": [
{
"introduced": "0.102.0-alpha.0"
},
{
"fixed": "0.103.10"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "crates.io",
"name": "rustls-webpki"
},
"ranges": [
{
"events": [
{
"introduced": "0.104.0-alpha.1"
},
{
"fixed": "0.104.0-alpha.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-299"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-20T21:51:17Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "If a certificate had more than one `distributionPoint`, then only the first `distributionPoint` would be considered against each CRL\u0027s `IssuingDistributionPoint` `distributionPoint`, and then the certificate\u0027s subsequent `distributionPoint`s would be ignored.\n\nThe impact was that correct provided CRLs would not be consulted to check revocation. With `UnknownStatusPolicy::Deny` (the default) this would lead to incorrect but safe `Error::UnknownRevocationStatus`. With `UnknownStatusPolicy::Allow` this would lead to inappropriate acceptance of revoked certificates.\n\nThis vulnerability is thought to be of limited impact. This is because both the certificate and CRL are signed -- an attacker would need to compromise a trusted issuing authority to trigger this bug. An attacker with such capabilities could likely bypass revocation checking through other more impactful means (such as publishing a valid, empty CRL.)\n\nMore likely, this bug would be latent in normal use, and an attacker could leverage faulty revocation checking to continue using a revoked credential.",
"id": "GHSA-pwjx-qhcg-rvj4",
"modified": "2026-03-25T19:56:38Z",
"published": "2026-03-20T21:51:17Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/rustls/webpki/security/advisories/GHSA-pwjx-qhcg-rvj4"
},
{
"type": "PACKAGE",
"url": "https://github.com/rustls/webpki"
},
{
"type": "WEB",
"url": "https://rustsec.org/advisories/RUSTSEC-2026-0049.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "webpki: CRLs not considered authoritative by Distribution Point due to faulty matching logic"
}
GHSA-V8J7-GW8H-M2J4
Vulnerability from github – Published: 2025-04-01 12:30 – Updated: 2025-09-24 18:30A MongoDB server under specific conditions running on Linux with TLS and CRL revocation status checking enabled, fails to check the revocation status of the intermediate certificates in the peer's certificate chain. In cases of MONGODB-X509, which is not enabled by default, this may lead to improper authentication. This issue may also affect intra-cluster authentication. This issue affects MongoDB Server v5.0 versions prior to 5.0.31, MongoDB Server v6.0 versions prior to 6.0.20, MongoDB Server v7.0 versions prior to 7.0.16 and MongoDB Server v8.0 versions prior to 8.0.4. Required Configuration : MongoDB Server must be running on Linux Operating Systems and CRL revocation status checking must be enabled
{
"affected": [],
"aliases": [
"CVE-2025-3085"
],
"database_specific": {
"cwe_ids": [
"CWE-299"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-04-01T12:15:16Z",
"severity": "HIGH"
},
"details": "A MongoDB server under specific conditions running on Linux with TLS and CRL revocation status checking enabled, fails to check the revocation status of the intermediate certificates in the peer\u0027s certificate chain. In cases of MONGODB-X509, which is not enabled by default, this may lead to improper authentication. This issue may also affect intra-cluster authentication. This issue affects MongoDB Server v5.0 versions prior to 5.0.31, MongoDB Server v6.0 versions prior to 6.0.20, MongoDB Server v7.0 versions prior to 7.0.16 and MongoDB Server v8.0 versions prior to 8.0.4.\nRequired Configuration :\u00a0MongoDB Server must be running on Linux Operating Systems and CRL revocation status checking must be enabled",
"id": "GHSA-v8j7-gw8h-m2j4",
"modified": "2025-09-24T18:30:23Z",
"published": "2025-04-01T12:30:35Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-3085"
},
{
"type": "WEB",
"url": "https://jira.mongodb.org/browse/SERVER-95445"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-VQR9-9VW2-33W6
Vulnerability from github – Published: 2026-09-27 12:30 – Updated: 2026-09-29 21:33When an application enables both OCSP and CRL revocation checking on one WOLFSSL_CTX or certificate manager, wolfSSL skips the CRL check for any peer certificate that carries no Authority Information Access OCSP URL, and accepts a certificate the loaded CRL lists as revoked. The soft-fail policy for a missing responder collapses the OCSP result onto success before the code decides whether the CRL fallback is still needed, so "no responder exists" becomes indistinguishable from "the responder answered good". Affected builds define both HAVE_OCSP and HAVE_CRL: --enable-ocsp --enable-crl directly, and implicitly --enable-all, --enable-distro, --enable-curl, --enable-nginx, --enable-haproxy, --enable-stunnel, --enable-lighty, --enable-wpas, --enable-strongswan, --enable-mosquitto, --enable-jni, --enable-openvpn and --enable-krb. An application is affected only if it calls both wolfSSL_CTX_EnableOCSP() (or wolfSSL_EnableOCSP() / wolfSSL_CertManagerEnableOCSP()) and wolfSSL_CTX_EnableCRL() (or the equivalents) with a CRL loaded; an application that uses OCSP stapling alone through wolfSSL_CTX_EnableOCSPStapling() is not affected, because that sets up a separate OCSP instance. The defect sits in ProcessPeerCerts() and is reachable over TLS 1.0 through TLS 1.3 and DTLS, both on a client verifying a server certificate and on a server verifying a client certificate under mutual or post-handshake authentication. When the skipped check falls on a chain certificate rather than the leaf, the unchecked intermediate is promoted into the certificate manager and stays a trusted signer for every later connection on that context, so an affected long-running process needs its WOLFSSL_CTX torn down and not only its library replaced. All wolfSSL versions from 5.9.2 and earlier are affected; on versions 5.9.1 and 5.9.2 the WOLFSSL_OCSP_CHECKALL configuration fails closed with OCSP_NEED_URL, which leaves wolfSSL_CTX_EnableOCSP() without CHECKALL as the exposed configuration on 5.9.2.
{
"affected": [],
"aliases": [
"CVE-2026-94417"
],
"database_specific": {
"cwe_ids": [
"CWE-299"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-27T10:16:59Z",
"severity": "LOW"
},
"details": "When an application enables both OCSP and CRL revocation checking on one WOLFSSL_CTX or certificate manager, wolfSSL skips the CRL check for any peer certificate that carries no Authority Information Access OCSP URL, and accepts a certificate the loaded CRL lists as revoked. The soft-fail policy for a missing responder collapses the OCSP result onto success before the code decides whether the CRL fallback is still needed, so \"no responder exists\" becomes indistinguishable from \"the responder answered good\". Affected builds define both HAVE_OCSP and HAVE_CRL: --enable-ocsp --enable-crl directly, and implicitly --enable-all, --enable-distro, --enable-curl, --enable-nginx, --enable-haproxy, --enable-stunnel, --enable-lighty, --enable-wpas, --enable-strongswan, --enable-mosquitto, --enable-jni, --enable-openvpn and --enable-krb. An application is affected only if it calls both wolfSSL_CTX_EnableOCSP() (or wolfSSL_EnableOCSP() / wolfSSL_CertManagerEnableOCSP()) and wolfSSL_CTX_EnableCRL() (or the equivalents) with a CRL loaded; an application that uses OCSP stapling alone through wolfSSL_CTX_EnableOCSPStapling() is not affected, because that sets up a separate OCSP instance. The defect sits in ProcessPeerCerts() and is reachable over TLS 1.0 through TLS 1.3 and DTLS, both on a client verifying a server certificate and on a server verifying a client certificate under mutual or post-handshake authentication. When the skipped check falls on a chain certificate rather than the leaf, the unchecked intermediate is promoted into the certificate manager and stays a trusted signer for every later connection on that context, so an affected long-running process needs its WOLFSSL_CTX torn down and not only its library replaced. All wolfSSL versions from 5.9.2 and earlier are affected; on versions 5.9.1 and 5.9.2 the WOLFSSL_OCSP_CHECKALL configuration fails closed with OCSP_NEED_URL, which leaves wolfSSL_CTX_EnableOCSP() without CHECKALL as the exposed configuration on 5.9.2.",
"id": "GHSA-vqr9-9vw2-33w6",
"modified": "2026-09-29T21:33:06Z",
"published": "2026-09-27T12:30:21Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-94417"
},
{
"type": "WEB",
"url": "https://github.com/wolfSSL/wolfssl/pull/11500"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
Mitigation
Ensure that certificates are checked for revoked status.
Mitigation
If certificate pinning is being used, ensure that all relevant properties of the certificate are fully validated before the certificate is pinned, including the revoked status.
No CAPEC attack patterns related to this CWE.