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-26185

Clockwatching: Enumerating Directus Users via Timing Side-Channels

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 12, 2026·6 min read·157 visits

Executive Summary (TL;DR)

Directus implemented a 'stall' mechanism to hide whether a user exists during password resets. However, they validated the 'reset_url' parameter *after* the user lookup but *before* the stall for existing users. This created a 500ms timing discrepancy: existing users return an error immediately (fast), while non-existing users trigger the artificial delay (slow).

A logic error in the Directus password reset flow allows attackers to enumerate valid email addresses by measuring server response times. By manipulating the 'reset_url' parameter, attackers can bypass the application's anti-enumeration timing protections.

The Hook: When Security Features Backfire

There is a special place in hell reserved for timing attacks. They are subtle, statistical, and often born from the very code intended to prevent them. CVE-2026-26185 is a classic example of this irony, affecting Directus, the popular headless CMS.

Directus developers, being security-conscious folks, knew that password reset forms are a goldmine for attackers. If you type in an email and the server says "Email sent!" versus "User not found," you've just handed the attacker a valid username. To combat this, they implemented a stall() function—a utility designed to force all requests to take a standardized amount of time (500ms), masking the database lookup speed.

But here's the catch: implementing a constant-time response is like trying to carry water in a sieve. You have to plug every hole. In this case, they missed one logic branch, turning their shield into a magnifying glass for attackers.

The Flaw: A Matter of Order

The vulnerability lies in the UsersService within the @directus/api package. Specifically, the requestPasswordReset method. The goal of this function is simple: look up a user by email and send them a reset link. If the user doesn't exist, the system waits out the stall timer and throws a generic error. If the user does exist, it proceeds to validate parameters.

The flaw was purely chronological. The code performed the expensive operation (database lookup) before validating the input reset_url.

Here is the logic flow in the vulnerable version:

  1. Start Timer.
  2. Database Lookup: Check if email exists.
  3. If User Missing: Wait 500ms (Stall), then throw Forbidden.
  4. If User Exists: Check if reset_url is in the allow-list. If not, throw InvalidPayload immediately.

Do you see the problem? If I provide a valid email but a garbage URL, the code skips the stall and errors out instantly. If I provide an invalid email and a garbage URL, the code enters the If User Missing block and sleeps for half a second. That 500ms delta is the sound of the server leaking data.

The Code: The Smoking Gun

Let's look at the TypeScript source. This is the diff from commit e69aa7a5248c6e3e822cb1ac354dee295df90b2a. It perfectly illustrates how a simple swap of lines closes the side channel.

Vulnerable Code (Before):

// 1. Start the clock
const timeStart = performance.now();
 
// 2. Look for the user (The leakage source)
const user = await this.getUserByEmail(email);
 
// 3. If no user, STALL then throw
if (user?.status !== 'active') {
    await stall(STALL_TIME, timeStart);
    throw new ForbiddenError();
}
 
// 4. If user exists, check URL. 
// OOPS: This throws immediately, bypassing the stall logic above.
if (url && isUrlAllowed(url, ...) === false) {
    throw new InvalidPayloadError(...);
}

Fixed Code (After):

const timeStart = performance.now();
 
// 1. Check the URL FIRST. 
// If it's bad, fail fast for EVERYONE. 
if (url && isUrlAllowed(url, ...) === false) {
    throw new InvalidPayloadError(...);
}
 
// 2. Now look for the user
const user = await this.getUserByEmail(email);
 
// 3. Standardize timing
if (user?.status !== 'active') {
    await stall(STALL_TIME, timeStart);
    throw new ForbiddenError();
}

By moving the URL validation to the top, the application ensures that a bad reset_url results in a fast rejection regardless of whether the email is in the database. The side channel is closed because the response path is now identical for both valid and invalid users when the URL is malicious.

The Exploit: Measuring the Pulse

Exploiting this does not require complex memory corruption or packet injection. It requires a stopwatch. An attacker can write a simple Python script to enumerate users.

The Attack Chain:

  1. Setup: Identify a Directus instance (e.g., via the Wappalyzer browser extension or looking for /admin endpoints).
  2. Payload Construction: Create a POST request to /auth/password/request.
    • email: The target email you want to verify (e.g., admin@target.com).
    • reset_url: A known blocked URL or just http://evil.com (assuming the server enforces an allow-list).
  3. Execution: Send the request and measure the time to first byte (TTFB).

The Heuristic:

  • ~50ms Response (400 Bad Request): "Jackpot." The user exists. The code hit the database, found the user, hit the URL check, and bailed out early.
  • ~500ms Response (403 Forbidden): "Miss." The user does not exist. The code hit the database, found nothing, and sat in the stall() penalty box for half a second.

This isn't theoretical. With a 500ms difference, you don't even need statistical averaging. A single request is often enough to confirm existence.

The Impact: Why It Matters

Is this going to bring down the banking system? No. But in the reconnaissance phase of an attack, user enumeration is incredibly valuable.

Knowing valid usernames allows an attacker to:

  1. Launch Targeted Phishing: If I know j.doe@company.com is a valid admin, I can craft a specific spear-phishing email targeting them.
  2. Credential Stuffing: Instead of spraying a million passwords at a million potential emails, I can spray 100 common passwords at the 50 emails I know exist. This drastically reduces the noise generated in logs and increases the success rate.
  3. Bypass Lockouts: Attackers can specifically target valid accounts to trigger account lockouts (Denial of Service) without wasting requests on dead accounts.

For a platform like Directus, which often manages critical data for apps and websites, exposing the list of administrators is a significant breach of privacy.

The Fix & Mitigation

The fix is straightforward: Update to Directus v11.14.1 or @directus/api v32.2.0.

