CVE-2026-8932 の概念実証再現。libcurl のコネクション再利用における不完全な mTLS 設定マッチングの問題です。
CVE-2026-8932 は、リクエスト間で相互 TLS (mTLS) 設定が変更された際の libcurl のコネクション再利用ロジックに影響します。
脆弱な条件下では、mTLS 関連の設定オプションが変更され、そのコネクションの再利用を防ぐべきであったにもかかわらず、libcurl が既存の TLS コネクションを再利用する可能性があります。
この PoC は、同じクライアント証明書と秘密鍵を使用しながら、2 つのリクエスト間で秘密鍵のパスワードを変更することで、この問題を実証します。
最初のリクエストでは正しいパスワードを使用します:
correct-password
2 番目のリクエストでは意図的に無効なパスワードを使用します:
WRONG-PASSWORD
libcurl が既存の TLS コネクションを誤って再利用した場合、2 番目のリクエストでは別の 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
脆弱なコネクションマッチング実装では、既存のコネクションが依然として再利用可能と見なされる可能性があります。
テストは 2 つの HTTP リクエストで構成されます。
最初のリクエストで TLS コネクションを確立します:
URL:
https://server.test:8443/A
クライアント証明書:
client.crt
秘密鍵:
client.key
鍵パスワード:
correct-password
サーバーが HTTP keep-alive をサポートしているため、TLS コネクションは維持されます。
2 番目のリクエストでは同じ証明書と秘密鍵を使用しますが、鍵パスワードを変更します:
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 は以下の環境でテストされました:
OS:
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
したがって、2 番目のリクエストは変更された鍵設定を使用して別の 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 コネクションを通じて受信されました。
また、サーバーは両方のリクエストに対して同じクライアント ID を報告します:
Client-A
PoC で使用される秘密鍵は暗号化されています。
最初のリクエストでは以下を使用します:
correct-password
これにより、libcurl/OpenSSL が秘密鍵にアクセスし、TLS コネクションを確立できます。
2 番目のリクエストでは設定を以下に変更します:
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 セッション再開ではありません。
この 2 つのメカニズムは異なります。
すでに確立された TLS コネクションが開いたままになり、別の HTTP リクエストに使用されます:
TLS connection #2
|
+-- GET /A
|
+-- GET /B
2 回目の TLS ハンドシェイクは不要です。
新しい TCP/TLS コネクションが作成されますが、以前のコネクションからの暗号化セッション情報を使用して新しい TLS ハンドシェイクを短縮します。
この PoC が依存しているのはこれではありません。
この PoC は意図的に以下を共有します:
CURL_LOCK_DATA_CONNECT
2 つの easy ハンドル間で共有しますが、以下は共有しません:
CURL_LOCK_DATA_SSL_SESSION
これにより、実証はコネクション再利用に焦点を当てたままになります。
リポジトリには、再現成功時のキャプチャ出力が含まれています。

キャプチャ出力は以下を実証しています:
/A リクエストRe-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 を作成する可能性があります。
したがって、関連する条件は絶対的なコネクション番号ではなく、PoC 実行中に以下が現れることです:
/A and /B
が PoC 実行中に同じ TLS コネクション上に現れることです。
アップストリームの修正は以下です:
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
したがって、秘密鍵のパスワードの変更は、既存のコネクションが再利用に互換性があると見なされるのを防ぐはずです。
アップストリームの修正は、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
2 番目のリクエストが新しい 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 の設計、実験方法論、実装、および技術分析は、OpenAI ChatGPT (GPT-5.6 Luna) の支援を受けて AliReza によって開発されました。
人間による検証、実行、テスト、および再現は、リポジトリの作者によって 実施されました。
このリポジトリは、セキュリティ研究、脆弱性分析、および教育目的で提供されています。
明示的な許可を得たシステムおよび環境でのみ使用してください。