CVE-2026-1002

Ghost in the Machine: Vert.x Cache Poisoning DoS

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 16, 2026·6 min read

Executive Summary (TL;DR)

By sending a specifically crafted URL containing encoded dot segments, an attacker can trick the Vert.x web server into caching a '404 Not Found' response for a legitimate file. Subsequent requests from valid users will receive the cached 404, effectively making the file vanish from the web until the cache expires or the server restarts.

A logic error in URI normalization allows unauthenticated attackers to poison the Vert.x StaticHandler cache, causing persistent Denial of Service for legitimate files.

The Hook: The High-Performance Trap

Eclipse Vert.x is the darling of the Java reactive world. It's fast, non-blocking, and designed to handle massive concurrency without breaking a sweat. Developers love it because it feels like driving a Ferrari compared to the minivan that is a traditional servlet container.

But here is the thing about high-performance machinery: the tighter you tune the engine, the more catastrophic a loose bolt becomes. In this case, the loose bolt wasn't in the complex event loop or the networking stack. It was in the most boring place imaginable: the StaticHandler, the component responsible for serving your CSS, JS, and HTML files.

This vulnerability isn't about crashing the server with a buffer overflow. It's more subtle and arguably more annoying. It's a cache poisoning attack that allows an attacker to tell the server, "Hey, this file doesn't exist," and the server believes it so hard that it refuses to serve the file to anyone else. It is like convincing a library that a book is missing so they delete the catalogue entry while the book is sitting right on the shelf.

The Flaw: Reading RFCs is Hard

The root cause lies in how Vert.x handles URI normalization. When a web server receives a request for /foo/../bar, it needs to simplify that path to /bar before looking it up on the disk. This logic is governed by RFC 3986, specifically the "Remove Dot Segments" algorithm (Section 5.2.4).

RFCs are notoriously dry, and implementing them is like trying to follow IKEA instructions written in ancient Sumerian. Vert.x implemented a utility called HttpUtils.removeDots to handle this. One specific rule (Rule C) states that if a path segment ends in .., you should remove that segment and the one preceding it.

The flaw was a classic off-by-one logic error. The algorithm tries to find the previous path segment to delete it. Usually, segments are separated by slashes (/). The vulnerable code looked for the last slash in the output buffer to decide what to delete. But what happens if you are at the very start of the buffer and there is no preceding slash?

[!NOTE] The code assumed a slash always existed to demarcate segments. When that assumption failed, the code did... absolutely nothing. It skipped the deletion entirely.

The Code: The One-Line Fix

Let's look at the smoking gun in io.vertx.core.http.impl.HttpUtils.java. This is where the cleanup happens. The developer wanted to remove the last segment from obuf (the output buffer).

Here is the vulnerable code:

int pos = obuf.lastIndexOf("/");
if (pos != -1) {
    // If we found a slash, delete from there to the end
    obuf.delete(pos, obuf.length());
}
// If pos WAS -1, we did nothing. Oops.

If obuf contained segment (no slash), pos returns -1. The if block is skipped. The dot segment isn't removed. The path remains dirty.

Here is the fix introduced in commit d007e7b418543eb1567fe95cf20f5450a5c2d047:

int pos = obuf.lastIndexOf("/");
// If pos is -1, it means the whole buffer is the segment to remove.
// So set length to 0. Otherwise, set it to the slash position.
obuf.setLength(pos == -1 ? 0 : pos);

This tiny change—handling the -1 case by clearing the buffer—closes the loophole. It ensures that even the first segment in a path gets nuked if a dot-dot follows it.

The Exploit: Weaponizing the Cache

So how do we turn a path normalization bug into a Denial of Service? We use the StaticHandler's caching mechanism against itself. By default, Vert.x caches the results of file lookups to speed up responses. This includes caching the fact that a file was not found (404).

The attack vector relies on sending a URI that confuses the normalizer but is technically valid enough to pass through the router.

The Attack Chain:

  1. Target: A file at http://target.com/assets/style.css.
  2. The Payload: The attacker requests http://target.com/assets/nonexistent%2F..%2Fstyle.css.
    • Note the encoded slash %2F. This trick causes the router to treat nonexistent/../ as part of the filename initially.
  3. The Glitch: The removeDots function fails to strip the segment correctly due to the bug described above.
  4. The Lookup: Vert.x tries to find a file literally named with the malformed path. It obviously doesn't exist.
  5. The Poison: Vert.x generates a 404 response. Crucially, it caches this 404 under a key that collides with or masks the legitimate style.css entry due to the normalization discrepancy.

Now, when a legitimate user requests http://target.com/assets/style.css, the cache intercepts the request and says: "Oh, I remember this one! It doesn't exist." The user gets a 404.

The Impact: Persistent Annoyance

Let's be clear: this isn't Remote Code Execution (RCE). We aren't popping shells or stealing database credentials. However, in a business context, availability is security.

Imagine an e-commerce site where the checkout.js file suddenly returns a 404 for every customer. The backend is fine, the database is fine, but the frontend is broken. No one can buy anything.

The persistence is the killer here. In many DoS attacks, you stop the traffic, and the service recovers. Here, the service is "poisoned." The 404 remains in the Local Least Recently Used (LRU) cache until:

  1. The cache entry expires (time-to-live).
  2. The cache fills up and evicts the entry.
  3. The administrator restarts the service.

An attacker needs to send only one packet per file to take it offline. It is extremely efficient.

The Fix: Update or Degrade

The remediation is straightforward. The Eclipse team patched this in Vert.x versions 4.5.24 and 5.0.7. If you are running anything older, your static assets are sitting ducks.

If you cannot update immediately because of "enterprise reasons" (we know the drill), you can apply a workaround. You must disable caching in the StaticHandler.

// The "I can't patch yet" workaround
StaticHandler handler = StaticHandler.create()
    .setCachingEnabled(false); // Goodbye performance, hello safety

This stops the bleeding by preventing the bad 404 result from sticking, but it turns your high-performance event loop into a dumb file reader that hits the disk for every single request. Patching is highly recommended.

Fix Analysis (1)

Technical Appendix

CVSS Score
6.9/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:L

Affected Systems

Vert.x Web 4.0.0 through 4.5.23Vert.x Web 5.0.0 through 5.0.6

Affected Versions Detail

Product
Affected Versions
Fixed Version
Eclipse Vert.x
Eclipse Foundation
>= 4.0.0, <= 4.5.234.5.24
Eclipse Vert.x
Eclipse Foundation
>= 5.0.0, <= 5.0.65.0.7
AttributeDetail
CWECWE-444 (Inconsistent Interpretation of HTTP Requests)
CVSS v4.06.9 (Medium)
Attack VectorNetwork (Unauthenticated)
ImpactDenial of Service (Cache Poisoning)
Fix Commitd007e7b418543eb1567fe95cf20f5450a5c2d047
ProtocolHTTP/1.1, HTTP/2
CWE-444
Inconsistent Interpretation of HTTP Requests

The application interprets HTTP requests inconsistently compared to intermediaries or standards, leading to cache poisoning or request smuggling.

Vulnerability Timeline

CVE Published
2026-01-15
Fix Merged to Core
2026-01-14

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.