NGINXDAV模块缓冲区溢出漏洞 | CVE-2026-27654原理分析研究

张开发
2026/4/20 10:27:08 15 分钟阅读

分享文章

NGINXDAV模块缓冲区溢出漏洞 | CVE-2026-27654原理分析研究
0x0 背景介绍NGINX是一款高性能的HTTP和反向代理服务器ngx_http_dav_module模块用于处理WebDAV协议请求。该模块在处理MOVE或COPY方法时若location配置了alias指令且为前缀匹配存在缓冲区溢出漏洞。攻击者可通过构造恶意的Destination请求头导致 worker 进程崩溃或在document root之外修改文件名。0x1 环境搭建Ubuntu241.1-Ubuntu24Docker搭建配置:暂无其实配置下nginx.conf就行0x2 漏洞复现2.1 原理场景 A前缀 location alias dav_methods 开启 copy/move最小触发面示例受影响配置关键点location 为前缀匹配非正则并使用 alias同时 dav_methods 允许 copy move。server{listen8080;# 前缀 location非正则 aliaslocation /dav/{aliasD:/webdav_root/;# Windows 示例Linux 可用 /var/www/dav/dav_methods put delete mkcol copy move;create_full_put_path on;dav_access user:rw group:rw all:r;}}为什么这份配置会导致漏洞原则上alias 会让核心函数 ngx_http_map_uri_to_path() 在拼接路径时执行 r-uri.len - alias这样的计算alias 值来源于 location 名称长度WebDAV 的 COPY/MOVE 会把 Destination 解析成新的 r-uri然后复用同一套路径映射逻辑去生成目标文件路径只要让 Destination 的 URI 长度小于 location 前缀长度size_t 的减法就会发生无符号下溢为后续堆溢出埋雷2.2 原理场景 B触发崩溃/异常行为COPY下面是一组“看起来很正常”的 COPY 请求源 URI 是 /dav/a.txt落在 /dav/ locationDestination 刻意写成更短的 /dav不带尾部 /与源保持“非 collection”一致。模拟 HTTP 流量COPY /dav/a.txt HTTP/1.1 Host:127.0.0.1:8080 Destination: /dav Overwrite: T Content-Length:0观察服务端日志可能出现 worker 异常、崩溃或在调试版/ASAN 下直接报内存越界。响应行为可能直接断开连接、返回 500或返回异常状态码取决于溢出是否立即破坏关键堆元数据。2.3 原理场景 C触发崩溃/异常行为MOVEMOVE复现方式类似区别在于后续逻辑可能走rename / ext_rename_file分支但目标路径的生成入口仍在ngx_http_map_uri_to_path()因此同样可触发。模拟 HTTP 流量MOVE /dav/a.txt HTTP/1.1 Host:127.0.0.1:8080 Destination: /dav Overwrite: T Content-Length:02.4 流量特征总结• 方法 COPY 或 MOVE • 必备头 Destination • 关键特征 • Destination 采用绝对路径形式以/ 开头或同仓库绝对 URLhttp://host/... • Destination 的路径 短于当前 location 前缀长度例如 location /dav/Destination 却为 /dav • 源 URI 与 Destination 的“collection 形态”一致源码强制要求尾部 / 同步还是附上一半的实验吧0x3 漏洞原理分析3.1-架构与模块定位从 WebDAV 到核心路径映射三个问题• 用户输入HTTP 报文在哪一层被接受 • 这个输入如何被转换成“文件系统路径” • 最终是谁在做内存分配/拷贝边界条件是否由同一处统一守住 沿着这三个问题往下走很快就能把链路收敛到两个核心点WebDAV的COPY/MOVE处理与 HTTP核心模块的URI-path映射。3.2 [核心入口] Destination 的“可信假设”它被允许改变 r-uri先从COPY/MOVE的入口开始追ngx_http_dav_handler()在识别到NGX_HTTP_COPY/NGX_HTTP_MOVE后统一进入ngx_http_dav_copy_move_handler()在这个函数里有一段关键但容易被忽略的操作它会把Destination解析成duri然后暂时替换 r-uri让后续复用核心的路径映射函数生成目标路径static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r) { // ... dest r-headers_in.destination; if (dest NULL) { /* ... */ } // 解析 Destination得到 duri duri.len last - p; duri.data p; flags NGX_HTTP_LOG_UNSAFE; if (ngx_http_parse_unsafe_uri(r, duri, args, flags) ! NGX_OK) { goto invalid_destination; } // 先 map 源路径 if (ngx_http_map_uri_to_path(r, path, root, 0) NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } uri r-uri; r-uri duri; // 再 map 目的路径危险r-uri 已经被 Destination 接管 if (ngx_http_map_uri_to_path(r, ©.path, root, 0) NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } r-uri uri; // ... }预期的安全边界Destination虽然来自客户端但应该被限制在“同一仓库/同一location 语义”下例如alias的扣除长度必须小于等于URI长度。实际代码的缺失这里的Destination只做了“URL 与 unsafe URI”的通用校验却没有做“与当前location前缀的一致性校验”。一旦r-uri被换成更短的duri后续核心映射函数就可能在alias分支里。换句话说WebDAV层把一个外部输入升级为了核心层的关键上下文r-uri但没有同时把核心层所依赖的不变量r-uri.len alias一并守住。3.3-[爆发点] map_uri_to_path()的无符号下溢r-uri.len - alias接着看向真正“最后一道失守的防线”ngx_http_map_uri_to_path()在 root_lengths NULL非脚本 root/alias最常见路径时它直接计算需要的缓冲区长度并分配然后把 root与r-uri拷到这块堆内存里u_char * ngx_http_map_uri_to_path(ngx_http_request_t *r, ngx_str_t *path, size_t *root_length, size_t reserved) { u_char *last; size_t alias; ngx_http_core_loc_conf_t *clcf; clcf ngx_http_get_module_loc_conf(r, ngx_http_core_module); alias clcf-alias; if (alias !r-valid_location) { // ... return NULL; } if (clcf-root_lengths NULL) { *root_length clcf-root.len; path-len clcf-root.len reserved r-uri.len - alias 1; path-data ngx_pnalloc(r-pool, path-len); if (path-data NULL) { return NULL; } last ngx_copy(path-data, clcf-root.data, clcf-root.len); } else { // ... } last ngx_copy(last, r-uri.data alias, r-uri.len - alias); *last \0; return last; }现在关键点来了alias是什么它不是布尔值而是一个 长度扣除量。当你在前缀location里配置alias时clcf-alias会被赋值为location名称长度例如location /dav/则 alias 5用于从r-uri中扣除/dav/这段前缀。问题在于这段代码没有任何检查来保证 r-uri.len alias。· r-uri.len - alias在size_t上发生无符号下溢变成一个极大的数例如(4-5)会变成(2^{N}-1)这种级别的值。 · path-lenroot.len reserved (r-uri.len -alias)1又在size_t上发生加法溢出回绕最终得到一个看起来很小的分配长度。 · ngx_pnalloc()按这个“小长度”分配堆缓冲区。 · 随后ngx_copy(last, r-uri.data alias, r-uri.len -alias)按“下溢后的巨大长度”执行拷贝把堆缓冲区直接写穿。预期设计alias扣除的是“location前缀长度”因此理论上r-uri必须至少包含该前缀否则这次映射就不应继续。实际实现把“前缀必然存在”当作前置条件却没有在函数边界显式检查。平时这一假设成立是因为正常路由匹配下r-uri 与 clcf 来源一致但 WebDAV 的 COPY/MOVE 把 Destination 塞进了 r-uri让这个假设就会失效。3.4-[触发条件] 为什么必须是 “dav_methods COPY/MOVE 前缀 location alias”把条件逐个还原会发现它们是刚好让“错误假设”成立并被外部输入击穿1、必须是 COPY/MOVE 只有这两个方法会读取 Destination 并把它映射为目标路径见 ngx_http_dav_copy_move_handler()。2、必须启用 dav_methods 的 copy/move 否则请求根本进不了这条链路ngx_http_dav_handler()会 NGX_DECLINED。3、必须是前缀 location非正则 alias 前缀 location 下 clcf-alias 通常是 clcf-name.len 的具体数值例如 /dav/5而正则 location 的alias走 NGX_MAX_SIZE_T_VALUE等特殊分支链路形态不同。4、Destination 必须“短于” location 前缀 这是触发 r-uri.len -alias下溢的最直接方式例如 · location /dav/alias5 · Destination: /davr-uri.len4 ·下溢成立(4-5)在 size_t 上回绕成极大值 /**原则上奥TZM**/3.5-[攻击链路] 从堆溢出到“文件读写 / Webshell / RCE”的推演闭环如何能扩大危害呢“能否打到 RCE”拆成两步先看是否能形成稳定的内存破坏原语再看是否能把它转化为可利用的控制流或文件落地。第一内存破坏原语这里的越界写发生在堆上而且拷贝源来自r-uri.data alias长度来自r-uri.len - alias下溢后超大。这意味着1、可以尝试通过构造超长Destination以及精心控制其内容影响覆盖数据2、覆盖范围可能跨越多个堆块/对象取决于分配大小回绕后落在哪个size class第二风险场景落地在 WebDAV 场景下更危险WebDAV 本身就是“文件系统操作接口”一旦出现内存破坏攻击面就从“崩溃”升级为“利用后执行更高危文件操作”1、任意文件写/覆盖在 COPY/MOVE 流程中目标路径一旦被破坏或后续对象被篡改可能将写入指向非预期位置。2、Webshell 落地 - RCE若 Nginx 后端或同主机上的其他组件会执行某目录下的脚本/二进制攻击者可尝试将 payload 写入可执行位置从而实现代码执行。3、DoS即使无法稳定利用堆元数据破坏往往可稳定导致 worker 崩溃形成拒绝服务。3.6-链路总结注入点 - 爆发点完整调用/数据链HTTP COPY/MOVE Destination**利用点** -ngx_http_dav_handler()方法分发 -ngx_http_dav_copy_move_handler()解析Destination得到duri并将r-uriduri -ngx_http_map_uri_to_path()alias分支计算r-uri.len -alias -path-len发生整数回绕under-allocation -ngx_copy(..., r-uri.len -alias)**爆发点堆缓冲区溢出**0x4 修复建议1、升级最新版本将插件升级安全版本https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-276542、临时防护措施防火墙 / WAF检测并拦截求方法为COPY|MOVE且存在Destination头限制访问 仅保留必要方法例如只需要 PUT 就移除 copy/move免责声明本文仅用于安全研究目的未经授权不得用于非法渗透测试活动。

更多文章