CVE-2026-23849

Clockwatching: Weaponizing Milliseconds in File Browser Authentication

Alon Barad
Alon Barad
Software Engineer

Jan 21, 2026·6 min read·5 visits

Executive Summary (TL;DR)

File Browser versions prior to 2.55.0 failed to use constant-time comparison logic during authentication. Because verifying a password with bcrypt takes significantly longer than checking if a user exists in the database, the server responded much faster for invalid users than for valid ones. This discrepancy allows attackers to accurately map out valid accounts on the system.

A classic timing side-channel vulnerability in the popular File Browser application allows unauthenticated attackers to enumerate valid usernames by measuring the server's response time during login attempts.

The Hook: The Ticking Time Bomb

In the world of web application security, we often obsess over the data the server explicitly sends back—error messages, stack traces, or JSON payloads. But sometimes, the most damning information isn't what the server says, but how long it takes to say it.

File Browser is a sleek, Go-based file manager that people love for its speed and simplicity. It’s often used to manage sensitive documents, making it a juicy target. However, in its quest for efficiency, the authentication mechanism leaked information through a classic side-channel: time.

This vulnerability (CVE-2026-23849) is a textbook example of a timing attack. It allows an attacker to ask the server, "Does the user 'admin' exist?" and receive a silent "Yes" simply by watching a stopwatch. It’s the digital equivalent of knocking on a door: if someone walks to the peephole to check who it is, you know someone is home, even if they don't open the door. If the house is empty, the silence is immediate.

The Flaw: A Short-Circuit to Disaster

The root cause lies in a common programming optimization that turns fatal in a security context: the short-circuit logical OR operator (||). Developers are taught to optimize code by failing fast. If a condition is met that invalidates the request, why do more work?

In the vulnerable JSONAuth.Auth function, the code logic was essentially: "If the user lookup fails OR the password check fails, deny access."

Here is the logic in plain English:

  1. Check the database for the username.
  2. If the user is NOT found, stop immediately and return an error.
  3. If the user IS found, proceed to hash the provided password and compare it to the stored hash.

The fatal flaw is step 3. File Browser uses bcrypt for password hashing. Bcrypt is intentionally slow. It uses a work factor (cost) to make brute-forcing hard, typically taking 50ms to 500ms depending on the server CPU. A database lookup for a missing user, on the other hand, takes microseconds—maybe 1ms total.

This creates a massive, measurable time gap.

  • Invalid User: ~1ms response.
  • Valid User: ~50ms+ response.

To a hacker, that 50ms difference is as loud as a siren.

The Code: The Smoking Gun

Let's look at the Go code responsible for this logic. This snippet is from auth/json.go.

The Vulnerable Code

u, err := usr.Get(srv.Root, cred.Username)
// The fatal flaw:
// If err != nil (User not found), the second part is NEVER executed.
if err != nil || !users.CheckPwd(cred.Password, u.Password) {
    return nil, os.ErrPermission
}

Go's runtime executes the left side of || first. If err != nil, the statement evaluates to true, and the block executes immediately. The expensive users.CheckPwd function is skipped entirely.

The Fix (Version 2.55.0)

The patch introduces a concept known as "Constant Time Execution" (or strictly speaking, normalized execution time). The developers realized they must perform a bcrypt hash even if the user doesn't exist.

// Define a dummy hash to burn CPU cycles if the user is missing
const dummyHash = "$2a$10$O4mEMeOL/nit6zqe.WQXauLRbRlzb3IgLHsa26Pf0N/GiU9b.wK1m"
 
func (a JSONAuth) Auth(...) {
    u, err := usr.Get(srv.Root, cred.Username)
 
    // Normalize the data
    hash := dummyHash
    if err == nil {
        hash = u.Password
    }
 
    // ALWAYS perform the expensive check
    // If user is missing, we check against dummyHash.
    // If user exists, we check against real hash.
    match := users.CheckPwd(cred.Password, hash)
 
    if !match || err != nil {
        return nil, os.ErrPermission
    }
    return u, nil
}

By forcing the server to crunch numbers regardless of the username's validity, the response times flatten out. The signal is lost in the noise.

The Exploit: Weaponizing Statistics

Exploiting timing attacks over a network (like the internet) is tricky because of "jitter." Network latency varies. A 50ms spike might just be a router hiccup, not a valid user. To exploit this reliably, we use statistics.

The Attack Strategy

  1. Calibration: The attacker sends 50 requests with random, garbage usernames (e.g., user_xyz123). They measure the average response time and calculate the standard deviation.
  2. Thresholding: A threshold is set, usually Mean + (5 * Standard_Deviation). Anything above this is statistically significant.
  3. Enumeration: The attacker iterates through a wordlist (e.g., admin, root, backup).
  4. Verification: If a request exceeds the threshold, it is flagged as a valid user.

The PoC Logic

A Python script targeting this vulnerability would look something like this:

def measure_response(user):
    start = time.perf_counter()
    requests.post(target, json={"username": user, "password": "x"})
    return time.perf_counter() - start
 
# 1. Calibrate Baseline (Invalid Users)
baseline_times = [measure_response(random_str()) for _ in range(50)]
threshold = mean(baseline_times) + 0.05 # Add 50ms buffer
 
# 2. Attack
for user in wordlist:
    latency = measure_response(user)
    if latency > threshold:
        print(f"[+] VALID USER FOUND: {user} ({latency:.4f}s)")

[!NOTE] In a real-world scenario, smart attackers will average multiple requests for the same username to smooth out network jitter, making the detection of valid accounts nearly 100% accurate.

The Impact: Why Should You Care?

You might ask, "So what if they know my username? They still need the password."

True, but in security, knowledge is power. Username enumeration is the precursor to more aggressive attacks.

  1. Focused Brute Force: Instead of trying 1000 passwords against 1000 users (1,000,000 requests), I can find the one valid admin user and try 10,000 passwords against just them. It's quieter and more effective.
  2. Social Engineering: If I know j.smith is a user, I can target John Smith with a specific phishing email regarding his File Browser account.
  3. Default Credentials: Attackers will instantly check valid users against common passwords like password, 123456, or the username itself.

In a system like File Browser, which often sits on the perimeter allowing access to internal files, a compromised account often leads to full data exfiltration or Remote Code Execution (if the user can upload files).

The Fix: Remediation

The fix is straightforward: Update to File Browser v2.55.0.

If you cannot update immediately, you must rely on perimeter defenses:

  1. Rate Limiting: Configure your reverse proxy (Nginx, Traefik, Apache) to aggressively rate-limit the /api/login endpoint. If an attacker can only make 1 request every 5 seconds, the statistical analysis required for a timing attack becomes painfully slow.
  2. Fail2Ban: Implement IP banning for repeated failed login attempts. Since the attacker must generate failed logins to measure the time, they will trip this alarm quickly.
  3. WAF Rules: Block requests that look like automated enumeration tools (checking for User-Agent strings or rapid-fire sequencing).

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
EPSS Probability
0.09%
Top 100% most exploited

Affected Systems

File Browser (GitHub: filebrowser/filebrowser)

Affected Versions Detail

Product
Affected Versions
Fixed Version
File Browser
File Browser
< 2.55.02.55.0
AttributeDetail
CWE IDCWE-208
Attack VectorNetwork
CVSS5.3 (Medium)
EPSS Score0.0009
ImpactInformation Disclosure (Username Enumeration)
Exploit StatusPoC Available
CWE-208
Observable Timing Discrepancy

Observable Timing Discrepancy

Vulnerability Timeline

Vulnerability Published
2026-01-19
Patch Released (v2.55.0)
2026-01-19

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.