Claude + Humans vs nginx: CVE-2026-27654 What humans still do when the Claude already found the bug. Calif Apr 10, 2026 1 Share We'd like to acknowledge Claude, Anthropic Research, NGINX developers and F5 PSIRT for partnering with us on this. It was a pleasant experience. By now we know AI can find real vulnerabilities and write working exploits . That part is no longer surprising. The more interesting question is the human role. Where does human expertise still matter when the initial bug report is already correct? What separates a crash from a real exploit? What does collaboration look like in practice, on a real vulnerability with a real fix and a real disclosure? CVE-2026-27654 is a useful case. The bug needs a non-default config: ngx_http_dav_module compiled in, and a location combining alias with dav_methods COPY or MOVE . The exposed population is small. Inside that population the bug is severe. Claude flagged it correctly: a heap buffer overflow in ngx_http_dav_copy_move_handler() , driven by an unsigned underflow in ngx_http_map_uri_to_path() when the Destination header is shorter than the location prefix. Claude provided a working crash: COPY /dav/x HTTP/1.1 Host: localhost Destination: /da <-- shorter than "/dav/" -> underflow That crashes a worker. Whether it can do more than that is a harder question, and at least for now, answering it takes a human. What it does, when it works: it escapes the WebDAV root. The alias directive is supposed to be a jail; a COPY against location /dav/ { alias /var/dav/uploads/; } should only ever touch files under /var/dav/uploads/ . The bug lets a remote attacker read or write files anywhere the worker UID can reach. Three of us worked through this with Claude independently, each in our own session, comparing notes between rounds. The independence mattered: the same prompt to two different Claude conversations produced one "impossible" and one working exploit (more on that under Round two). The first exploit out of the gate was a clean repro we could ship to F5; the refinements that followed came from looking at what each of us had built and asking which precondition felt least likely to exist on a real target. Round one: aim high ( PoC-1 ). Arbitrary file write with attacker-chosen content. PUT a webshell under the WebDAV root, then trigger the overflow on COPY to copy it to /var/www/html/x.php . Claude built it; it worked. But the heap groom needs the source-path buffer pushed into a separate malloc() block, which means a request URI over 4000 characters, which means the PUT must land in a directory tree twenty levels deep with ~200-character folder names. nginx builds that tree if you set create_full_put_path on , but "the server accepts arbitrarily long PUT paths" is not a precondition you find often. Round two: give up on write ( PoC-2 ). The question we put to Claude: We don't actually need to write our own bytes. If we control both the source and the destination of the COPY, can we copy a file that already exists, like /etc/passwd , into a download folder we can fetch it from? Two of us asked independently. One Claude said it was impossible. The other produced a working exploit first try: a single COPY, short URI so the source path stays in the request pool adjacent to the destination, and the same overflow rewrites both paths at once. That became PoC-2. The first thing we tested after it worked was whether it was as clean as it looked. The draft of this writeup said the worker "never crashes." This is not true, right? Because the second PoC did crash workers if memcpy didn't hit that lucky condition. It hadn't checked. We made it sweep all 16 alignment residues; two of them crash before any file is touched. The "never" became "on 14 of 16 alignments." Then the constraint. The traversal injected into the source path is 20 characters, fixed by the header structure. Claude's first count of how those 20 split was wrong: With a 3-level surviving prefix you spend 12 characters on /../../../ and have 8 left for the filename. Is this a correct assessment? It wasn't. /../../../ is 10, not 12; etc/passwd is 10, not 8. (Note to self: never ask Claude to file our tax returns.) Ten and ten, and etc/passwd fits exactly. We asked whether the constraint itself could be stretched and the answer was: not by changing the URI length (both endpoints of the controlled window shift together), but yes by tuning the header-key lengths, which we ended up doing in ยง6.3. Round three happened while we were writing this document ( slash-padding variant ). We were fact-checking why the deep PUT tree in PoC-1 is unavoidable, and the chain went like this: Can you do something like this to artificially expand the length? COPY /etc/../etc/../etc/../etc/../passwd HTTP/1.1 No. nginx normalizes .. before r->uri.len is set; the padding gets stripped. Does it also normalize the source path in COPY <source_path> ? We want a long source-path string to push it into its own malloc, but at the same t...
CVE-2026-27654 is a high-severity (CVSS 8.2) heap buffer overflow in nginx's `ngx_http_dav_module`, exploitable via a crafted HTTP COPY or MOVE request with a specific `Destination` header that triggers an unsigned underflow, potentially allowing file read/write outside the WebDAV root. The vulnerability affects F5 NGINX Plus versions r32 and r33, and is fixed in nginx versions 1.28.3 and 1.29.7. Exploitation requires a non-default configuration where the `ngx_http_dav_module` is compiled in and a location block combines `alias` with `dav_methods`.