
Proof-of-concept, воспроизводящий CVE-2026-8932 — уязвимость неполной конфигурации mTLS при повторном использовании соединений в libcurl, с локальным лабораторным сервером и PoC на C.
Proof-of-concept воспроизведение CVE-2026-8932 — проблемы неполного сопоставления конфигурации mTLS при повторном использовании соединений в libcurl.
CVE-2026-8932 затрагивает логику повторного использования соединений в libcurl, когда конфигурация mutual TLS (mTLS) изменяется между запросами.
В уязвимых условиях libcurl может повторно использовать существующее TLS-соединение, несмотря на то что связанная с mTLS опция конфигурации изменилась и должна была предотвратить повторное использование этого соединения.
Данный PoC демонстрирует проблему путём изменения пароля приватного ключа между двумя запросами при использовании одного и того же клиентского сертификата и приватного ключа.
Первый запрос использует правильный пароль:
correct-password
Второй запрос намеренно использует неверный пароль:
WRONG-PASSWORD
Если libcurl некорректно повторно использует существующее TLS-соединение, второй запрос не требует нового TLS-рукопожатия. Следовательно, неверный пароль приватного ключа никогда не потребуется для установления нового TLS-соединения, и запрос завершается успешно.
Таким образом, PoC демонстрирует условие повторного использования соединения, связанное с CVE-2026-8932.
| Свойство | Значение |
|---|
| CVE | CVE-2026-8932 |
| Компонент | libcurl |
| Тип уязвимости | Неполное сопоставление конфигурации mTLS |
| CWE | CWE-305 — Обход аутентификации через первичную слабость |
| Затронутая область | Повторное использование TLS-соединения |
| Протокол | HTTPS / mTLS |
| Клиент | libcurl API |
| curl CLI | Не затронут |
| Область PoC | Локальная лабораторная среда |
Уязвимость представляет собой проблему логики/сопоставления конфигурации, а не традиционную уязвимость безопасности памяти, такую как переполнение буфера или use-after-free.
libcurl хранит информацию о соединениях и может повторно использовать существующее соединение, когда последующий запрос считается совместимым с конфигурацией этого соединения.
Для TLS-соединений конфигурация соединения должна быть тщательно сопоставлена перед повторным использованием.
Уязвимая реализация не включала все соответствующие поля конфигурации mTLS в логику сопоставления соединений.
Среди затронутых настроек:
cert_type
key
key_type
key_passwd
key_blob
Важная настройка, используемая в этом PoC:
key_passwd
PoC устанавливает TLS-соединение с использованием зашифрованного приватного ключа клиента и правильного пароля:
correct-password
Затем выполняется ещё один запрос с использованием того же сертификата и ключа, но пароль изменяется на:
WRONG-PASSWORD
Уязвимая реализация сопоставления соединений может по-прежнему считать существующее соединение пригодным для повторного использования.
Тест состоит из двух HTTP-запросов.
Первый запрос устанавливает TLS-соединение:
URL:
https://server.test:8443/A
Клиентский сертификат:
client.crt
Приватный ключ:
client.key
Пароль ключа:
correct-password
TLS-соединение остаётся постоянным, поскольку сервер поддерживает HTTP keep-alive.
Второй запрос использует тот же сертификат и приватный ключ, но изменяет пароль ключа:
URL:
https://server.test:8443/B
Клиентский сертификат:
client.crt
Приватный ключ:
client.key
Пароль ключа:
WRONG-PASSWORD
Если libcurl создаёт новое TLS-соединение, загрузка зашифрованного приватного ключа с неверным паролем должна завершиться ошибкой.
Однако если libcurl некорректно повторно использует существующее соединение, новое TLS-рукопожатие не требуется.
Следовательно, неверный пароль не препятствует успешному выполнению HTTP-запроса.
Лабораторная установка состоит из:
localhost
|
v
+---------------------+
| Python mTLS Server |
| 127.0.0.1:8443 |
+----------+----------+
|
| HTTPS / TLS 1.3
|
+--------+--------+
| |
/A /B
\ /
\ /
v v
Same TLS Connection
|
v
libcurl
PoC (C)
Важное наблюдение заключается в том, что /A и /B должны поступать через одно и то же TLS-соединение.
CVE-2026-8932-PoC/
├── README.md
├── LICENSE
├── .gitignore
│
├── certs/
│ └── .gitkeep
│
├── poc/
│ └── poc.c
│
├── server/
│ └── server.py
│
└── docs/
├── CVE-2026-8932-POC.jpg
├── CVE-2026-8932-server-POC.jpg
├── poc-output.txt
└── server-output.txt
Каталог certs/ намеренно оставлен пустым в репозитории. Сертификаты и приватные ключи следует генерировать локально.
PoC был протестирован в следующей среде:
ОС:
Debian GNU/Linux 13 (trixie)
Архитектура:
x86_64
libcurl:
8.14.1
OpenSSL:
3.5.7
GCC:
14.2.0
Уязвимая установка libcurl, использованная для воспроизведения:
libcurl/8.14.1
Проверьте установленную версию:
curl --version
и:
pkg-config --modversion libcurl
mkdir -p ~/cve-2026-8932-lab/{certs,poc,server,docs}
cd ~/cve-2026-8932-lab
cd ~/cve-2026-8932-lab/certs
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes \
-key ca.key \
-sha256 \
-days 3650 \
-subj "/CN=CVE-2026-8932-CA" \
-out ca.crt
Сгенерируйте приватный ключ сервера:
openssl genrsa -out server.key 2048
Сгенерируйте CSR:
openssl req -new \
-key server.key \
-subj "/CN=server.test" \
-out server.csr
Создайте расширения сертификата:
printf "subjectAltName=DNS:server.test\nextendedKeyUsage=serverAuth\n" \
> server.ext
Подпишите сертификат с помощью тестового CA:
openssl x509 -req \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out server.crt \
-days 3650 \
-sha256 \
-extfile server.ext
Сгенерируйте приватный ключ клиента:
openssl genrsa -out client.key.tmp 2048
Сгенерируйте CSR:
openssl req -new \
-key client.key.tmp \
-subj "/CN=Client-A" \
-out client.csr
Преобразуйте приватный ключ в зашифрованный формат PKCS#8:
openssl pkcs8 \
-topk8 \
-in client.key.tmp \
-out client.key \
-v2 aes-256-cbc \
-passout pass:correct-password
Полученный приватный ключ зашифрован с помощью:
correct-password
Создайте расширения клиентского сертификата:
printf "extendedKeyUsage=clientAuth\n" \
> client.ext
Подпишите клиентский сертификат:
openssl x509 -req \
-in client.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out client.crt \
-days 3650 \
-sha256 \
-extfile client.ext
На этом этапе необходимые файлы должны существовать:
certs/
├── ca.crt
├── ca.key
├── client.crt
├── client.key
├── server.crt
└── server.key
Перейдите в каталог сервера:
cd ~/cve-2026-8932-lab/server
Запустите сервер:
python3 server.py
Сервер прослушивает:
0.0.0.0:8443
Требуется аутентификация клиентского сертификата.
Сервер также поддерживает активными соединения HTTP/1.1, чтобы libcurl мог повторно использовать TLS-соединение.
Откройте другой терминал:
cd ~/cve-2026-8932-lab/poc
Скомпилируйте:
gcc -Wall -Wextra -O0 -g \
poc.c \
$(pkg-config --cflags --libs libcurl) \
-o poc
Выполните:
./poc
PoC использует:
Клиентский сертификат : client.crt
Приватный ключ : client.key
Пароль запроса A : correct-password
Пароль запроса B : WRONG-PASSWORD
Наиболее важное свидетельство на стороне клиента:
[+] STEP 2: Request using modified mTLS configuration
[+] Key password = WRONG-PASSWORD
* Re-using existing https: connection with host server.test
> GET /B HTTP/1.1
Затем запрос получает:
< HTTP/1.1 200 OK
и PoC сообщает:
[!!!] B REQUEST SUCCEEDED
Это важно, поскольку /B использует намеренно неверный пароль приватного ключа.
Ключевое наблюдение заключается не просто в том, что /B завершается успешно.
Критическое свидетельство состоит в том, что libcurl явно сообщает:
Re-using existing https: connection
Следовательно, второй запрос не нуждается в выполнении ещё одного TLS-рукопожатия с использованием изменённой конфигурации ключа.
Вывод сервера обеспечивает независимое подтверждение повторного использования соединения.
Соответствующий вывод:
[+] TLS connection #2
Client CN : Client-A
[+] Connection #2 Client=Client-A Request=GET /A HTTP/1.1
[+] Connection #2 Client=Client-A Request=GET /B HTTP/1.1
Важное наблюдение:
/A -> Connection #2
/B -> Connection #2
Таким образом, оба HTTP-запроса были получены через одно и то же TLS-соединение.
Сервер также сообщает одну и ту же идентичность клиента:
Client-A
для обоих запросов.
Приватный ключ, используемый в PoC, зашифрован.
Первый запрос использует:
correct-password
что позволяет libcurl/OpenSSL получить доступ к приватному ключу и установить TLS-соединение.
Второй запрос изменяет конфигурацию на:
WRONG-PASSWORD
Если бы требовалось установить новое TLS-соединение, libcurl должен был бы снова обработать зашифрованный приватный ключ, и неверный пароль должен был бы привести к сбою операции.
Однако при повторном использовании существующего TLS-соединения TLS-сессия уже установлена.
Для /B новая операция аутентификации клиента не требуется.
Концептуально:
Request A
|
| correct-password
v
TLS handshake
|
v
Established TLS connection
|
+----------------------+
| |
v v
/A /B
|
WRONG-PASSWORD
|
X
No new TLS handshake
|
v
HTTP succeeds
Данный PoC демонстрирует повторное использование соединения, а не возобновление TLS-сессии.
Эти два механизма различаются.
Уже установленное TLS-соединение остаётся открытым и используется для другого HTTP-запроса:
TLS connection #2
|
+-- GET /A
|
+-- GET /B
Второе TLS-рукопожатие не требуется.
Создаётся новое TCP/TLS-соединение, но криптографическая информация о сессии из более раннего соединения используется для сокращения нового TLS-рукопожатия.
Это не то, на что опирается данный PoC.
PoC намеренно разделяет:
CURL_LOCK_DATA_CONNECT
между двумя easy-дескрипторами, при этом не разделяя:
CURL_LOCK_DATA_SSL_SESSION
Это сохраняет фокус демонстрации на повторном использовании соединения.
Репозиторий содержит захваченный вывод успешного воспроизведения.

