# REPRO-2026-00171: nginx WebDAV: heap-buffer-overflow in COPY/MOVE with alias directive
## Summary
Status: published
Severity: high
Type: security
Confidence: Unknown
## Identifiers
REPRO ID: REPRO-2026-00171
CVE: CVE-2026-27654
## Package
Name: nginx
Ecosystem: source
Affected: OSS 0.5.13–0.9.7, 1.0.0–1.28.2, 1.29.0–1.29.6; Plus R32–R36
Fixed: Unknown
## Root Cause
# RCA Report: CVE-2026-27654 — nginx WebDAV COPY/MOVE heap-buffer-overflow with alias directive
## Summary
CVE-2026-27654 is a heap-based buffer overflow in nginx's `ngx_http_dav_module` that triggers when processing WebDAV COPY or MOVE requests under a location that uses the `alias` directive. The vulnerability stems from an integer underflow in `ngx_http_map_uri_to_path()`: when the destination URI (`duri`) is shorter than the location prefix length (`clcf->alias`), the buffer size calculation `clcf->root.len + r->uri.len - alias + 1` underflows as an unsigned `size_t`, resulting in a near-zero or wrapped allocation. The subsequent `memcpy` of the alias root path and destination URI then overflows the tiny heap buffer. This is reachable unauthenticated by any client that can send a WebDAV COPY/MOVE request to an nginx instance with `dav_methods` enabled under an aliased location.
## Impact
- **Package/component affected**: nginx Open Source and Plus, `ngx_http_dav_module`
- **Affected versions**: `0.5.13–0.9.7`, `1.0.0–1.28.2`, `1.29.0–1.29.6`
- **Fixed versions**: `1.28.3` (stable), `1.29.7` (mainline)
- **Risk level**: High (CVSS 3.1: 8.2, CVSS 4.0: 8.8)
- **Consequences**: Worker process crash (DoS), potential arbitrary file write outside the intended directory, and possible escalation to RCE depending on heap allocator state.
## Root Cause
The bug is located in `ngx_http_dav_copy_move_handler()` (`src/http/modules/ngx_http_dav_module.c`). Before computing the destination filesystem path, the handler temporarily overwrites `r->uri` with the parsed `Destination` header URI (`duri`) and calls `ngx_http_map_uri_to_path()`. That function computes the required buffer length with:
```c
path->len = clcf->root.len + reserved + r->uri.len - alias + 1;
```
All operands are unsigned `size_t`. When `alias` (which stores the location prefix length) is larger than `clcf->root.len + r->uri.len + 1`, the subtraction wraps around. For example, with `location /davvvv/` (alias = 7) and `alias /a/` (root.len = 3), a destination URI of `/xx` (len = 2) produces:
`3 + 2 - 7 + 1` → `5 - 7` wraps to `SIZE_MAX - 1`, then `+ 1` wraps back to `0`.
`ngx_pnalloc(pool, 0)` returns a tiny pointer from the nginx pool. The following `ngx_copy(path->data, clcf->root.data, 3)` writes 3 bytes into a zero-byte allocation, and the subsequent `ngx_copy(last, r->uri.data + alias, r->uri.len - alias)` passes a negative-size parameter (`-5`) to `memcpy`, causing a massive out-of-bounds read/write.
**Fix commit**: `9739e75` in the nginx repository (`Dav: destination length validation for COPY and MOVE.`). The patch adds an explicit check before the path mapping:
```c
if (clcf->alias && clcf->alias != NGX_MAX_SIZE_T_VALUE && duri.len < clcf->alias) {
return NGX_HTTP_BAD_REQUEST;
}
```
This rejects any COPY/MOVE request whose destination URI is shorter than the location prefix, preventing the underflow.
## Reproduction Steps
The reproduction is fully automated in `repro/reproduction_steps.sh`.
What the script does:
1. Clones the nginx repository (or reuses the existing clone).
2. Builds the vulnerable version (`release-1.29.6`) and the fixed version (`release-1.29.7`), both compiled with AddressSanitizer (`-fsanitize=address`).
3. Creates a minimal nginx configuration with a location `/davvvv/` that maps via `alias` to a short directory path, and enables `dav_methods COPY MOVE`.
4. Starts the **vulnerable** nginx binary in the foreground (`daemon off; master_process off;`), sends a `COPY` request with a deliberately short `Destination: http://127.0.0.1:8080/xx` header, and captures ASAN output via `ASAN_OPTIONS=log_path=...`.
5. Repeats the same request against the **fixed** nginx binary and captures the HTTP response.
6. Verifies that the vulnerable build crashes with an ASAN error while the fixed build returns HTTP `400 Bad Request` with no ASAN error.
**Expected evidence**:
- `logs/vulnerable_asan.txt` — ASAN report showing `negative-size-param: (size=-5)` inside `ngx_http_map_uri_to_path` called from `ngx_http_dav_copy_move_handler`.
- `logs/fixed_curl.txt` — HTTP `400 Bad Request` response from nginx/1.29.7.
## Evidence
### Vulnerable build (release-1.29.6)
ASAN log (`logs/vulnerable_asan.txt`):
```
==16927==ERROR: AddressSanitizer: negative-size-param: (size=-5)
#0 ... in memcpy
#1 ... in ngx_http_map_uri_to_path src/http/ngx_http_core_module.c:1987
#2 ... in ngx_http_dav_copy_move_handler src/http/modules/ngx_http_dav_module.c:703
#3 ... in ngx_http_dav_handler src/http/modules/ngx_http_dav_module.c:194
#4 ... in ngx_http_core_content_phase src/http/ngx_http_core_module.c:1282
...
SUMMARY: AddressSanitizer: negative-size-param ... in memcpy
```
The stack trace confirms the crash occurs inside `ngx_http_map_uri_to_path` while handling the COPY request in the DAV module.
### Fixed build (release-1.29.7)
HTTP response (`logs/fixed_curl.txt`):
```
400 Bad Request
400 Bad Request
nginx/1.29.7
HTTP_CODE:400
```
The fixed binary rejects the malicious request with a `400 Bad Request` and logs:
```
client sent invalid "Destination" header: "http://127.0.0.1:8080/xx"
```
### Environment
- OS: Linux 6.18.5 x86_64
- Compiler: GCC 13.3.0
- ASAN flags: `-fsanitize=address -g -O1 -fno-omit-frame-pointer`
- nginx configure: `--with-http_dav_module`
## Recommendations / Next Steps
1. **Upgrade immediately** to nginx `1.28.3` (stable) or `1.29.7` (mainline) or later.
2. **Mitigation** (if upgrading is not immediately possible): disable WebDAV COPY and MOVE methods, or avoid using the `alias` directive in locations that expose DAV methods.
3. **Regression testing**: any future changes to `ngx_http_dav_copy_move_handler` or `ngx_http_map_uri_to_path` should include a test case with a destination URI shorter than the location prefix under an aliased location.
4. **Code review**: audit other callers of `ngx_http_map_uri_to_path` that temporarily modify `r->uri` to ensure similar length validations are in place.
## Additional Notes
- **Idempotency**: `repro/reproduction_steps.sh` has been executed twice consecutively on a clean environment and produced the same confirmed verdict both times.
- **Edge cases**: The trigger requires a specific relationship between the alias path length, the location prefix length, and the destination URI length. The script uses `location /davvvv/` (len 7) + `alias /a/` (len 3) + destination `/xx` (len 2) to force the underflow to wrap to exactly `0`, which maximizes the chance of a detectable heap overflow. Other combinations (e.g., longer destination URIs that still underflow to small non-zero values) are also exploitable.
- **Limitations**: The reproduction requires building nginx from source with ASAN, which takes several minutes. The script caches the built binaries in `$ROOT/builds/` to avoid repeated compilation.
## Reproduction Details
Reproduced: 2026-05-28T12:07:11.635Z
Duration: 1301 seconds
Tool calls: 173
Turns: 151
Handoffs: 3
## Quick Verification
Run one of these commands to verify locally:
pruva-verify REPRO-2026-00171
pruva-verify CVE-2026-27654
Or open in GitHub Codespaces (zero-friction, auto-runs):
https://github.com/codespaces/new?ref=repro/REPRO-2026-00171&repo=N3mes1s/pruva-sandbox
Or download and run the script manually:
curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00171/artifacts/bundle/repro/reproduction_steps.sh
chmod +x reproduction_steps.sh
./reproduction_steps.sh
WARNING: Run in a sandboxed environment. This exploits a real vulnerability.
## References
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-27654
## Artifacts
- bundle/repro/rca_report.md (analysis, 7003 bytes)
- bundle/repro/reproduction_steps.sh (reproduction_script, 4887 bytes)
- bundle/vuln_variant/rca_report.md (analysis, 8209 bytes)
- bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 7112 bytes)
- bundle/context.json (other, 2544 bytes)
- bundle/metadata.json (other, 655 bytes)
- bundle/ticket.md (ticket, 5114 bytes)
- bundle/repro/validation_verdict.json (other, 1180 bytes)
- bundle/repro/nginx.conf (other, 476 bytes)
- bundle/vuln_variant/root_cause_equivalence.json (other, 1378 bytes)
- bundle/vuln_variant/patch_analysis.md (documentation, 4701 bytes)
- bundle/vuln_variant/variant_manifest.json (other, 3214 bytes)
- bundle/vuln_variant/runtime_manifest.json (other, 2583 bytes)
- bundle/vuln_variant/validation_verdict.json (other, 1422 bytes)
- bundle/logs/vuln_prefix_curl_err.txt (other, 0 bytes)
- bundle/logs/vuln_exact_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_prefix_curl_err.txt (other, 0 bytes)
- bundle/logs/fixed_stderr.txt (other, 0 bytes)
- bundle/logs/vuln_urlenc_curl.txt (other, 0 bytes)
- bundle/logs/vuln_nested_curl_err.txt (other, 0 bytes)
- bundle/logs/test_stderr.txt (other, 0 bytes)
- bundle/logs/fixed_prefix_stderr.txt (other, 0 bytes)
- bundle/logs/fixed_nested_curl_err.txt (other, 0 bytes)
- bundle/logs/vulnerable_asan.txt (other, 3262 bytes)
- bundle/logs/fixed_script_curl.txt (other, 172 bytes)
- bundle/logs/vuln_prefix.18268 (other, 3262 bytes)
- bundle/logs/vuln_move_stderr.txt (other, 0 bytes)
- bundle/logs/fixed_exact_curl.txt (other, 172 bytes)
- bundle/logs/fixed_nested_stdout.txt (other, 0 bytes)
- bundle/logs/asan_vulnerable.17016 (other, 3262 bytes)
- bundle/logs/variant_final.log (log, 1116 bytes)
- bundle/logs/vuln_exact_curl_err.txt (other, 0 bytes)
- bundle/logs/fixed_curl_err.txt (other, 0 bytes)
- bundle/logs/fixed_urlenc_stderr.txt (other, 0 bytes)
- bundle/logs/fixed_script_curl_err.txt (other, 0 bytes)
- bundle/logs/vuln_script_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_nested_stderr.txt (other, 0 bytes)
- bundle/logs/fixed_move_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_script_stdout.txt (other, 0 bytes)
- bundle/logs/vuln_move_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_move_curl_err.txt (other, 0 bytes)
- bundle/logs/error.log (log, 8781 bytes)
- bundle/logs/vuln_script_curl.txt (other, 0 bytes)
- bundle/logs/fixed_script_stderr.txt (other, 0 bytes)
- bundle/logs/vuln_prefix_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_move_stderr.txt (other, 0 bytes)
- bundle/logs/vuln_move.18292 (other, 3262 bytes)
- bundle/logs/fixed_nested_curl.txt (other, 172 bytes)
- bundle/logs/vuln_script.18286 (other, 3262 bytes)
- bundle/logs/vuln_script_curl_err.txt (other, 0 bytes)
- bundle/logs/vuln_nested_stdout.txt (other, 0 bytes)
- bundle/logs/vulnerable_curl_err.txt (other, 0 bytes)
- bundle/logs/fixed_exact_stdout.txt (other, 0 bytes)
- bundle/logs/vuln_nested.18280 (other, 3262 bytes)
- bundle/logs/test_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_prefix_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_urlenc_curl_err.txt (other, 0 bytes)
- bundle/logs/vuln_nested_stderr.txt (other, 0 bytes)
- bundle/logs/access.log (log, 3380 bytes)
- bundle/logs/vuln_script_stderr.txt (other, 0 bytes)
- bundle/logs/vulnerable_stdout.txt (other, 0 bytes)
- bundle/logs/vuln_urlenc_stdout.txt (other, 0 bytes)
- bundle/logs/variant_tests.log (log, 76 bytes)
- bundle/logs/vuln_prefix_curl.txt (other, 0 bytes)
- bundle/logs/vuln_nested_curl.txt (other, 0 bytes)
- bundle/logs/fixed_move_curl.txt (other, 172 bytes)
- bundle/logs/vuln_move_curl_err.txt (other, 0 bytes)
- bundle/logs/vulnerable_curl.txt (other, 0 bytes)
- bundle/logs/test_curl_err.txt (other, 0 bytes)
- bundle/logs/test_curl.txt (other, 0 bytes)
- bundle/logs/vuln_urlenc_stderr.txt (other, 0 bytes)
- bundle/logs/vuln_move_curl.txt (other, 0 bytes)
- bundle/logs/fixed_exact_stderr.txt (other, 0 bytes)
- bundle/logs/nginx.pid (other, 6 bytes)
- bundle/logs/vuln_exact.18274 (other, 3262 bytes)
- bundle/logs/vuln_urlenc_curl_err.txt (other, 0 bytes)
- bundle/logs/fixed_urlenc_stdout.txt (other, 0 bytes)
- bundle/logs/fixed_curl.txt (other, 172 bytes)
- bundle/logs/vulnerable_stderr.txt (other, 0 bytes)
- bundle/logs/fixed_stdout.txt (other, 0 bytes)
- bundle/logs/vuln_exact_curl.txt (other, 0 bytes)
- bundle/logs/vuln_prefix_stderr.txt (other, 0 bytes)
- bundle/logs/vuln_exact_stderr.txt (other, 0 bytes)
- bundle/logs/vuln_urlenc.18298 (other, 3305 bytes)
- bundle/logs/fixed_exact_curl_err.txt (other, 0 bytes)
- bundle/logs/fixed_urlenc_curl.txt (other, 172 bytes)
- bundle/logs/fixed_prefix_curl.txt (other, 172 bytes)
## API Access
- JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00171
- Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00171/artifacts/bundle/repro/reproduction_steps.sh
- Web: https://pruva.dev/r/REPRO-2026-00171
## For AI Assistants
To help users with this reproduction:
1. The `pruva-verify` command is the simplest way to run the reproduction
2. The script downloads and runs automatically in an isolated temp directory
3. Always recommend running in a VM, container, or disposable environment
4. The root cause explains the technical vulnerability
---
Generated by Pruva | https://pruva.dev