Jan 23, 2026·5 min read·119 visits
Spring Security tried to fix a password truncation issue (CVE-2025-22228) by banning passwords longer than 72 bytes. However, they enforced this check too early in the authentication flow. By sending a 73+ byte password, attackers could trigger an immediate exception for non-existent users (bypassing the 'dummy' hash), while existing users took longer to process. This timing discrepancy allowed for reliable username enumeration.
A medium-severity timing attack vulnerability in Spring Security's DaoAuthenticationProvider. An improperly implemented length check for BCrypt passwords allowed attackers to bypass timing mitigations and enumerate usernames by analyzing response times.
Spring Security is the gold standard for Java authentication. But in early 2025, the maintainers learned a hard lesson in "fixing things until they break." This is the story of CVE-2025-22234, a vulnerability that proves the road to hell is paved with good intentions—and strict input validation.
It all started when someone realized the BCrypt algorithm has a fundamental limitation: it silently ignores any password characters beyond the 72nd byte. Spring decided to fix this "silent truncation" by throwing an error if a user provided a long password. Sounds reasonable, right? It's better to fail loud than to give a false sense of security.
Wrong. By throwing that error indiscriminately, they accidentally gave attackers a high-precision stopwatch and a map to every valid username in the database. It’s like installing a high-tech burglar alarm that screams "NOBODY IS HOME" the second someone touches the doorknob.
To understand this bug, you have to understand the ancient curse of BCrypt. The algorithm has a hard limit: it only hashes the first 72 bytes of input. Anything after that is ghost data. In a previous patch (CVE-2025-22228), Spring Security decided to enforce this limit strictly.
The logic was added to the BCrypt class: if password.length > 72, throw an IllegalArgumentException. The problem is that authentication happens in two distinct contexts:
The developers applied the exception to both scenarios. This created a logic bomb inside DaoAuthenticationProvider, the component responsible for juggling user lookups and password checks.
Let's look at DaoAuthenticationProvider. It tries to be clever to prevent timing attacks. If a user isn't found in the database, it runs a "dummy" hash operation so the request takes roughly the same amount of time as a successful lookup.
if (user == null) {
// Run a dummy check to waste time and hide the fact the user is missing
mitigateAgainstTimingAttack(authentication);
throw new BadCredentialsException(...);
}Inside mitigateAgainstTimingAttack, it calls the vulnerable BCrypt method. In version 6.4.4, the code looked like this:
private static String hashpw(byte passwordb[], String salt, boolean for_check) {
// THE BUG: This throws immediately, before any CPU work is done
if (passwordb.length > 72) {
throw new IllegalArgumentException("password cannot be more than 72 bytes");
}
// ... expensive hashing loop that takes ~100ms ...
}Do you see it? If I send a 73-byte password for a non-existent user, the "dummy" check crashes instantly due to the exception. It doesn't waste time. It exits.
However, if the user exists, the application loads the user details first, potentially checks the hash differently, or hits the exception later in the pipeline. This creates a massive timing gap.
This vulnerability allows for a classic Side-Channel Timing Attack. We can enumerate users by measuring how fast the server rejects us. Here is the attack chain:
admin and a password string of 75 As.The Results:
DaoAuthenticationProvider sees user == null. It calls mitigateAgainstTimingAttack. The BCrypt class sees 75 bytes and throws the exception immediately. Total time: ~5ms.> [!NOTE] > The difference between 5ms and 150ms is an eternity in network terms. It's trivially easy to distinguish, even with network jitter.
The fix, released in version 6.4.5 (and others), is elegant in its simplicity. The developers realized they needed to distinguish between creating a password and checking one.
The patched code introduces context awareness using the for_check boolean:
private static String hashpw(byte passwordb[], String salt, boolean for_check) {
// FIXED: Only enforce length limit if we are NOT checking (i.e., we are encoding a new password)
if (!for_check && passwordb.length > 72) {
throw new IllegalArgumentException("password cannot be more than 72 bytes");
}
// Proceed with hashing (using first 72 bytes) to maintain timing consistency
// ...
}Now, if I send a long password for a ghost user, the system shrugs, proceeds to the expensive hashing loop (using the first 72 bytes), and returns in 150ms. The timing leak is plugged because the "dummy" calculation actually runs, ensuring User Found and User Not Found take the same amount of time.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Spring Security VMware | 6.4.4 | 6.4.5 |
Spring Security VMware | 6.3.8 | 6.3.9 |
Spring Security VMware | 5.8.18 | 5.8.19 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-208 |
| Attack Vector | Network |
| CVSS | 5.3 (Medium) |
| Impact | Information Disclosure (Username Enumeration) |
| Exploit Status | PoC Available |
| Root Cause | Exception thrown before constant-time operation |
The application functions with a timing discrepancy, where the time taken to respond to a query varies based on the input, allowing an attacker to infer information.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.
CVE-2026-70035 is a high-severity Remote Code Execution (RCE) vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio caused by improper handling during the deserialization of untrusted data (CWE-502). An unauthenticated attacker can execute arbitrary code within the context of the affected process by delivering a crafted serialized payload.
An elevation of privilege vulnerability exists in ASP.NET Core and Microsoft Visual Studio due to improper synchronization during concurrent thread execution. Authenticated remote attackers with low privileges can exploit a timing window to manipulate shared resources and assume higher-level execution contexts.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.