Захваченный вывод демонстрирует:
/ARe-using existing https: connection/BПолный вывод терминала также доступен в:
docs/poc-output.txt

Свидетельства на стороне сервера демонстрируют, что:
/A -> TLS connection #2
/B -> TLS connection #2
Полный вывод сервера доступен в:
docs/server-output.txt
Если перед запуском PoC выполняется ручной тест с помощью curl, сервер может отобразить более раннее соединение:
TLS connection #1
Это соединение не связано с фактическим выполнением PoC.
Например, запрос ручной проверки:
curl \
--resolve server.test:8443:127.0.0.1 \
--cacert ~/cve-2026-8932-lab/certs/ca.crt \
--cert ~/cve-2026-8932-lab/certs/client.crt \
--key ~/cve-2026-8932-lab/certs/client.key \
--pass correct-password \
https://server.test:8443/A
может создать соединение #1.
Фактический PoC затем может создать соединение #2.
Таким образом, релевантным условием является не абсолютный номер соединения, а то, что:
/A и /B
появляются на одном и том же TLS-соединении во время выполнения PoC.
Исправление в upstream:
7541ae569d82fb308a5e2d94916027da4fa3ba3e
Коммит:
tls: fix incomplete mTLS config in conn reuse and session cache
Исправление добавляет недостающие сравнения конфигурации mTLS в логику сопоставления соединений.
Концептуально логика сопоставления теперь учитывает значения, включая:
blobcmp(c1->key_blob, c2->key_blob) &&
curl_strequal(c1->cert_type, c2->cert_type) &&
Curl_safecmp(c1->key, c2->key) &&
curl_strequal(c1->key_type, c2->key_type) &&
!Curl_timestrcmp(c1->key_passwd, c2->key_passwd)
Важное изменение для данного PoC — сравнение:
key_passwd
Таким образом, изменение пароля приватного ключа должно предотвращать рассмотрение существующего соединения как совместимого для повторного использования.
Исправление в upstream также добавило регрессионное покрытие для поведения сопоставления конфигурации TLS.
Соответствующий тест связан с:
test 3303
Регрессионное тестирование охватывает различия в конфигурации mTLS, включая:
key_passwd
key
key_type
cert_type
Например, идентичные конфигурации должны совпадать:
config A == config B
тогда как изменение пароля ключа не должно:
config A key_passwd = password-A
config B key_passwd = password-B
↓
connection match = false
Это то же измерение конфигурации, которое задействовано в данном PoC.
/B завершается с ошибкойЕсли /B завершается с ошибкой, сначала проверьте, действительно ли libcurl повторно использовал соединение.
Подробный вывод должен содержать:
Re-using existing https: connection
Если эта строка не появляется, условие уязвимости не было продемонстрировано.
/A и /B появляются на разных TLS-соединенияхСервер должен показывать:
Connection #X -> /A
Connection #X -> /B
Если вместо этого вы видите:
Connection #X -> /A
Connection #Y -> /B
то второй запрос создал новое TLS-соединение, и предполагаемое условие не было воспроизведено.
Проверьте, что:
CURL_LOCK_DATA_CONNECT разделяется.CURLOPT_FORBID_REUSE не включён.CURLOPT_FRESH_CONNECT не включён.Connection: keep-alive.Приватный ключ клиента должен быть сгенерирован с использованием:
correct-password
Затем PoC намеренно использует:
WRONG-PASSWORD
Не изменяйте пароль, встроенный в команду генерации приватного ключа, если только соответствующее значение в PoC также не изменено.
Данный репозиторий предназначен для контролируемых исследований в области безопасности и воспроизведения уязвимостей.
PoC разработан для работы против локально размещённого тестового сервера:
127.0.0.1:8443
Не используйте PoC против систем без авторизации.
Приватные ключи и сгенерированные сертификаты должны оставаться вне системы контроля версий.
Репозиторий должен содержать только:
certs/.gitkeep
а не сгенерированные приватные ключи.
Дизайн PoC, лабораторная методология, реализация и технический анализ были разработаны AliReza при содействии OpenAI ChatGPT (GPT-5.6 Luna).
Проверка человеком, выполнение, тестирование и воспроизведение были выполнены автором репозитория.
Данный репозиторий предоставлен для исследований в области безопасности, анализа уязвимостей и образовательных целей.
Используйте его только в системах и средах, на которые у вас есть явная авторизация.