WebDAV COPY/MOVE path containment and resource overwrite risk (improper path validation)

HIGH
nginx/nginx
Commit: f0a084645b8f
Affected: 1.29.0 - 1.29.6 (prior to the fix); fixed in 1.29.7
2026-05-10 07:16 UTC

Description

The commit adds validation in the WebDAV COPY/MOVE path handling to ensure that the source and destination do not refer to the same resource and that the destination is not a parent/ancestor of the source path. If a conflict is detected (same URI or a prefix relationship), the handler returns 403 Forbidden. This mitigates a vulnerability where mis-specified COPY/MOVE paths could corrupt or destroy files by overwriting or manipulating resources via path containment issues.

Proof of Concept

PoC (actionable steps): Assumptions: A DAV-enabled nginx instance at example.org has WebDAV enabled and contains a file at /dav/secret.txt. The Destination header is evaluated by the server during COPY/MOVE operations. 1) Copy to the same location (expected to be caught by the fix): Request: COPY /dav/secret.txt HTTP/1.1 Host: example.org Destination: http://example.org/dav/secret.txt Expected on vulnerable versions: The operation may proceed and could overwrite or otherwise mishandle the resource. After the fix, the server should respond with 403 Forbidden. 2) Copy into a parent/ancestor path (example of a prefix relationship): Request: COPY /dav/subdir/secret.txt HTTP/1.1 Host: example.org Destination: http://example.org/dav/subdir/ Expected on vulnerable versions: Depending on implementation details, this could lead to unintended containment/overwrite behavior. After the fix, the server should respond with 403 Forbidden. Notes: - Use actual WebDAV clients or HTTP tools that support the COPY method (e.g., curl -X COPY). - Ensure the test environment has appropriate backups and is isolated, as COPY/MOVE can overwrite or restructure resources.

Commit Details

Author: Sai Krishna Kumar Reddy Yadamakanti

Date: 2026-05-05 12:01 UTC

Message:

Dav: improved path validation for COPY and MOVE operations The COPY and MOVE handler did not validate whether source and destination paths referred to the same resource or a parent-child collection relationship, which could corrupt or destroy files. Now 403 is returned if paths match or one is a prefix of the other. Reported by Mufeed VH of Winfunc Research.

Triage Assessment

Vulnerability Type: Path traversal / Resource overwrite protection

Confidence: HIGH

Reasoning:

The commit adds validation to ensure COPY/MOVE source and destination do not refer to the same resource or to a parent collection, returning 403 in such cases. This mitigates a vulnerability where mis-specified paths could lead to overwriting, corrupting, or destroying files, i.e., a resource containment/overwrite risk in DAV operations.

Verification Assessment

Vulnerability Type: WebDAV COPY/MOVE path containment and resource overwrite risk (improper path validation)

Confidence: HIGH

Affected Versions: 1.29.0 - 1.29.6 (prior to the fix); fixed in 1.29.7

Code Diff

diff --git a/src/http/modules/ngx_http_dav_module.c b/src/http/modules/ngx_http_dav_module.c index 4619b139a2..dd960ca271 100644 --- a/src/http/modules/ngx_http_dav_module.c +++ b/src/http/modules/ngx_http_dav_module.c @@ -47,6 +47,9 @@ static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r, ngx_http_dav_loc_conf_t *dlcf); static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r); +static void ngx_http_dav_merge_slashes(ngx_str_t *path); +static ngx_int_t ngx_http_dav_validate_paths(ngx_http_request_t *r, + ngx_str_t *src, ngx_str_t *dst, ngx_uint_t slash, ngx_table_elt_t *dest); static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path); static ngx_int_t ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path); @@ -719,6 +722,9 @@ ngx_http_dav_copy_move_handler(ngx_http_request_t *r) r->uri = uri; + ngx_http_dav_merge_slashes(&path); + ngx_http_dav_merge_slashes(&copy.path); + copy.path.len--; /* omit "\0" */ if (copy.path.data[copy.path.len - 1] == '/') { @@ -733,6 +739,12 @@ ngx_http_dav_copy_move_handler(ngx_http_request_t *r) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http copy to: \"%s\"", copy.path.data); + if (ngx_http_dav_validate_paths(r, &path, &copy.path, slash, dest) + != NGX_OK) + { + return NGX_HTTP_FORBIDDEN; + } + if (ngx_link_info(copy.path.data, &fi) == NGX_FILE_ERROR) { err = ngx_errno; @@ -870,6 +882,65 @@ ngx_http_dav_copy_move_handler(ngx_http_request_t *r) } +static void +ngx_http_dav_merge_slashes(ngx_str_t *path) +{ + u_char *p, *q; + + p = path->data; + q = path->data; + + while (*p) { + *q++ = *p; + + if (*p++ == '/') { + while (*p == '/') { + p++; + } + } + } + + *q++ = '\0'; + path->len = q - path->data; +} + + +static ngx_int_t +ngx_http_dav_validate_paths(ngx_http_request_t *r, ngx_str_t *src, + ngx_str_t *dst, ngx_uint_t slash, ngx_table_elt_t *dest) +{ + size_t len; + + len = src->len - 1; + + if (len > 0 && src->data[len - 1] == '/') { + len--; + } + + if (len == dst->len && ngx_strncmp(src->data, dst->data, len) == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "both URI \"%V\" and \"Destination\" URI \"%V\" " + "point to the same location", + &r->uri, &dest->value); + return NGX_HTTP_FORBIDDEN; + } + + if (slash + && ngx_strncmp(src->data, dst->data, ngx_min(len, dst->len)) == 0 + && (len < dst->len + ? dst->data[len] == '/' + : src->data[dst->len] == '/')) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"%V\" could not be %Ved to collection \"%V\"", + &r->uri, &r->method_name, &dest->value); + return NGX_HTTP_FORBIDDEN; + } + + return NGX_OK; +} + + static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path) {
← Back to Alerts View on GitHub →