If you cannot update immediately, you are in a tight spot. You could technically try to ensure your PASSWORD_RESET_URL_ALLOW_LIST is empty or extremely permissive to prevent the validation error from triggering, but that introduces other security risks (like Host Header injection or open redirects).

The only real cure is the patch. This vulnerability serves as a reminder for developers: Input validation should always happen before resource-intensive or sensitive operations. Validate your inputs at the door, not after you've already invited them into the living room.

Official Patches

DirectusGitHub Commit Fix

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

Affected Systems

Directus < 11.14.1@directus/api < 32.2.0

Affected Versions Detail

Product
Affected Versions
Fixed Version
Directus
Directus
< 11.14.111.14.1
@directus/api
Directus
< 32.2.032.2.0
AttributeDetail
CWE IDCWE-203 (Observable Discrepancy)
Attack VectorNetwork
CVSS5.3 (Medium)
ImpactInformation Disclosure (User Enumeration)
Exploit StatusProof of Concept (Trivial)
Affected ComponentUsersService.requestPasswordReset

MITRE ATT&CK Mapping

T1589.002Gather Victim Identity Information: Email Addresses
Reconnaissance
T1110Brute Force
Credential Access
CWE-203
Observable Discrepancy

The product behaves differently or sends different responses depending on whether a user account exists, which allows an attacker to enumerate valid usernames.

Known Exploits & Detection

Internal ResearchTiming-based enumeration using Python requests library to measure response delta.

Vulnerability Timeline

Fix committed to repository
2026-01-14
GitHub Advisory Published
2026-02-12
CVE Assigned
2026-02-12

References & Sources

  • [1]GHSA-jr94-gj3h-c8rf
  • [2]NVD - CVE-2026-26185

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.

More Reports

•23 minutes ago•GHSA-7CX2-G3H9-382P
8.1

GHSA-7CX2-G3H9-382P: Multiple Vulnerabilities in Crawl4AI Docker API (Arbitrary File Write, SSRF, CRLF Log Injection)

An in-depth technical analysis of multiple security vulnerabilities in the self-hosted Docker API server of Crawl4AI up to version 0.8.7. These flaws include a critical arbitrary file write via symlink traversal and TOCTOU weakness, CRLF log injection, webhook header injection, and SSRF filter gaps. These have been remediated in version 0.8.8.

Alon Barad
Alon Barad
0 views•6 min read
•about 1 hour ago•GHSA-F989-C77F-R2CQ
8.2

GHSA-f989-c77f-r2cq: LLM Credential Exfiltration and SSRF in Crawl4AI Docker Server

A technical evaluation of the Crawl4AI open-source web crawling and scraping library revealed a high-severity credential exfiltration vulnerability in its self-hosted Dockerized API server. The flaw arises from an unvalidated base_url parameter in request payloads and a dynamic prefix resolution mechanism that retrieves system environment variables. Unauthenticated remote attackers can leverage these features in tandem to extract host-level secrets or redirect configured LLM API keys to an external listener under their control.

Amit Schendel
Amit Schendel
2 views•6 min read
•about 1 hour ago•GHSA-365W-HQF6-VXFG
9.8

GHSA-365w-hqf6-vxfg: Multiple Critical Vulnerabilities in Crawl4AI Docker API Server

The Crawl4AI Docker API server, in versions 0.8.6 and prior, contains multiple critical vulnerabilities including improper path sanitization, missing authentication on administration routes, hardcoded JWT secrets, and SSRF. These vulnerabilities allow remote, unauthenticated attackers to write arbitrary files, execute arbitrary code, and pivot into private cloud environments.

Amit Schendel
Amit Schendel
5 views•7 min read
•about 4 hours ago•GHSA-534H-C3CW-V3H9
5.5

GHSA-534h-c3cw-v3h9: Local Information Disclosure via Abstract-Namespace Socket in Nuxt Dev Server

A local security vulnerability in the Nuxt development server (nuxt dev) allows local unprivileged users to access sensitive configuration files and source code. On Linux environments running Node.js 20+, Nuxt bound its internal vite-node IPC server to an abstract-namespace Unix socket without any peer authentication, enabling co-resident local users to connect and request module code directly.

Amit Schendel
Amit Schendel
4 views•5 min read
•about 5 hours ago•GHSA-8RFP-98V4-MMR6
0.0

GHSA-8RFP-98V4-MMR6: Protocol-Filtering Bypass via Unicode Obfuscation in Mozilla Bleach

Mozilla Bleach is an open-source HTML sanitizing library for Python. Versions up to and including 6.3.0 contain an incomplete filtering implementation in the URI validation logic ('sanitize_uri_value'). This logic fails to detect disallowed protocols, such as 'javascript:', if they contain Unicode invisible characters, whitespace characters, or characters with a code point greater than U+00A0. While standard-compliant web browsers do not directly execute invalid URI schemes containing these non-standard characters, downstream systems that normalize Unicode text by stripping invisible or non-ASCII characters can unintentionally reactivate the 'javascript:' prefix, causing Cross-Site Scripting (XSS). Additionally, this behavior violates Bleach's core sanitization contract by outputting URIs that bypass protocol allowlists configured by the caller.

Amit Schendel
Amit Schendel
4 views•7 min read
•about 5 hours ago•GHSA-G75F-G53V-794X
4.3

GHSA-G75F-G53V-794X: CPU Exhaustion via Unbounded Email Regular Expression Scanning in Bleach

An uncontrolled resource consumption vulnerability exists in the Python package Bleach when parsing text to linkify email addresses. When `parse_email=True` is enabled, the regular expression engine is forced into a quadratic-time complexity scan on specially crafted payloads lacking an '@' symbol. This causes immediate CPU exhaustion and blocks application server worker processes.

Amit Schendel
Amit Schendel
4 views•6 min read