我在 libcurl 的 SMB 协议处理器中发现了一个释放后使用漏洞。当第二次 SMB 传输重用与同一服务器的现有连接时,新请求的文件路径(req->path)成为一个指向已释放堆内存的悬空指针。该内存通过 strlen() 读取并复制到外发的 SMB 数据包中,导致堆内容泄露给服务器或崩溃。
一行修复:让 req->path 拥有自己的副本,而不是借用 needle 中 smbc->share 的指针。
| CVE | CVE-2026-3805 |
| 漏洞类型 | 释放后使用(CWE-416) |
| 根本原因 | req->path 指向 needle 的 smbc->share,连接复用时被释放 |
| 引入版本 | 777c5209df(2025-04-30)- curl 8.13.0 |
| 修复版本 | e090be9f73a7a71459ef678c - curl 8.19.0(2026年3月11日) |
| 受影响版本 | curl 8.13.0 至 8.18.0 |
| 影响 | 服务器获取堆信息泄露,崩溃 |
| 严重程度 | 7.5 - 高 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
smb_setup_connection() 在一个用于缓存查找的临时 "needle" 连接上运行。它将 req->path 设置为指向 smbc->share(needle 拥有的堆内存)内部。当连接缓存找到可复用的连接时,needle 被销毁 - 释放 smbc->share - 但 req->path 仍存在于 easy handle 上,现在成为悬空指针。
当 smb_send_open() 构建 SMB OPEN 请求时,它调用 strlen(req->path) 并将结果复制到外发数据包中。该已释放堆区域中的任何数据都会发送到服务器。如果攻击者控制服务器(SSRF,或用户连接到恶意 SMB),他们将通过 SMB NT_CREATE_ANDX 请求中的“文件名”接收到泄露的堆内容。
libcurl 积极复用连接以提高性能。当你向同一主机发出多个请求时,curl 会检查是否复用现有连接,而不是建立新连接。机制如下:
SMB 协议处理器在两个位置存储状态:
smbc(连接状态)在连接的 meta_hash 上req(请求状态)在 easy handle 的 meta 上漏洞:在 needle 设置期间,req->path 被设置为指向 smbc->share 内部。当 needle 在复用时被销毁,smbc->share 被释放,但 req->path 仍然指向那里。
在 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; // <--- 指向 NEEDLE 上的 smbc->share
对于 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); // 销毁 needle -> 释放 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; // 释放后读取
// ...
curlx_strcopy(msg.bytes, sizeof(msg.bytes), req->path, byte_count - 1); // 释放后读取
已释放的堆内存可能被重新分配并包含敏感数据。strlen(req->path) 会向前扫描直到遇到空字节,curlx_strcopy() 将该内容复制到发送给服务器的 SMB 数据包中。
攻击场景:攻击者控制 SMB 服务器的 SSRF。受害者应用程序向攻击者的服务器发出两次 SMB 请求。第二次请求将堆内容作为 SMB OPEN 请求中的“文件名”泄露。
如果已释放的内存已归还给操作系统(未映射),strlen() 会触发 SIGSEGV/访问违规。
即使没有释放后使用,SMB 连接复用在语义上也是错误的。smb_send_tree_connect() 使用的是 复用 连接(旧共享名)中的 smbc->share,而不是新请求的共享名。TREE_CONNECT 会指向完全错误的共享名。
# 指向同一服务器的两个 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
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);
// 当 e2 在 e1 完成后运行并复用连接时:发生释放后使用
使用 -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 获取完整复现脚本。
根本问题是 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,
/* 将任何正斜杠转换为反斜杠,解析文件路径 */
*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` 指向 `struct smb_conn` 中的某处,该结构位于连接元数据中。
* 如果连接先被销毁,req->path 将指向已释放的内存。 */
但这只考虑了连接在 easy handle 之前销毁的场景,而遗漏了 连接复用时 needle 销毁 的场景,而这正是实际的触发条件。
| 日期 | 事件 |
|---|---|
| 2025-04-30 | 777c5209df 重构 SMB 以使用元哈希,引入了该漏洞 |
!defined(CURL_DISABLE_SMB)defined(USE_CURL_NTLM_CORE)sizeof(curl_off_t) > 4(64 位 off_t,大多数平台标准)在具有 NTLM 支持的 curl 构建中,SMB 默认启用。
e090be9f73a7a71459ef678c777c5209dfCVE-2026-3805 - 已在 curl 8.19.0 中修复。受影响版本:8.13.0 至 8.18.0。
Daniel Wade - GitHub - [email protected]
| 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 公布 |