✓ProveIt
Examples / Report #3335085 Saved analysis

TOCTOU Race Condition in HTTP/2 Connection Reuse Leads to Certificate Validation Bypass

curl · 2025-09-1117.9s analysis · $0.022 in model calls

What the report claims

TOCTOU race condition in HTTP/2 connection reuse allows certificate validation bypass via post-handshake CA file modification.

Compare with curl's recorded outcome

Labeled AI slop

curl's maintainer lists this report as AI slop

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Needs human review

The available evidence does not settle the report. A maintainer should investigate.

curl policy: Local attackers already present
Judgment withdrawn. The answer failed the evidence check.
See the withdrawn model reasoning

The following reasoning failed the citation check and does not support the final finding.

  • The attack requires local write access to the CA trust store file, which curl's policy explicitly excludes: 'When an issue can only be attacked by an attacker present on the local system... the problem is not really in curl' and 'curl cannot protect against attacks where an attacker has write access to the same directory where curl is directed to save files.'
  • The PoC does not demonstrate a Man-in-the-Middle attack: both requests go to the legitimate server (server log confirms). The server's certificate is unchanged and valid for the host. Connection reuse continues to the real server — the attacker gains no interception capability.
  • RFC 7540 Section 9.1.1 requires the certificate to be valid for the host, which it remains. The trust store change is a local configuration mutation, not a certificate validity change.
  • The relevant curl connection reuse and certificate validation code was not provided for verification, but the policy and attack analysis are sufficient.
How this analysis ran 5 claims · 0 citations verified

Evidence check: judge quoted an external source that does not say that (https://datatracker.ietf.org/doc/html/rfc7540#section-9.1.1)

Checked against curl master on 2025-09-11 (839b22863e2f). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 4 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 3548 tokens · $0.0003
3 · Evidence
1 code snippets
real curl source at 839b22863e2f · 6 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
12593 tokens · 15.2s · $0.0222
5 · Receipts
0 of 2 citations verified
every citation must point at code the judge was shown
Inspect the withdrawn draft reply

This draft belongs to the withdrawn judgment. It may contain unsupported claims.

Thank you for the detailed report. However, this falls under curl's 'Local attackers already present' and 'Saving files' policy exclusions: an attacker who can modify your CA trust store file already controls what curl trusts and can do far worse. The PoC shows both requests reaching the legitimate server — no MitM is achieved because connection reuse keeps the original TLS session to the real server. The certificate remains valid for the host per RFC 7540; only the local trust store changed. This is not considered a security vulnerability in curl.

Inspect all claims and source code 5 claims · 1 code excerpts

This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.

Report claims

C1flaw not verifiable

libcurl assumes trust from initial certificate validation is permanent for the entire connection lifecycle

Read the reasoning

The provided source code only shows curl_easy_perform (lib/easy.c:844-847), a thin wrapper. The actual TLS handshake, certificate validation, and HTTP/2 connection reuse logic (likely in lib/vtls.c, lib/openssl.c, lib/conncache.c, or lib/http2.c) are not provided. Cannot verify whether libcurl assumes trust is permanent.

C2location not verifiable

in libcurl's handling of persistent HTTP/2 connections during TLS handshake

Read the reasoning

The code handling persistent HTTP/2 connections and TLS handshake is not in the provided source snippets. SSL_CTX_set_alpn_protocols is an OpenSSL API, not curl's connection reuse logic.

C3trigger supported

attacker modifies CA file on disk after initial validation but before subsequent requests

Read the reasoning

The attack script (attack.sh in the report) demonstrates this trigger: a background process swaps the CA file symlink after 0.5s while curl makes two requests. This is a described attack vector, not a code claim.

C4impact contradicted

subsequent HTTP/2 streams bypass certificate validation enabling full Man-in-the-Middle attack

Read the reasoning

The PoC shows both requests go to the same legitimate server presenting a certificate signed by the legitimate CA. The CA file swap changes the client's trust store, not the server's certificate. Connection reuse continues to the real server — no MitM position is gained. The attacker would need to redirect traffic to their own server, but connection reuse prevents that. RFC 7540 requires the certificate to be valid for the host, which it remains.

E4:0 E5:0
C5reproduction supported

symlink swap race condition causing both requests to succeed despite CA change

Read the reasoning

The attack script demonstrates this race condition: symlink swap between requests causing both to succeed. This describes the PoC behavior accurately.

What the report says about other projects

confirmed The server certificate must be signed by a trusted CA for validation to succeed

“The certificates to add to the trust store can be specified using following options. -CAfile file Load the specified file which contains a certificate or several of them in case the input is in PEM or PKCS#12 format.” source

confirmed HTTP/2 connection reuse requires the same TLS session to be reused only if the security parameters are identical

“For 'https' resources, connection reuse additionally depends on having a certificate that is valid for the host in the URI. The certificate presented by the server MUST satisfy any checks that the client would perform when forming a new TLS connection for the host in the URI.” source

Read the original report (excerpt)

Full report on HackerOne ↗

I've discovered a Time-of-Check to Time-of-Use (TOCTOU) vulnerability in how `libcurl` handles persistent HTTP/2 connections. During the initial handshake, `libcurl` correctly validates the server's certificate against the user-provided CA bundle. However, it then assumes this trust is permanent for the entire life of the connection. If an attacker can modify the CA file on disk *after* this initial check, `libcurl` will continue to reuse the now-trusted connection for new HTTP/2 streams without ever re-validating its trust anchor. This allows an attacker to completely bypass certificate validation for all subsequent requests, enabling a full Man-in-the-Middle attack. **Affected version:** This vulnerability was confirmed on the latest stable release, **curl 8.16.0**, which I compiled from source to ensure the test was relevant. Given the nature of the bug, it likely affects all versions that support HTTP/2 connection reuse. My test build's version output: `curl 8.16.0 (x86_64-pc-linux-gnu) libcurl/8.16.0 OpenSSL/3.0.2 ... nghttp2/1.43.0` **Steps To Reproduce:** The following Proof of Concept demonstrates the vulnerability in a reliable way. It uses a Python script to orchestrate the test environment (compiling `curl`, setting up a server, and generating certificates) and then executes a small shell script to perform the actual attack. **Step 1: Save and run the Proof of Concept code** Save the code below as `poc.py` and run it with `python3 poc.py`. The script requires standard build tools (`build-essential`, etc.) and the `openssl` command-line tool. ```python # Proof of Concept for Curl HTTP/2 TOCTOU Vulnerability import threading, ssl, http.server, time, os, glob, sys, subprocess def compile_curl(): # Stage 1: Compile Curl 8.16.0 from source to ensure we test the latest version. print("--- STAGE 1: COMPILING CURL 8.16.0 ---") if os.path.exists("curl-8.16.0/src/curl"): print("Curl 8.16.0 already compiled.") return os.path.abspath("curl-8.16.0/src/curl") # Dependencies for Debian/Ubuntu can be installed with: # apt-get install -y build-essential libssl-dev libnghttp2-dev libpsl-dev subprocess.run("wget -q https://curl.se/download/curl-8.16.0.tar.gz && tar -xzf curl-8.16.0.tar.gz", shell=True, check=True) original_dir = os.getcwd() os.chdir("curl-8.16.0") print("\n--- Running ./configur …

Source code

lib/easy.clines 844–847 · definition of curl_easy_perform
844CURLcode curl_easy_perform(CURL *data)
845{
846 return easy_perform(data, FALSE);
847}