Jan 21, 2026·6 min read·52 visits
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.
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 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:
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.
To a hacker, that 50ms difference is as loud as a siren.
Let's look at the Go code responsible for this logic. This snippet is from auth/json.go.
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 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.
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.
user_xyz123). They measure the average response time and calculate the standard deviation.Mean + (5 * Standard_Deviation). Anything above this is statistically significant.admin, root, backup).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.
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.
admin user and try 10,000 passwords against just them. It's quieter and more effective.j.smith is a user, I can target John Smith with a specific phishing email regarding his File Browser account.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 is straightforward: Update to File Browser v2.55.0.
If you cannot update immediately, you must rely on perimeter defenses:
/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.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
File Browser File Browser | < 2.55.0 | 2.55.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-208 |
| Attack Vector | Network |
| CVSS | 5.3 (Medium) |
| EPSS Score | 0.0009 |
| Impact | Information Disclosure (Username Enumeration) |
| Exploit Status | PoC Available |
Observable Timing Discrepancy
An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.
CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.
CVE-2026-113872 (GHSA-q7qp-rjg7-q26m) is a high-severity denial-of-service vulnerability affecting the multer multipart/form-data upload middleware for Node.js and Express in versions 2.0.0 through 2.4.0. When an endpoint is configured to accept multiple files using an asynchronous fileFilter callback, a crafted, truncated multipart HTTP request can trigger an unhandled error event on the underlying busboy stream parser after request teardown has already completed. Because all error listeners are stripped during teardown, Node.js treats the resulting error event as an uncaught exception and immediately terminates the server process.
CVE-2026-115897 (also tracked as GHSA-m5mm-p8h7-m29v and aliased as CVE-2026-115754) is a Cross-Site Scripting (XSS) vulnerability in the Yahoo serialize-javascript npm package prior to version 7.1.3. The flaw exists in the regular expression used by escapeFunctionBody() to neutralize HTML script closing tags within serialized JavaScript functions. Because function bodies are sanitized in isolation before structural formatting is applied, a function whose string representation terminates directly with the sequence '</script' bypasses the positive lookahead check. When combined with serialization formatting options that introduce whitespace delimiters and a subsequent property that supplies a closing bracket, an attacker can prematurely terminate an enclosing HTML <script> block and inject arbitrary HTML or JavaScript into the rendered document.
CVE-2026-109841 is a Subresource Integrity (SRI) verification bypass vulnerability in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js that powers the global fetch() implementation. The flaw stems from improper handling of case sensitivity (CWE-178) when validating cryptographic hash algorithm tokens within SRI metadata. Because undici compared incoming algorithm identifiers against a strictly lowercase set without prior ASCII-lowercase normalization, valid uppercase or mixed-case tokens such as SHA256- or Sha384- were treated as unrecognized algorithms and discarded. Under SRI forward-compatibility rules, discarding all provided tokens results in an empty integrity list, causing undici to fail open and accept tampered HTTP response bodies without cryptographic verification.
A resource allocation vulnerability (CWE-770) exists in the Server-Sent Events (SSE) parser within the Node.js undici HTTP client library. In affected versions, the EventSourceStream implementation fails to restrict the count of internal Buffer chunk references and accumulated string nodes prior to event dispatch boundaries. A malicious or compromised SSE endpoint can exploit this behavior by streaming fragmented, unterminated lines or continuous sequences of short data fields without empty separator lines, bypassing configured byte-size limits and causing a fatal Out-of-Memory (OOM) crash in the client Node.js process.