
CVE-2026-8932를 재현하는 개념 증명으로, libcurl 연결 재사용에서의 불완전한 mTLS 구성 매칭 결함을 다루며, 로컬 랩 서버와 C PoC를 포함합니다.
CVE-2026-8932에 대한 개념 증명(Proof-of-concept) 재현으로, libcurl의 연결 재사용에서 발생하는 불완전한 mTLS 구성 매칭 문제입니다.
CVE-2026-8932는 요청 간에 상호 TLS(mTLS) 구성이 변경될 때 libcurl의 연결 재사용 로직에 영향을 미칩니다.
취약한 조건에서 libcurl은 mTLS 관련 구성 옵션이 변경되어 해당 연결을 재사용하지 말아야 함에도 불구하고 기존 TLS 연결을 재사용할 수 있습니다.
이 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
The resulting private key is encrypted with:
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
클라이언트 인증서 인증이 필요합니다.
서버는 또한 libcurl이 TLS 연결을 재사용할 수 있도록 HTTP/1.1 연결을 활성 상태로 유지합니다.
다른 터미널을 엽니다:
cd ~/cve-2026-8932-lab/poc
컴파일:
gcc -Wall -Wextra -O0 -g \
poc.c \
$(pkg-config --cflags --libs libcurl) \
-o poc
다음을 실행하세요:
./poc
PoC는 다음을 사용합니다:
Client certificate : client.crt
Private key : client.key
Request A password : correct-password
Request B password : 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에 대해서는 새로운 클라이언트 인증 작업이 필요하지 않습니다.
개념적으로: