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, 信息泄露, 内存安全, 内核驱动, 堆内存, 漏洞分析, 网络协议, 路径指针, 路径探测, 连接重用, 错误配置检测