Rat5ak/CVE-2026-3805-curl-SMB-UAF

GitHub: Rat5ak/CVE-2026-3805-curl-SMB-UAF

分析 CVE-2026-3805 漏洞原理,揭示 curl SMB 连接重用导致的使用后释放风险。

Stars: 0 | Forks: 1

# CVE-2026-3805:curl SMB 连接重用中的使用后释放漏洞 我在 libcurl 的 SMB 协议处理程序中发现了一个使用后释放(Use-After-Free)漏洞。 当第二次 SMB 传输重用与同一服务器的现有连接时,新请求的文件路径(`req->path`)是指向已释放堆内存的悬空指针。该内存通过 `strlen()` 读取并复制到发出的 SMB 数据包中,从而将堆内容泄露给服务器或导致崩溃。 一键修复:让 `req->path` 拥有自己的副本,而不是借用 needle 的 `smbc->share`。 | | | |---|---| | CVE | CVE-2026-3805 | | 错误类别 | 使用后释放(UAF,CWE-416) | | 根本原因 | `req->path` 指向 needle 的 `smbc->share`,在连接重用时被释放 | | 引入版本 | `777c5209df`(2025-04-30)— curl 8.13.0 | | 修复版本 | `e090be9f73a7a71459ef678c` — curl 8.19.0(2026-03-11) | | 影响范围 | curl 8.13.0 至 8.18.0 | | 影响 | 向服务器泄露堆信息,可能导致崩溃 | | 严重程度 | 中等 | ## 简明总结 `smb_setup_connection()` 在用于缓存查找的临时“needle”连接上运行。它将 `req->path` 设置为指向 needle 的 `smbc->share`(needle 所拥有的堆内存)内部。当连接缓存找到可重用的连接时,needle 被销毁并释放 `smbc->share`,但 `req->path` 在 easy handle 上仍然存活,此时已成为悬空指针。 当 SMB 请求继续执行时: ``` // lib/url.c, url_find_or_create_conn() line 3619: out: if(needle) Curl_conn_free(data, needle); // Destroys needle -> frees smbc->share ``` ## 背景:curl 连接重用 libcurl 会积极地重用连接以提升性能。当你对同一主机发起多个请求时,curl 会检查是否存在可重用的连接,而不是建立新连接。其机制如下: 1. 创建一个带有新请求参数的临时“needle”连接 2. 在连接缓存中搜索匹配的连接 3. 如果找到:重用现有连接,**销毁 needle** 4. 如果未找到:needle 成为实际连接 SMB 协议处理程序的状态保存在两个位置: - `smbc`(连接状态)位于连接的 `meta_hash` 上 - `req`(请求状态)位于 easy handle 的 `meta` 上 该漏洞的成因是:`req->path` 在 needle 设置期间被指向 `smbc->share` 内部。当 needle 在连接重用时被销毁,`smbc->share` 被释放,但 `req->path`(位于仍存活的 easy handle 上)仍然指向该内存。 ## 漏洞详情 在 `smb_parse_url_path()` 中,代码解析 SMB URL 路径并将其拆分为共享名和文件路径: ``` // lib/smb.c, smb_parse_url_path() line 431: smbc->share = curlx_strdup((*path == '/' || *path == '\\') ? path + 1 : path); // ... *slash++ = 0; req->path = slash; // <--- points into smbc->share on the NEEDLE ``` 对于 URL `smb://server/share1/file1.txt`,这会创建: - `smbc->share` = `"share1\0file1.txt"`(堆分配,归 needle 所有) - `req->path` = 指向 `"file1.txt"`(位于 `smbc->share` 内部) 当连接重用触发时: ``` // lib/url.c, url_find_or_create_conn() line 3619: out: if(needle) Curl_conn_free(data, needle); // Destroys needle -> frees smbc->share ``` `Curl_conn_free()` 调用 `Curl_hash_destroy(&conn->meta_hash)`,进而调用 `smb_conn_dtor()` 释放 `smbc->share`。但 `req->path`(位于 easy handle 上,仍存活)仍然指向已释放的内存。 当 SMB 请求继续执行时: ``` // lib/smb.c, smb_send_open() line 750-769: const size_t byte_count = strlen(req->path) + 1; // UAF READ // ... curlx_strcopy(msg.bytes, sizeof(msg.bytes), req->path, byte_count - 1); // UAF READ ``` ## 影响 ### 信息泄露 已释放的堆内存可能已被重新分配并包含敏感数据。 `strlen(req->path)` 会向前扫描直到遇到空字节,`curlx_strcopy()` 将该内容复制到发送给服务器的 SMB 数据包中。 攻击场景:SSRF 情况下,攻击者控制 SMB 服务器。受害应用在攻击者服务器上发起两次 SMB 请求,第二次请求会将堆内容泄露为 SMB OPEN 请求中的“文件名”。 ### 拒绝服务 如果已释放的内存已被返回给操作系统(未映射),`strlen()` 会触发 SIGSEGV/访问冲突。 ### 次级漏洞:错误的共享 即使没有 UAF,SMB 连接重用也存在语义错误。 `smb_send_tree_connect()` 使用的是 *已重用连接* 的 `smbc->share`(旧共享),而不是新请求的共享。TREE_CONNECT 会连接到错误的共享。 ## 复现 ### 通过 curl CLI ``` # 两个指向同一服务器的 SMB URL,不同的共享/文件: curl smb://192.168.1.100/share1/file1.txt -o /dev/null \ smb://192.168.1.100/share2/file2.txt -o /dev/null ``` ### 通过 libcurl(multi-handle) ``` CURLM *multi = curl_multi_init(); CURL *e1 = curl_easy_init(); curl_easy_setopt(e1, CURLOPT_URL, "smb://server/share1/file1"); curl_multi_add_handle(multi, e1); CURL *e2 = curl_easy_init(); curl_easy_setopt(e2, CURLOPT_URL, "smb://server/share2/file2"); curl_multi_add_handle(multi, e2); // When e2 runs after e1 completes and reuses the connection: UAF ``` ### 使用 AddressSanitizer 使用 `-fsanitize=address` 构建 curl 并运行上述测试: ``` ==PID==ERROR: AddressSanitizer: heap-use-after-free on address 0x... READ of size 1 at 0x... thread T0 #0 strlen #1 smb_send_open lib/smb.c:750 #2 smb_request_state lib/smb.c:1163 ... freed by thread T0 here: #0 free #1 smb_conn_dtor lib/smb.c:388 #2 Curl_hash_destroy #3 Curl_conn_free lib/url.c:557 ``` 完整的复现脚本请参见 [poc/REPRODUCE_UAF.sh](poc/REPRODUCE_UAF.sh)。 ## 修复方案 根本问题是 `req->path` 借用了一个指向 needle 的 `smbc` 所拥有内存的指针。修复方案是让 `req->path` 拥有自己的副本: ``` --- a/lib/smb.c +++ b/lib/smb.c @@ -378,7 +378,7 @@ static void smb_easy_dtor(void *key, size_t klen, void *entry) (void)key; (void)klen; + curlx_free(req->path); curlx_free(req); } @@ -428,7 +428,10 @@ static CURLcode smb_parse_url_path(struct Curl_easy *data, /* Parse the path for the file path converting any forward slashes into backslashes */ *slash++ = 0; - req->path = slash; + req->path = curlx_strdup(slash); + if(!req->path) { + Curl_safefree(smbc->share); + return CURLE_OUT_OF_MEMORY; + } ``` Stefan Eissing 在 `e090be9f73a7a71459ef678c` 实现了官方修复。 ## 开发者注意事项 开发者已部分意识到悬空指针的风险。`smb_easy_dtor()` 中存在如下注释: ``` /* `req->path` points to somewhere in `struct smb_conn` which is * kept at the connection meta. If the connection is destroyed first, * req->path points to free'd memory. */ ``` 但这只考虑了连接在 easy handle 之前被销毁的场景,遗漏了 **连接重用期间 needle 被销毁** 的场景,而这正是实际的触发条件。 ## 时间线 | 日期 | 事件 | |------|-------| | 2025-04-30 | `777c5209df` 重构 SMB 以使用 meta hash,引入该漏洞 | | 2026-03-07 | 我在安全审计中发现该漏洞 | | 2026-03-08 | 通过 HackerOne 报告给 curl(#3591944) | | 2026-03-08 | curl 联系 distros@openwall | | 2026-03-11 | curl 8.19.0 发布并修复漏洞,CVE-2026-3805 公布 | ## 受影响的配置 - 必须启用 SMB:`!defined(CURL_DISABLE_SMB)` - 必须可用 NTLM 核心:`defined(USE_CURL_NTLM_CORE)` - `sizeof(curl_off_t) > 4`(64 位 off_t,在大多数平台上为标准值) 在启用 NTLM 支持的 curl 构建中,SMB 默认启用。 ## 参考资料 - 修复提交:`e090be9f73a7a71459ef678c`(https://github.com/curl/curl/commit/e090be9f73a7a71459ef678c) - 官方公告:[curl.se/docs/CVE-2026-3805.html](https://curl.se/docs/CVE-2026-3805.html) - HackerOne 报告:[#3591944](https://hackerone.com/reports/3591944) - 引入提交:`777c5209df`(https://github.com/curl/curl/commit/777c5209df) *CVE-2026-3805 — 在 curl 8.19.0 中修复。受影响版本:8.13.0 至 8.18.0。* *Daniel Wade - [GitHub](https://github.com/Rat5ak) - danjwade95@gmail.com*
标签:curl, CVE-2026-3805, CWE-416, heap info disclosure, HTTP库, libcurl, SMB, smb_setup_connection, SSRF, Use-After-Free, 信息泄露, 内存安全, 内核驱动, 堆内存, 漏洞分析, 网络协议, 路径指针, 路径探测, 连接重用, 错误配置检测