Ghost in the Machine: Vert.x Cache Poisoning DoS
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:
- Target: A file at
http://target.com/assets/style.css. - The Payload: The attacker requests
http://target.com/assets/nonexistent%2F..%2Fstyle.css.- Note the encoded slash
%2F. This trick causes the router to treatnonexistent/../as part of the filename initially.
- Note the encoded slash
- The Glitch: The
removeDotsfunction fails to strip the segment correctly due to the bug described above. - The Lookup: Vert.x tries to find a file literally named with the malformed path. It obviously doesn't exist.
- The Poison: Vert.x generates a 404 response. Crucially, it caches this 404 under a key that collides with or masks the legitimate
style.cssentry 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:
- The cache entry expires (time-to-live).
- The cache fills up and evicts the entry.
- 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 safetyThis 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.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:LAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Eclipse Vert.x Eclipse Foundation | >= 4.0.0, <= 4.5.23 | 4.5.24 |
Eclipse Vert.x Eclipse Foundation | >= 5.0.0, <= 5.0.6 | 5.0.7 |
| Attribute | Detail |
|---|---|
| CWE | CWE-444 (Inconsistent Interpretation of HTTP Requests) |
| CVSS v4.0 | 6.9 (Medium) |
| Attack Vector | Network (Unauthenticated) |
| Impact | Denial of Service (Cache Poisoning) |
| Fix Commit | d007e7b418543eb1567fe95cf20f5450a5c2d047 |
| Protocol | HTTP/1.1, HTTP/2 |
MITRE ATT&CK Mapping
The application interprets HTTP requests inconsistently compared to intermediaries or standards, leading to cache poisoning or request smuggling.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.