## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-RAT5AK-CVE-2026-3805-CURL-SMB-UAF
# CVE-2026-3805: Use-After-Free при переиспользовании SMB-соединений curl
Я обнаружил уязвимость типа use-after-free в обработчике протокола SMB в libcurl. Когда вторая SMB-передача переиспользует существующее соединение с тем же сервером, путь к файлу для нового запроса (`req->path`) является висячим указателем на освобождённую память в куче. Эта память читается через `strlen()` и копируется в исходящий SMB-пакет, что приводит к утечке содержимого кучи на сервер или к краху.
Исправление в одну строку: заставить `req->path` владеть собственной копией, а не заимствовать указатель из `smbc->share` иглы (needle).
|
---|---
CVE| CVE-2026-3805
Класс уязвимости| Use-After-Free (CWE-416)
Корневая причина| `req->path` указывает внутрь `smbc->share` иглы, освобождаемого при переиспользовании соединения
Появилась| `777c5209df` (2025-04-30) — curl 8.13.0
Исправлена| `e090be9f73a7a71459ef678c` — curl 8.19.0 (11 марта 2026)
Затронуты| curl 8.13.0 – 8.18.0
Воздействие| Раскрытие содержимого кучи серверу, крах
Серьёзность| 7.5 - HIGH - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
## TL;DR
`smb_setup_connection()` выполняется на временном соединении-«игле» (needle), используемом для поиска в кэше соединений. Она устанавливает `req->path` так, чтобы он указывал _внутрь_ `smbc->share` (память в куче, принадлежащая игле). Когда кэш соединений находит переиспользуемое соединение, игла уничтожается — `smbc->share` освобождается, — но `req->path` остаётся на easy handle, теперь уже висячий.
Когда `smb_send_open()` формирует запрос SMB OPEN, она вызывает `strlen(req->path)` и копирует результат в исходящий пакет. Любые данные, оказавшиеся в этой освобождённой области кучи, уходят на сервер. Если атакующий контролирует сервер (SSRF либо пользователь подключается к вредоносному SMB-серверу), он получает утёкшее содержимое кучи в качестве «имени файла» в запросе SMB NT_CREATE_ANDX.
* * *
## Предыстория: переиспользование соединений в curl
libcurl агрессивно переиспользует соединения ради производительности. Когда вы выполняете несколько запросов к одному хосту, curl проверяет, можно ли переиспользовать существующее соединение, вместо установки нового. Механизм работает так:
1. Создаётся временное соединение-«игла» (needle) с параметрами нового запроса
2. Выполняется поиск подходящего соединения в кэше соединений
3. Если найдено: переиспользуется существующее соединение, **игла уничтожается**
4. Если не найдено: игла становится реальным соединением
Обработчик протокола SMB хранит состояние в двух местах:
* `smbc` (состояние соединения) в `meta_hash` соединения
* `req` (состояние запроса) в `meta` easy handle
Ошибка: `req->path` устанавливается так, чтобы указывать _внутрь_ `smbc->share` во время настройки иглы. Когда игла уничтожается при переиспользовании, `smbc->share` освобождается, но `req->path` по-прежнему указывает туда.
## Ошибка
В `smb_parse_url_path()` код разбирает путь SMB URL и разделяет его на имя общего ресурса (share) и путь к файлу:
root@kitploit:~
// 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"` (выделено в куче, принадлежит игле)
* `req->path` = указатель на `"file1.txt"` (внутри `smbc->share`)
Когда срабатывает переиспользование соединения:
root@kitploit:~
// 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-запрос продолжает выполнение:
root@kitploit:~
// 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
root@kitploit:~
# Two SMB URLs to the same server, different shares/files:
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)
root@kitploit:~
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
Соберите curl с флагом `-fsanitize=address` и выполните команды выше:
root@kitploit:~
==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` заимствует указатель на память, принадлежащую `smbc` иглы. Исправление заставляет `req->path` владеть собственной копией:
root@kitploit:~
--- 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()` есть такой комментарий:
root@kitploit:~
/* `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. Был упущен сценарий **уничтожения иглы при переиспользовании соединения** , который и является фактическим триггером.
* * *
## Хронология
Дата| Событие
---|---
* * *
## Подверженные конфигурации
* SMB должен быть включён: `!defined(CURL_DISABLE_SMB)`
* NTLM core должен быть доступен: `defined(USE_CURL_NTLM_CORE)`
* `sizeof(curl_off_t) > 4` (64-битный off_t, стандарт на большинстве платформ)
SMB включён по умолчанию в сборках curl, поддерживающих NTLM.
* * *
## Ресурсы
* Коммит с исправлением: `e090be9f73a7a71459ef678c`
* Официальное уведомление: curl.se/docs/CVE-2026-3805.html
* Отчёт HackerOne: #3591944
* Коммит, внёсший уязвимость: `777c5209df`
* * *
_CVE-2026-3805 — исправлено в curl 8.19.0. Затронуты версии: 8.13.0 – 8.18.0._
_Daniel Wade —GitHub — some-email@example.com_