CVE-2026-33557: Verified Repro With Script Download
CVE-2026-33557: Apache Kafka SASL/OAUTHBEARER accepts unvalidated JWTs
CVE-2026-33557 is verified against Apache Kafka · maven. Affected versions: 4.1.0 through 4.1.1. Vulnerability class: Auth Bypass. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00281.
What Is CVE-2026-33557?
CVE-2026-33557 is a critical, suspected authentication weakness in Apache Kafka's broker-side SASL/OAUTHBEARER handling, where the default JWT validator may fail to properly enforce token expiration on replay. Pruva reproduced it (reproduction REPRO-2026-00281).
CVE-2026-33557 Severity & CVSS Score
CVE-2026-33557 is rated critical severity, with a CVSS base score of 9.1 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected Apache Kafka Versions
Apache Kafka · maven versions 4.1.0 through 4.1.1 are affected.
How to Reproduce CVE-2026-33557
pruva-verify REPRO-2026-00281 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00281/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-33557
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
expired JWT replayed directly in Kafka SASL/OAUTHBEARER initial client response
- TCP connection to Kafka broker
- SaslHandshake(OAUTHBEARER)
- SaslAuthenticate(auth=Bearer <expired JWT>)
- OAuthBearerValidatorCallbackHandler
- DefaultJwtValidator
Alternate trigger on Apache Kafka 4.1.0 broker SASL/OAUTHBEARER: non-expired JWTs with wrong issuer, wrong audience, or untrusted signature are accepted through the same DefaultJwtValidator -> ClientJwtValidator broker path. Kafka 4.1.2 rejects all tested variants, so this is not a fixed-version bypass.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-33557
Apache Kafka 4.1.0/4.1.1 broker-side SASL/OAUTHBEARER validation can accept attacker-supplied JWTs through the network authentication path when the default broker validator is used. The current reproduction focuses on the judge-requested observable vulnerable-vs-fixed divergence for an expired JWT replayed directly over Kafka's TCP SASL boundary: Kafka 4.1.0 returns a successful SaslAuthenticate response for the expired replayed JWT, while Kafka 4.1.2 rejects the same token with invalid_token and broker-side BrokerJwtValidator expiration evidence. This avoids Kafka client-side expiry enforcement by using a raw Kafka protocol client only for the SASL frames.
- Package/component affected: Apache Kafka broker SASL/OAUTHBEARER authentication, specifically
org.apache.kafka.common.security.oauthbearer.DefaultJwtValidatorused byOAuthBearerValidatorCallbackHandler. - Affected versions: Apache Kafka 4.1.0 through 4.1.1 according to the Apache advisory for CVE-2026-33557.
- Fixed versions: Apache Kafka 4.1.2 and 4.2.0+.
- Risk level and consequences: Important/high authentication validation weakness. The advisory impact is that a broker using the default validator may accept JWTs without validating signature, issuer, or audience. In this run, the expired-token replay path demonstrates that the vulnerable broker accepts an expired JWT at the SASL authentication step, while the fixed broker rejects it.
Impact Parity
- Disclosed/claimed maximum impact:
authz_bypass/ authentication validation bypass for Kafka SASL/OAUTHBEARER. - Reproduced impact from this run: network-protocol authentication divergence on the real Kafka broker TCP listener. The vulnerable broker returns
SaslAuthenticate(error_code=0)for an expired JWT replayed directly in the SASL initial client response; the fixed broker returns an invalid-token challenge and logs expiration validation failure. - Parity:
fullfor the broker-side token validation bypass at the SASL/OAUTHBEARER TCP authentication boundary. - Not demonstrated: The proof does not require a Kafka CLI client to use the expired credential because the standard client enforces expiry before replay. The primary proof intentionally bypasses that client-side behavior with raw Kafka SASL frames to test the broker-side validator directly.
Root Cause
Kafka 4.1.0's DefaultJwtValidator.configure() always delegates to ClientJwtValidator when no explicit verification key resolver is passed. ClientJwtValidator performs only structural and claim-presence checks for scope, exp, sub, and iat; it does not verify the JWT signature and does not reject an exp timestamp that is already in the past. Therefore, the broker-side OAuthBearerValidatorCallbackHandler can receive a replayed JWT over TCP and treat it as valid.
Kafka 4.1.2 changes DefaultJwtValidator.configure() so that when sasl.oauthbearer.jwks.endpoint.url is present, the default delegate becomes BrokerJwtValidator, which performs signature and claims validation, including expiration. The fix commit referenced by the advisory is 01d8e7db8d08dbd538892b409457ea6bfcc2a422.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.sh. - The script installs required Python dependencies, reuses or downloads Kafka 4.1.0 and Kafka 4.1.2 binaries, starts a real mock JWKS/OAuth issuer, starts real Kafka KRaft brokers with SASL/OAUTHBEARER enabled, and sends raw Kafka
SaslHandshakeandSaslAuthenticateTCP frames containing the same expired attacker-controlled JWT. - Expected evidence of reproduction:
- Kafka 4.1.0: expired JWT replay receives
SaslAuthenticatesuccess (error_code=0, no invalid-token auth bytes). - Kafka 4.1.2: the same expired JWT receives an invalid-token response and broker logs show
BrokerJwtValidatorrejecting theexpclaim as expired. - Valid JWT controls are also run so the fixed broker's rejection is not caused by a broken test setup.
- Kafka 4.1.0: expired JWT replay receives
Evidence
Primary evidence is written under bundle/logs/:
bundle/logs/evidence.log: summary, decoded JWT claims, version comparison, and key protocol outcomes.bundle/logs/raw_vulnerable_expired.json: parsed raw Kafka SASL response showing vulnerable acceptance of the expired JWT.bundle/logs/raw_fixed_expired.json: parsed raw Kafka SASL response showing fixed rejection of the same expired JWT.bundle/logs/broker_vulnerable.log: real Kafka 4.1.0 broker runtime log.bundle/logs/broker_fixed.log: real Kafka 4.1.2 broker runtime log with expiration rejection details.bundle/logs/mock_oauth_server.log: generated expired/valid JWTs and JWKS requests.
Key expected excerpts:
- Vulnerable parsed response:
"sasl_authenticate_error_code": 0,"accepted_by_broker": true, and an expired JWT claim whereexp < replay_time. - Fixed parsed response:
"rejected_by_broker": truewith"invalid_token"in the auth bytes/error payload. - Fixed broker log:
The JWT is no longer valid ... Expiration Time ...fromBrokerJwtValidator.
Environment details captured include Java version, Kafka binary versions, mock issuer/JWKS URL, decoded token timestamps, and the raw TCP proof JSON files.
Recommendations / Next Steps
- Upgrade affected Kafka deployments to 4.1.2, 4.2.0, or later.
- If upgrade is not immediately possible, explicitly configure
sasl.oauthbearer.jwt.validator.class=org.apache.kafka.common.security.oauthbearer.BrokerJwtValidatorfor brokers using SASL/OAUTHBEARER and configure a trustedsasl.oauthbearer.jwks.endpoint.url. - Add integration tests that replay expired, wrong-issuer, wrong-audience, and unsigned/tampered JWTs directly against the broker-side SASL/OAUTHBEARER listener, not only through Kafka clients.
Additional Notes
The reproduction script is designed to be idempotent: it creates isolated per-run KRaft data directories, uses unique ports per attempt where practical, kills broker/mock processes on exit, and writes fresh runtime evidence on every run. The proof intentionally uses a raw Kafka TCP client after broker startup because the standard Kafka CLI client refuses to replay expired credentials locally; using it would mask the broker-side validator path that this ticket asks to verify.
Variant Analysis & Alternative Triggers for CVE-2026-33557
A distinct alternate trigger was confirmed on the vulnerable Apache Kafka 4.1.0 broker-side SASL/OAUTHBEARER path, but no bypass of the Kafka 4.1.2 fix was found. Instead of replaying an expired JWT, this variant matrix used non-expired JWTs with (1) an unexpected issuer, (2) an unexpected audience, and (3) a signature produced by an untrusted key while retaining the trusted kid. Kafka 4.1.0 accepted all three invalid tokens over the real network SASL/OAUTHBEARER broker listener because DefaultJwtValidator delegated to ClientJwtValidator; Kafka 4.1.2 rejected the same inputs because the fixed DefaultJwtValidator selected BrokerJwtValidator when sasl.oauthbearer.jwks.endpoint.url was configured. Therefore this is an alternate trigger on the vulnerable version, not a fixed-version bypass.
Fix Coverage / Assumptions
The fix relies on the invariant that a broker configured for secured OAuth/OIDC validation has sasl.oauthbearer.jwks.endpoint.url in the effective listener-prefixed configuration, and that this should cause the default validator to instantiate BrokerJwtValidator instead of ClientJwtValidator.
The relevant source-level change between Kafka 4.1.0 and 4.1.2 is in:
clients/src/main/java/org/apache/kafka/common/security/oauthbearer/DefaultJwtValidator.java- Kafka 4.1.0: if no injected
CloseableVerificationKeyResolveris present,DefaultJwtValidator.configure()always usesnew ClientJwtValidator(). - Kafka 4.1.2: if no injected resolver is present,
DefaultJwtValidator.configure()constructsConfigurationUtilsand checksSaslConfigs.SASL_OAUTHBEARER_JWKS_ENDPOINT_URL; when present it usesnew BrokerJwtValidator(), otherwise it falls back tonew ClientJwtValidator().
- Kafka 4.1.0: if no injected
The runtime proof also captures the shipped bytecode comparison in:
bundle/logs/vuln_variant/javap_defaultjwtvalidator_4.1.0.txtbundle/logs/vuln_variant/javap_defaultjwtvalidator_4.1.2.txt
The fix explicitly covers the production broker path using OAuthBearerValidatorCallbackHandler plus a configured JWKS endpoint. It does not attempt to remove all uses of ClientJwtValidator, because that class remains appropriate for client-side token parsing and for configurations that intentionally do not provide JWKS. The tested broker path was configured with:
listener.name.sasl_plaintext.oauthbearer.sasl.server.callback.handler.class=org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallbackHandlerlistener.name.sasl_plaintext.oauthbearer.sasl.oauthbearer.jwks.endpoint.url=<mock JWKS URL>sasl.oauthbearer.expected.issuer=https://mock-idp.example.comsasl.oauthbearer.expected.audience=kafka-broker
The Apache Kafka security model treats broker listener authentication as in scope when security is enabled. It also states that security is off by default and that trusted operators, classpath/JAR loading, and intentionally unsecured/development tooling are out of scope. This variant stays inside the in-scope boundary: an untrusted network peer sends a JWT over a broker SASL listener configured for OAUTHBEARER validation.
Variant / Alternate Trigger
The parent reproduction demonstrated an expired JWT replay. The variant matrix tested three materially different token-validation failures that reach the same sink from the same network trust boundary:
wrong_issuer: JWT has a valid futureexp, trusted signature, and correct audience, butiss=https://attacker-idp.example.netinstead of the configured expected issuer.wrong_audience: JWT has a valid futureexp, trusted signature, and correct issuer, butaud=other-serviceinstead ofkafka-broker.tampered_signature: JWT has valid futureexp, correct issuer, and correct audience, but is signed with an untrusted RSA key while using the trusted key id.
Exact entry point:
- Network protocol: TCP connection to Kafka broker listener.
- Kafka API messages:
SaslHandshake(OAUTHBEARER)followed bySaslAuthenticatecontaining the RFC 7628 initial client responseauth=Bearer <JWT>. - Target callback path:
OAuthBearerValidatorCallbackHandler.handle()->handleValidatorCallback()->DefaultJwtValidator.validate().
Specific code paths:
clients/src/main/java/org/apache/kafka/common/network/SaslChannelBuilder.java: selects the broker SASL callback handler. The default OAUTHBEARER server handler is the unsecured test handler unless the operator configuressasl.server.callback.handler.class; this test configures the securedOAuthBearerValidatorCallbackHandler.clients/src/main/java/org/apache/kafka/common/security/oauthbearer/OAuthBearerValidatorCallbackHandler.java: receives the attacker-supplied token from the SASL exchange and delegates to the configuredJwtValidator.clients/src/main/java/org/apache/kafka/common/security/oauthbearer/DefaultJwtValidator.java: vulnerable-vs-fixed delegate selection.clients/src/main/java/org/apache/kafka/common/security/oauthbearer/ClientJwtValidator.java: parses claims and requires claim presence but does not validate issuer, audience, signature trust, or current-time expiry.clients/src/main/java/org/apache/kafka/common/security/oauthbearer/BrokerJwtValidator.java: fixed broker validator using jose4j with signature verification, expected issuer/audience checks, requiredexp/iat, and expiration enforcement.Package/component affected: Apache Kafka broker SASL/OAUTHBEARER authentication, specifically the default broker-side JWT validator selected behind
OAuthBearerValidatorCallbackHandler.Affected versions as tested: Apache Kafka 4.1.0 binary distribution, release tag commit
13f70256db3c994c590e5d262a7cc50b9e973204.Fixed version as tested: Apache Kafka 4.1.2 binary distribution, release tag commit
c82fd9b934b4c1e6fa799e3f1dcc8f08d997740c.Consequences: On the vulnerable version, a network peer can authenticate to the broker with a token that should be rejected for issuer, audience, or signature trust reasons. This is an authentication validation bypass at the broker listener boundary.
Impact Parity
- Disclosed/claimed maximum impact for the parent:
authz_bypass/ broker-side SASL/OAUTHBEARER authentication validation bypass. - Reproduced impact from this variant run: Kafka 4.1.0 accepted invalid but non-expired JWTs over the production broker TCP SASL path; Kafka 4.1.2 rejected the same invalid inputs.
- Parity:
fullfor the vulnerable-version broker-side JWT validation bypass, because the invalid tokens were accepted by the broker authentication handshake. - Not demonstrated: No fixed-version bypass was demonstrated. The proof does not demonstrate post-auth topic read/write operations; it proves SASL authentication success/failure at the broker boundary.
Root Cause
The same underlying bug is reached because Kafka 4.1.0's DefaultJwtValidator.configure() used ClientJwtValidator whenever no explicit verification-key resolver was injected. In the secured broker callback handler path, this meant a broker with sasl.oauthbearer.jwks.endpoint.url, expected issuer, and expected audience configured still ended up using a validator intended for client-side token parsing. ClientJwtValidator extracts scope, exp, sub, and iat and constructs a BasicOAuthBearerToken, but it does not verify the token signature, compare iss to the expected issuer, compare aud to the expected audience, or enforce current-time expiration.
Kafka 4.1.2 closes the tested path by changing DefaultJwtValidator.configure() to instantiate BrokerJwtValidator when sasl.oauthbearer.jwks.endpoint.url is present. BrokerJwtValidator.configure() builds a jose4j JwtConsumer with DISALLOW_NONE, setRequireExpirationTime(), setRequireIssuedAt(), setVerificationKeyResolver(...), and optional expected issuer/audience. The fixed broker logs show BrokerJwtValidator rejection evidence for the tested invalid issuer/audience/signature tokens.
Fix reference from the parent reproduction: 01d8e7db8d08dbd538892b409457ea6bfcc2a422 was identified as the advisory fix commit, while the tested fixed release tag resolves to c82fd9b934b4c1e6fa799e3f1dcc8f08d997740c.
Reproduction Steps
- Run
bundle/vuln_variant/reproduction_steps.sh. - The script downloads or reuses Kafka 4.1.0 and Kafka 4.1.2 binary distributions, starts a mock OAuth/JWKS issuer, starts isolated real Kafka KRaft brokers configured with SASL/OAUTHBEARER and
OAuthBearerValidatorCallbackHandler, and sends raw KafkaSaslHandshake/SaslAuthenticateframes with valid and invalid JWTs. - Expected evidence:
- Kafka 4.1.0 accepts all three variant tokens: wrong issuer, wrong audience, and tampered signature.
- Kafka 4.1.2 accepts the valid control token but rejects all three variant tokens with
invalid_tokenand broker log evidence fromBrokerJwtValidator. - The script exits
1by design because no fixed-version bypass is reproduced; this is still a successful negative-bypass / alternate-trigger run.
The script was executed twice successfully and produced the same verdict both times: alternate trigger confirmed on Kafka 4.1.0, no bypass on Kafka 4.1.2.
Evidence
Primary evidence locations:
bundle/logs/vuln_variant/reproduction_steps.log: full latest execution log.bundle/logs/vuln_variant/variant_evidence.log: summarized variant matrix and raw response excerpts.bundle/logs/vuln_variant/raw_vulnerable_wrong_issuer.jsonbundle/logs/vuln_variant/raw_vulnerable_wrong_audience.jsonbundle/logs/vuln_variant/raw_vulnerable_tampered_signature.jsonbundle/logs/vuln_variant/raw_fixed_wrong_issuer.jsonbundle/logs/vuln_variant/raw_fixed_wrong_audience.jsonbundle/logs/vuln_variant/raw_fixed_tampered_signature.jsonbundle/logs/vuln_variant/broker_vulnerable.logbundle/logs/vuln_variant/broker_fixed.logbundle/logs/vuln_variant/fixed_version.txt: exact tested release/tag identity.
Key evidence from the latest run:
{
"alternate_trigger_confirmed_on_vulnerable": true,
"bypass_confirmed_on_fixed": false,
"valid_controls_ok": true,
"vulnerable_variant_accepts": {
"wrong_issuer": true,
"wrong_audience": true,
"tampered_signature": true
},
"fixed_variant_rejects": {
"wrong_issuer": true,
"wrong_audience": true,
"tampered_signature": true
},
"fixed_variant_accepts": {
"wrong_issuer": false,
"wrong_audience": false,
"tampered_signature": false
}
}
The fixed broker returned invalid_token for all three variant inputs. For example, the wrong-issuer raw response has accepted_by_broker=false, rejected_by_broker=true, and SASL auth bytes {"status":"invalid_token"}. The fixed broker log includes rejection from BrokerJwtValidator with: Issuer (iss) claim value (https://attacker-idp.example.net) doesn't match expected value of https://mock-idp.example.com.
Environment details captured by the script include Java version, Kafka binary paths, release tag commits, mock issuer/JWKS URL, token claims, raw Kafka response JSON, and broker runtime logs.
Recommendations / Next Steps
- Treat the Kafka 4.1.2 fix as covering the tested secured broker callback path. No additional patch is required for the wrong-issuer, wrong-audience, or tampered-signature data paths when
sasl.oauthbearer.jwks.endpoint.urlis configured. - Keep regression tests for more than expiration. The fix should remain covered by integration tests that send raw SASL/OAUTHBEARER frames for expired tokens, wrong issuer, wrong audience, unsigned/
nonealgorithm tokens, and tokens signed by untrusted keys. - Document and lint broker configurations so production deployments using OAUTHBEARER do not accidentally use
OAuthBearerUnsecuredValidatorCallbackHandleror omit the JWKS endpoint while expecting production-grade JWT validation. - Consider an operator-facing warning when a broker listener uses OAUTHBEARER without a secured server callback handler or without JWKS configuration, because Kafka's security model permits insecure configurations but operators may misinterpret them.
Additional Notes
The reproduction script is idempotent: it creates per-run working directories under bundle/vuln_variant/artifacts/, chooses fresh local ports, and kills mock/broker processes on exit. Both required verification runs completed without crashing; both returned exit code 1 because the fixed version rejected every variant input, which is the expected negative-bypass outcome. The Killed messages in the log correspond to deliberate broker shutdown with kill -9 during cleanup after successful probes, not an infrastructure failure.
CVE-2026-33557 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-33557
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-33557
FAQ: CVE-2026-33557
How does the CVE-2026-33557 replay attack work?
SaslAuthenticate response for the expired token, while Kafka 4.1.2 rejects it with invalid_token and broker-side BrokerJwtValidator expiration evidence.Which Kafka versions are affected by CVE-2026-33557, and where is it fixed?
How severe is CVE-2026-33557?
How can I reproduce CVE-2026-33557?
References for CVE-2026-33557
Authoritative sources for CVE-2026-33557 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.