✓ProveIt
Examples / Report #1755083 Saved analysis

CVE-2022-43551: Another HSTS bypass via IDN

curl · 2022-10-2913.3s analysis · $0.013 in model calls

What the report claims

HSTS bypass via IDN where a hostname containing a Unicode character that converts to '.' bypasses HSTS checks.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2022-43551 Another HSTS bypass via IDN

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 cites http.c:3727 (data->state.up.hostname) as the flaw location, but the provided snippets do not show where this field is set relative to IDN conversion.
  • The IDN conversion (Nameprep) code and the hostname resolution code are not included, so the central claim—that the pre-conversion hostname is stored in the HSTS cache while the post-conversion hostname is used for lookup—cannot be confirmed or refuted from the given context.
  • The report references a prior CVE (CVE-2022-42916) with a similar pattern, making the claim plausible, but the provided code is insufficient to decide.
What still needs checking
  • The code that assigns data->state.up.hostname (likely in lib/url.c or lib/urldata.h area) and the IDN conversion function (likely Curl_idnconvert or similar).
  • The HSTS lookup path used when processing an HTTP request (to see which hostname form is used for the cache query).
How this analysis ran 5 claims · 0 citations verified

Checked against curl master on 2022-10-29 (a55256cfb242). 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 · 5 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1625 tokens · $0.0002
3 · Evidence
2 code snippets
real curl source at a55256cfb242
4 · Judgment
Nemotron-3-Ultra-550b-a55b
7232 tokens · 10.9s · $0.0131
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 and reproduction steps. To evaluate this, we need to see where `data->state.up.hostname` is populated and whether that happens before or after IDN conversion. Could you point to the function that sets this field, or provide the relevant url.c/urldata.c snippets? Also, the HSTS lookup path used during an HTTP request (not just the parse-on-HTTPS-response path shown) would clarify whether the same hostname form is used for the cache query.

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

Report claims

C1flaw not verifiable

HSTS checks are bypassed if any character in the IDN converts to '.'

Read the reasoning

The claim that HSTS checks are bypassed when IDN converts a character to '.' requires seeing both the IDN conversion code and the HSTS lookup code to verify a mismatch. The provided snippets show only the call to Curl_hsts_parse (http.c:3727) and the parse function itself (hsts.c:141-239), but not where data->state.up.hostname is populated or where IDN conversion occurs.

C2location not verifiable

The host name before IDN conversion is used when writing to the HSTS cache

Read the reasoning

The report asserts that data->state.up.hostname holds the pre-IDN-conversion hostname. The provided code at http.c:3727 passes this field to Curl_hsts_parse, and hsts.c:214,229,236 shows it is used for both storage and lookup. However, the code that assigns data->state.up.hostname (and whether that happens before or after IDN conversion) is not in the provided snippets.

C3trigger not verifiable

Using a hostname with a Unicode character that converts to '.' in an HTTP request

Read the reasoning

This describes a trigger condition (using a Unicode character that IDN-converts to '.'). Verifying whether such a character reaches the HSTS cache in unconverted form requires the IDN conversion code and the hostname assignment code, which are not provided.

C4impact not verifiable

Access to the site with HTTPS after bypassing HSTS

Read the reasoning

The impact claim (HTTPS access after bypass) is a behavioral consequence. The provided code does not show the HSTS lookup path used during an HTTP request to determine whether an upgrade to HTTPS would occur.

C5reproduction not verifiable

Step 2: curl -v --hsts hsts.txt https://accounts.google%E3%80%82com; Step 3: curl -v --hsts hsts.txt http://accounts.google%E3%80%82com

Read the reasoning

Reproduction steps are user-level instructions. They cannot be verified against the provided source snippets alone; they require a running build and the full hostname-resolution/IDN code paths.

Read the original report

Full report on HackerOne ↗

## Summary: I found an issue similar to CVE-2022-42916 again. Since the phenomenon is the same, I will describe the same as last time. HSTS checks are bypassed if any character in the IDN convert(Nameprep) to a '.' for example"。"(UTF-8:E38082). I think there are other characters that become ".(UTF-8:2E)" as a result of converting with IDN. This is because the host name before IDN conversion is used when writing to the HSTS cache. ## Steps To Reproduce: [add details for how we can reproduce the issue] 1. Start from a state where there is no entry for the access destination host name in the HSTS cache 2. `curl -v --hsts hsts.txt https://accounts.google%E3%80%82com` 3. `curl -v --hsts hsts.txt http://accounts.google%E3%80%82com` Result of 3. ``` C:\test\curl-7.86.0-win64-mingw\bin>curl -v --hsts hsts.txt http://accounts.google%E3%80%82com --head * Trying 142.250.206.237:80... * Connected to accounts.google縲Dom (142.250.206.237) port 80 (#0) > HEAD / HTTP/1.1 > Host: accounts.google.com > User-Agent: curl/7.86.0 > Accept: */* > ``` If you execute 3. after executing the below, you will access the site with HTTPS. `curl -v --hsts hsts.txt https://accounts.google.com` I use [this](https://curl.se/download/curl-7.86.0.zip) in a Windows environment. I checked the HSTS cache after executing 2. and found the host name before IDN conversion. ``` # Your HSTS cache. https://curl.se/docs/hsts.html # This file was generated by libcurl! Edit at your own risk. .accounts.google。com "20231029 15:57:29" ``` I think the problem is in http.c:line 3727. data->state.up.hostname is the hostname of the IDN unconverted. ``` CURLcode check = Curl_hsts_parse(data->hsts, data->state.up.hostname, headp + strlen("Strict-Transport-Security:")); ``` ## Impact HSTS bypass.

Source code

lib/http.clines 3702–3752 · lines 3727-3727 cited by the report
3702 data->req.newurl = strdup(data->req.location); /* clone */
3703 if(!data->req.newurl)
3704 return CURLE_OUT_OF_MEMORY;
3705
3706 /* some cases of POST and PUT etc needs to rewind the data
3707 stream at this point */
3708 result = http_perhapsrewind(data, conn);
3709 if(result)
3710 return result;
3711 }
3712 }
3713 }
3714
3715#ifndef CURL_DISABLE_HSTS
3716 /* If enabled, the header is incoming and this is over HTTPS */
3717 else if(data->hsts && checkprefix("Strict-Transport-Security:", headp) &&
3718 ((conn->handler->flags & PROTOPT_SSL) ||
3719#ifdef CURLDEBUG
3720 /* allow debug builds to circumvent the HTTPS restriction */
3721 getenv("CURL_HSTS_HTTP")
3722#else
3723 0
3724#endif
3725 )) {
3726 CURLcode check =
3727 Curl_hsts_parse(data->hsts, data->state.up.hostname,
3728 headp + strlen("Strict-Transport-Security:"));
3729 if(check)
3730 infof(data, "Illegal STS header skipped");
3731#ifdef DEBUGBUILD
3732 else
3733 infof(data, "Parsed STS header fine (%zu entries)",
3734 data->hsts->list.size);
3735#endif
3736 }
3737#endif
3738#ifndef CURL_DISABLE_ALTSVC
3739 /* If enabled, the header is incoming and this is over HTTPS */
3740 else if(data->asi && checkprefix("Alt-Svc:", headp) &&
3741 ((conn->handler->flags & PROTOPT_SSL) ||
3742#ifdef CURLDEBUG
3743 /* allow debug builds to circumvent the HTTPS restriction */
3744 getenv("CURL_ALTSVC_HTTP")
3745#else
3746 0
3747#endif
3748 )) {
3749 /* the ALPN of the current request */
3750 enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
3751 result = Curl_altsvc_parse(data, data->asi,
3752 headp + strlen("Alt-Svc:"),
lib/hsts.clines 141–239 · definition of Curl_hsts_parse
141CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname,
142 const char *header)
143{
144 const char *p = header;
145 curl_off_t expires = 0;
146 bool gotma = FALSE;
147 bool gotinc = FALSE;
148 bool subdomains = FALSE;
149 struct stsentry *sts;
150 time_t now = time(NULL);
151
152 if(Curl_host_is_ipnum(hostname))
153 /* "explicit IP address identification of all forms is excluded."
154 / RFC 6797 */
155 return CURLE_OK;
156
157 do {
158 while(*p && ISBLANK(*p))
159 p++;
160 if(Curl_strncasecompare("max-age=", p, 8)) {
161 bool quoted = FALSE;
162 CURLofft offt;
163 char *endp;
164
165 if(gotma)
166 return CURLE_BAD_FUNCTION_ARGUMENT;
167
168 p += 8;
169 while(*p && ISBLANK(*p))
170 p++;
171 if(*p == '\"') {
172 p++;
173 quoted = TRUE;
174 }
175 offt = curlx_strtoofft(p, &endp, 10, &expires);
176 if(offt == CURL_OFFT_FLOW)
177 expires = CURL_OFF_T_MAX;
178 else if(offt)
179 /* invalid max-age */
180 return CURLE_BAD_FUNCTION_ARGUMENT;
181 p = endp;
182 if(quoted) {
183 if(*p != '\"')
184 return CURLE_BAD_FUNCTION_ARGUMENT;
185 p++;
186 }
187 gotma = TRUE;
188 }
189 else if(Curl_strncasecompare("includesubdomains", p, 17)) {
190 if(gotinc)
191 return CURLE_BAD_FUNCTION_ARGUMENT;
192 subdomains = TRUE;
193 p += 17;
194 gotinc = TRUE;
195 }
196 else {
197 /* unknown directive, do a lame attempt to skip */
198 while(*p && (*p != ';'))
199 p++;
200 }
201
202 while(*p && ISBLANK(*p))
203 p++;
204 if(*p == ';')
205 p++;
206 } while (*p);
207
208 if(!gotma)
209 /* max-age is mandatory */
210 return CURLE_BAD_FUNCTION_ARGUMENT;
211
212 if(!expires) {
213 /* remove the entry if present verbatim (without subdomain match) */
214 sts = Curl_hsts(h, hostname, FALSE);
215 if(sts) {
216 Curl_llist_remove(&h->list, &sts->node, NULL);
217 hsts_free(sts);
218 }
219 return CURLE_OK;
220 }
221
222 if(CURL_OFF_T_MAX - now < expires)
223 /* would overflow, use maximum value */
224 expires = CURL_OFF_T_MAX;
225 else
226 expires += now;
227
228 /* check if it already exists */
229 sts = Curl_hsts(h, hostname, FALSE);
230 if(sts) {
231 /* just update these fields */
232 sts->expires = expires;
233 sts->includeSubDomains = subdomains;
234 }
235 else
236 return hsts_create(h, hostname, subdomains, expires);
237
238 return CURLE_OK;
239}