✓ProveIt
Examples / Report #2764830 Saved analysis

CVE-2024-9681: HSTS subdomain overwrites parent cache entry

curl · 2024-10-0713.6s analysis · $0.015 in model calls

What the report claims

A subdomain's HSTS header can overwrite the parent domain's HSTS expiration time in curl's HSTS cache, causing incorrect expiration values.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2024-9681 HSTS subdomain overwrites parent cache entry

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.

Why this finding?
  • The report provides detailed reproduction steps and describes behavior that contradicts RFC 6797 (confirmed by external sources E1, E2), which explicitly states a UA must not modify a superdomain's expiry time.
  • However, the curl source code for HSTS handling (hsts.c) is not included in the provided source (only lib/urldata.h lines 183-258 is shown), so the implementation flaw cannot be verified against the code.
  • The deterministic checks indicate the revision was 'grounded' but do not provide the actual HSTS code findings.
  • The issue appears plausible and would be a security-relevant standards violation (parent domain DoS via subdomain), but code-level confirmation is missing.
What still needs checking
  • The hsts.c source file (or relevant functions like Curl_hsts_parse, Curl_hsts_save, or cache update logic) from revision 86d5c2651d3e
  • The specific function where HSTS cache entries are read, updated, and written when includeSubDomains is processed
How this analysis ran 5 claims · 0 citations verified

Checked against curl tested commit 86d5c2651d3e (86d5c2651d3e). 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 · 3 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2030 tokens · $0.0002
3 · Evidence
1 code snippets
real curl source at 86d5c2651d3e · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
9267 tokens · 10.6s · $0.0152
5 · Receipts
0 of 0 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed report with clear reproduction steps. The behavior you describe contradicts RFC 6797 Section 8.1 and 11.2, which state a UA must not modify a superdomain's expiry time. However, the provided source code for this review only includes lib/urldata.h, not the HSTS implementation (hsts.c). Could you point to the specific function in hsts.c where the cache update occurs when includeSubDomains is processed? That would allow us to verify the fix location.

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

Report claims

C1flaw not verifiable

When a subdomain sets a HSTS header with includeSubDomains, curl incorrectly updates the parent domain's expiration time with the subdomain's expiration time.

Read the reasoning

The claim describes a flaw in HSTS cache update logic, but the provided curl source only includes lib/urldata.h (lines 183-258), which contains CURLcode typedef and unrelated structures. The HSTS implementation (hsts.c) is not in the provided source, so the code cannot be examined to confirm or contradict the flaw.

C2location not verifiable

The bug occurs in the function that parses and stores HSTS entries from the cache file.

Read the reasoning

The claim points to a function in hsts.c that parses and stores HSTS entries. The provided source does not include hsts.c, so the location cannot be verified.

C3trigger not verifiable

Connecting to a subdomain that sends a Strict-Transport-Security header with includeSubDomains causes the parent domain's cache entry to be overwritten.

Read the reasoning

The trigger involves CURLOPT_HSTS and CURLHSTS processing when a subdomain sends includeSubDomains. The relevant code path is not in the provided source (only urldata.h is shown).

C4impact not verifiable

The parent domain's HSTS expiration time is set to the subdomain's expiration time, potentially extending it incorrectly.

Read the reasoning

The impact claim describes the behavioral result of the alleged bug. Without the HSTS cache update code, the code-level cause cannot be verified.

C5reproduction not verifiable

Create a cache file with a short expiration for a subdomain, then perform two requests to the same subdomain; the second request overwrites the parent domain's expiration time.

Read the reasoning

The reproduction steps reference a test file (testhsts.txt) and command-line invocations. The provided source does not include test files or the command-line tool's HSTS handling code.

What the report says about other projects

confirmed The UA MUST NOT modify the expiry time or the includeSubDomains directive of any superdomain matched Known HSTS Host.

“The UA MUST NOT modify the expiry time or the includeSubDomains directive of any superdomain matched Known HSTS Host.” source

confirmed Section 8.1 of RFC 6797 should say 'Update the UA's cached information for the Known HSTS Host' without the conditional about max-age and includeSubDomains conveying different information.

“Section 8.1 says: o Update the UA's cached information for the Known HSTS Host if either or both of the max-age and includeSubDomains header field value tokens are conveying information different than that already maintained by the UA. It should say: o Update the UA's cached information for the Known HSTS Host.” source

Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: Suppose my HSTS cache file has the following content: ``` .domain.com "20241107 01:02:03" .sub.domain.com "unlimited" ``` Now, I connect to https://sub.domain.com/. Suppose this domain now sets a HSTS policy: `Strict-Transport-Security: max-age=15768000 ; includeSubDomains`. Surprisingly my HSTS cache file now becomes: ``` .domain.com "unlimited" .sub.domain.com "20250408 00:26:19" ``` While the HSTS policy for "sub.domain.com" is correctly updated, the HSTS expiration time for "domain.com" is mistakenly set to be the previous expiration time for "sub.domain.com". If I have multiple levels of subdomains in my HSTS cache, the situation is more confusing. Suppose my HSTS cache is: ``` .com "20241108 01:02:03" .badssl.com "20260408 04:39:00" ``` Now I connect to https://hsts.badssl.com/index.html. After that, the HSTS cache becomes: ``` .com "20260408 04:39:00" .hsts.badssl.com "20250408 04:49:30" ``` ## Steps To Reproduce: * curl version: curl 8.11.0-DEV (x86_64-pc-linux-gnu) libcurl/8.11.0-DEV OpenSSL/3.0.2 libpsl/0.21.0, curl source HEAD commit: 86d5c2651d3ea8af316eff2a2452ae61413c66ba * Also reproducible in curl 8.10.1 release version. 1. Create a text file `testhsts.txt` with the following content: `.badssl.com "20241101 00:25:31"` (less than 1 month expiration time) 2. Run `curl -v --hsts ./testhsts.txt "http://hsts.badssl.com/index.html"`. Check the content of `testhsts.txt` 3. Run `curl -v --hsts ./testhsts.txt "http://hsts.badssl.com/index.html"` again. Check the content of `testhsts.txt` again. * After step 2, the content of `testhsts.txt` is: ``` .badssl.com "20241101 00:25:31" .hsts.badssl.com "20250408 04:39:00" ``` * After step 3, the content of `testhsts.txt` is: ``` .badssl.com "20250408 04:39:00" .hsts.badssl.com "20250408 04:40:01" ``` You can see the expiration time of `.badssl.com` is set incorrectly. ## Supporting Material/References: None ## Impact For shared subdomains, i.e. different subdomains are controlled by different users, a malicious subdomain can influence the HSTS expiration time of the parent domain. By my tests, a subdomain can only increase the expiration time of its parent domain, but can't shorten it. A malicious subdomain can cause a denial of service of its parent domain, if the parent domain only plans to support HSTS for a short period of time, and wants to revert to plaintext http after a wh …

Source code

lib/urldata.hlines 183–258 · definition of CURLcode
183typedef CURLcode (*Curl_datastream)(struct Curl_easy *data,
184 struct connectdata *conn,
185 int *didwhat,
186 int select_res);
187#endif
188
189#include "mime.h"
190#include "imap.h"
191#include "pop3.h"
192#include "smtp.h"
193#include "ftp.h"
194#include "file.h"
195#include "vssh/ssh.h"
196#include "http.h"
197#include "rtsp.h"
198#include "smb.h"
199#include "mqtt.h"
200#include "ftplistparser.h"
201#include "multihandle.h"
202#include "c-hyper.h"
203#include "cf-socket.h"
204
205#ifdef HAVE_GSSAPI
206# ifdef HAVE_GSSGNU
207# include <gss.h>
208# elif defined HAVE_GSSAPI_GSSAPI_H
209# include <gssapi/gssapi.h>
210# else
211# include <gssapi.h>
212# endif
213# ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
214# include <gssapi/gssapi_generic.h>
215# endif
216#endif
217
218#ifdef USE_LIBSSH2
219#include <libssh2.h>
220#include <libssh2_sftp.h>
221#endif /* USE_LIBSSH2 */
222
223#define READBUFFER_SIZE CURL_MAX_WRITE_SIZE
224#define READBUFFER_MAX CURL_MAX_READ_SIZE
225#define READBUFFER_MIN 1024
226
227/* The default upload buffer size, should not be smaller than
228 CURL_MAX_WRITE_SIZE, as it needs to hold a full buffer as could be sent in
229 a write callback.
230
231 The size was 16KB for many years but was bumped to 64KB because it makes
232 libcurl able to do significantly faster uploads in some circumstances. Even
233 larger buffers can help further, but this is deemed a fair memory/speed
234 compromise. */
235#define UPLOADBUFFER_DEFAULT 65536
236#define UPLOADBUFFER_MAX (2*1024*1024)
237#define UPLOADBUFFER_MIN CURL_MAX_WRITE_SIZE
238
239#define CURLEASY_MAGIC_NUMBER 0xc0dedbadU
240#ifdef DEBUGBUILD
241/* On a debug build, we want to fail hard on easy handles that
242 * are not NULL, but no longer have the MAGIC touch. This gives
243 * us early warning on things only discovered by valgrind otherwise. */
244#define GOOD_EASY_HANDLE(x) \
245 (((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER))? TRUE: \
246 (DEBUGASSERT(!(x)), FALSE))
247#else
248#define GOOD_EASY_HANDLE(x) \
249 ((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER))
250#endif
251
252#ifdef HAVE_GSSAPI
253/* Types needed for krb5-ftp connections */
254struct krb5buffer {
255 struct dynbuf buf;
256 size_t index;
257 BIT(eof_flag);
258};