✓ProveIt
Examples / Report #1950627 Saved analysis

CVE-2023-28321: IDN wildcard match

curl · 2023-04-1710.8s analysis · $0.012 in model calls

What the report claims

curl /libcurl uses wildcards for validation during TLS communication, even if the hostname is an IDN, violating RFC-6125 section 6.4.3.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2023-28321 IDN wildcard match

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

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • The code at lib/vtls/hostcheck.c:105 checks `pattern` for "xn--" prefix, but `pattern` is the certificate's CN/SAN which contains wildcards (e.g., `x*.example.local`) and never starts with "xn--". The check should use `hostname`, which is the IDN in punycode (e.g., `xn--l8j.example.local`).
  • RFC 6125 §6.4.3 (confirmed by erratum 3090) explicitly forbids wildcard matching when the hostname is an IDN (A-label/U-label).
  • The bug allows a wildcard certificate to be accepted for an IDN hostname, enabling certificate spoofing for internationalized domain names.
  • The fix is a one-word change: `pattern` → `hostname` at line 105.
How this analysis ran 5 claims · 5 citations verified

Checked against curl master on 2023-04-17 (fc2f1e547a4a). Dev 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 · 7 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1617 tokens · $0.0002
3 · Evidence
4 code snippets
real curl source at fc2f1e547a4a · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
6895 tokens · 8.9s · $0.0115
5 · Receipts
5 of 5 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 clear report. The bug is confirmed: lib/vtls/hostcheck.c:105 checks the certificate pattern for "xn--" but should check the hostname. This allows wildcard certificates to incorrectly match IDN hostnames, violating RFC 6125 §6.4.3. We will fix this by changing `pattern` to `hostname` in the strncasecompare call.

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

Report claims

C1flaw supported

The code incorrectly uses pattern_label_end to check for wildcards, causing it to skip wildcard matching when the pattern contains "xn--"

Read the reasoning

The code at line 105 tests whether the certificate pattern starts with "xn--" to disable wildcard matching for IDNs. However, the pattern contains the wildcard (e.g., `x*.example.local`), so it never starts with "xn--". The hostname (e.g., `xn--l8j.example.local`) is the IDN and should be checked instead. This is a clear logic error.

C2location supported

The flaw occurs in lib/vtls/hostcheck.c in the function hostmatch on lines 100-106

Read the reasoning

Deterministic checks confirm the file and lines exist at the target revision.

C3trigger supported

A wildcard certificate with CN value containing "x*" is used, and the hostname contains "xn--" causing the pattern to be skipped

Read the reasoning

The trigger described matches the code path: a wildcard pattern (CN with `*`) and an IDN hostname (starting with `xn--`) will bypass the IDN check and proceed to wildcard matching at lines 124-128.

C4impact supported

The communication succeeds even though it should result in a validation error, leading to improper validation of certificate with host mismatch

Read the reasoning

Because the IDN check is ineffective, wildcard matching proceeds and can incorrectly validate a certificate for an IDN hostname, violating RFC 6125 §6.4.3.

C5reproduction supported

Create a wildcard certificate with CN `x*.example.local`, run openssl s_server, modify hosts to resolve `xn--l8j.example.local` to the local IP, then curl https://%E3%81%82.example.local --cacert server.crt

Read the reasoning

The steps are concrete and map directly to the flawed code path.

What the report says about other projects

confirmed RFC 6125 section 6.4.3 requires that clients SHOULD NOT attempt to match a presented identifier where the wildcard character is embedded within an A-label or U-label of an internationalized domain name.

“However, the client SHOULD NOT attempt to match a presented identifier where the wildcard character is embedded within an A-label or U-label [IDNA-DEFS] of an internationalized domain name [IDNA-PROTO].” source

Read the original report

Full report on HackerOne ↗

## Summary: curl /libcurl uses wildcards for validation during TLS communication, even if the hostname is an IDN. Even if wildcards are present in the CN/SAN of the certificate, they must not be used to match if the hostname is an IDN. This is described in [RFC-6125, section 6.4.3.][RFC] [RFC]: https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3 You probably know that. However, there was a problem with the implementation. `lib/vtls/hostcheck.c` in the function 'hostmatch' on lines 100-106. ``` /* We require at least 2 dots in the pattern to avoid too wide wildcard match. */ pattern_label_end = memchr(pattern, '.', patternlen); if(!pattern_label_end || (memrchr(pattern, '.', patternlen) == pattern_label_end) || strncasecompare(pattern, "xn--", 4)) return pmatch(hostname, hostlen, pattern, patternlen); ``` I think `strncasecompare(pattern, "xn--", 4))` is `strncasecompare(hostname, "xn--", 4))`. `pattern` is a value that contains wildcards because it is CN/SAN. In other words, it will not match "xn--" because it will be a string containing wildcards. ## Steps To Reproduce: 1. Create a wildcard certificate.As an example, attach a certificate and private key with CN value of `x*.example.local`. {F2298301} {F2298300} 2. `openssl s_server -accept 443 -cert server.crt -key server.key -www` 3. Modify hosts so that the name resolution result of `xn--l8j.example.local‘ is the IP of your machine in order to perform the test in the local environment. 4. `curl https://%E3%81%82.example.local --cacert server.crt` When the above is executed, the communication succeeds even though it should result in a validation error. ## Impact Improper Validation of Certificate with Host Mismatch.

Source code

lib/vtls/hostcheck.clines 75–131 · lines 100-106 cited by the report
75 */
76
77static bool hostmatch(const char *hostname,
C5: The reproduction steps exercise the `hostmatch` function with a wildcard pattern and an IDN hostname, which the code mishandles.
78 size_t hostlen,
79 const char *pattern,
80 size_t patternlen)
81{
82 const char *pattern_label_end, *wildcard, *hostname_label_end;
83 size_t prefixlen, suffixlen;
84
85 /* normalize pattern and hostname by stripping off trailing dots */
86 DEBUGASSERT(patternlen);
87 if(hostname[hostlen-1]=='.')
88 hostlen--;
89 if(pattern[patternlen-1]=='.')
90 patternlen--;
91
92 wildcard = memchr(pattern, '*', patternlen);
C3: Line 92 finds wildcard in pattern; lines 100-106 decide whether to skip wildcard matching. The condition at line 105 incorrectly uses `pattern` instead of `hostname`.
93 if(!wildcard)
94 return pmatch(hostname, hostlen, pattern, patternlen);
95
96 /* detect IP address as hostname and fail the match if so */
97 if(Curl_host_is_ipnum(hostname))
98 return FALSE;
99
100 /* We require at least 2 dots in the pattern to avoid too wide wildcard
C2: The function `hostmatch` at lines 77-129 contains the cited logic at lines 100-106 exactly as described.
101 match. */
102 pattern_label_end = memchr(pattern, '.', patternlen);
103 if(!pattern_label_end ||
104 (memrchr(pattern, '.', patternlen) == pattern_label_end) ||
105 strncasecompare(pattern, "xn--", 4))
C1: Line 105 checks `strncasecompare(pattern, "xn--", 4)` but should check `hostname` per RFC 6125 §6.4.3. The pattern is the certificate's CN/SAN (may contain wildcards like `x*.example.local`); the hostname is the IDN in punycode (starts with `xn--`). The current check never triggers for wildcard patterns.
106 return pmatch(hostname, hostlen, pattern, patternlen);
107
108 hostname_label_end = memchr(hostname, '.', hostlen);
109 if(!hostname_label_end)
110 return FALSE;
111 else {
112 size_t skiphost = hostname_label_end - hostname;
113 size_t skiplen = pattern_label_end - pattern;
114 if(!pmatch(hostname_label_end, hostlen - skiphost,
115 pattern_label_end, patternlen - skiplen))
116 return FALSE;
117 }
118 /* The wildcard must match at least one character, so the left-most
119 label of the hostname is at least as large as the left-most label
120 of the pattern. */
121 if(hostname_label_end - hostname < pattern_label_end - pattern)
122 return FALSE;
123
124 prefixlen = wildcard - pattern;
125 suffixlen = pattern_label_end - (wildcard + 1);
126 return strncasecompare(pattern, hostname, prefixlen) &&
C4: Lines 124-128 perform wildcard prefix/suffix matching when the IDN check at line 105 fails to trigger.
127 strncasecompare(wildcard + 1, hostname_label_end - suffixlen,
128 suffixlen) ? TRUE : FALSE;
129}
130
131/*
lib/strcase.hlines 39–39 · definition of strncasecompare
39#define strncasecompare(a,b,c) curl_strnequal(a,b,c)
lib/curl_memrchr.hlines 42–42 · definition of memrchr
Show 1 lines (not cited by the judge)
42#define memrchr(x,y,z) Curl_memrchr((x),(y),(z))
lib/vtls/hostcheck.clines 50–56 · definition of pmatch
Show 7 lines (not cited by the judge)
50static bool pmatch(const char *hostname, size_t hostlen,
51 const char *pattern, size_t patternlen)
52{
53 if(hostlen != patternlen)
54 return FALSE;
55 return strncasecompare(hostname, pattern, hostlen);
56}