
Proof-of-concept, воспроизводящий CVE-2026-8932 — уязвимость неполной конфигурации mTLS при повторном использовании соединений в libcurl, с локальным лабораторным сервером и PoC на C.
Доказательство концепции воспроизведения 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 certificate:
client.crt
Private key:
client.key
Key password:
correct-password
TLS-соединение остаётся постоянным, поскольку сервер поддерживает HTTP keep-alive.
Второй запрос использует тот же сертификат и закрытый ключ, но изменяет пароль ключа:
URL:
https://server.test:8443/B
Client certificate:
client.crt
Private key:
client.key
Key password:
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 был протестирован в следующей среде:
OS:
Debian GNU/Linux 13 (trixie)
Architecture:
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
Перейдите в каталог сервера: