December 18, 2024·4 min read·engineeringkafkasecurityscramtls

Kafka SCRAM replay protection depends on validating the server nonce

CVE-2024-56128 exposes a missing nonce check in Kafka SCRAM authentication without TLS. The fix restores validation of the exchange, while TLS prevents the plaintext interception required for exploitation.

A challenge-response authentication exchange needs to establish that a response belongs to the current challenge. Checking a password-derived proof does not excuse the server from checking the nonce returned with it. Kafka's December security disclosure concerns that missing validation in its SCRAM implementation.

CVE-2024-56128, announced December 18, affects SCRAM authentication when an attacker can access the exchange in plaintext. Apache explicitly excludes deployments using SCRAM with TLS. The distinction makes transport configuration part of determining exposure, even though the underlying defect is in the authentication algorithm's implementation.

I value Kafka's durable history because consumers can return to retained records independently. That makes the authentication boundary worth treating precisely. A description such as "SCRAM enabled" leaves out the transport condition that determines whether this particular vulnerability is exploitable.

The returned nonce connects the exchange to its challenge

RFC 5802, published in July 2010, defines the SCRAM exchange. The client starts with a username and a randomly generated nonce. The server extends that nonce with its own contribution and returns the combined value alongside the salt and iteration count used for the password-derived calculation.

The client returns the combined nonce in its final message, together with its proof. Server-side validation must check that the returned nonce matches the one sent. RFC 5802 also requires the client to check that the server's combined nonce begins with the client's original contribution.

These checks connect the participants' messages to the same exchange. The server's random contribution prevents the client from choosing the entire challenge. Returning and validating it establishes that the response incorporates the challenge this server issued during this authentication attempt.

The disclosure says Kafka omitted the server-side validation. Its description identifies replay attacks under the plaintext-access condition. It does not describe a general failure of SCRAM's cryptographic construction or claim that every deployment using password authentication is exposed. The implementation failed to enforce a requirement already present in the protocol.

This is a useful distinction when reading the fix. A stronger password is not a replacement for a missing protocol check. Nor does a successful proof calculation establish that every other field in the exchange has satisfied its own validation rule.

The fixed server rejects the message before completing authentication

The Kafka 3.9.0 server implementation makes the location of the check concrete. ScramSaslServer first processes the client's initial message, obtains the stored SCRAM credentials and constructs a server-first message with a newly generated server nonce.

The server retains that message while waiting for the client's final message. In the fixed implementation, it checks the final message's nonce against the saved server-first nonce before calling verifyClientProof. A failed check throws a SaslException with an invalid-client-nonce message.

Only after validation and proof verification does the server calculate its final signature and mark authentication complete. The exception path clears credentials and places the exchange in the failed state. The additional validation therefore occurs before the successful state transition.

The code also shows why this correction belongs inside the authentication implementation. The server already owns the challenge and the state of the exchange. An application consuming Kafka records cannot reconstruct that validation afterward, and a topic configuration has no corresponding responsibility.

The relevant invariant is local to each authentication conversation. The final client message must satisfy the checks for the server-first message retained by that conversation. Restoring that relationship corrects the acceptance decision at the point where the server makes it.

Exposure depends on plaintext access

Apache's CVE record directs administrators to inspect server.properties, particularly the listeners configuration. It identifies SASL_PLAINTEXT as a sign that a SCRAM deployment may be affected. The qualifier matters because the advisory concerns SCRAM exchanges without TLS, not every use of a listener supporting SASL.

An inventory therefore needs both the authentication mechanism and the transport used for that mechanism. A version number establishes whether the implementation contains the defect. Listener configuration establishes whether the installation exposes the plaintext authentication condition described by the advisory.

The record classifies the defect as an incorrect implementation of an authentication algorithm. It credits Tim Fox with finding it and Vikas Singh with developing the remediation. The published mitigation permits TLS protection for installations that cannot yet upgrade to a fixed release.

That mitigation follows the stated attack requirement. Encrypting the SCRAM exchange removes the plaintext access on which exploitation depends. It does not turn the older implementation into one that performs the missing check, so transport mitigation and software remediation remain separate changes with separate evidence.

I prefer security descriptions that keep those responsibilities explicit. SCRAM identifies the authentication mechanism, while TLS supplies transport protection. Combining them in a deployment does not make their guarantees interchangeable, and the advisory's unaffected case depends on that combination.

The fixed release must be chosen within its branch

Apache lists affected releases as 0.10.2.0 through 3.7.1, together with 3.8.0. The fixed releases are 3.7.2, 3.8.1 and 3.9.0. Those explicit boundaries are more useful than treating the advisory's general upgrade wording as a simple numerical comparison.

In particular, 3.8.0 remains affected even though its version number is higher than 3.7.2. An installation staying on the 3.8 branch needs the listed 3.8.1 fix. A branch-specific release decision avoids confusing the existence of a fix in one maintained line with its presence in every numerically later release.

TLS-protected SCRAM deployments are outside this vulnerability's stated exposure. For deployments without that protection, upgrading supplies the missing nonce validation, and enabling TLS supplies the recommended protection against interception. The disclosure supports both actions without requiring a switch away from SCRAM.

The fix also does not make unencrypted SCRAM the recommended configuration. Apache continues to recommend SCRAM exclusively with TLS. Removing this replay defect addresses one implementation error, while transport encryption continues to protect the exchange against observation. A completed upgrade should therefore be recorded separately from the transport configuration used by the clients and listeners involved.