✓ProveIt
Examples / Report #1101882 Saved analysis

CVE-2021-22876: Automatic referer leaks credentials

curl · 2021-02-1212.6s analysis · $0.014 in model calls

What the report claims

The --referer ';auto' feature copies the current URL verbatim into the Referer header of subsequent requests, potentially leaking credentials in logs.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2021-22876 Automatic referer leaks credentials

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.

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 patch demonstrates the flaw: Curl_follow copies the full URL (including user:pass and fragment) to the Referer header via strdup(data->change.url) when http_auto_referer is set.
  • The fix correctly uses the URL API to parse the URL and explicitly clear CURLUPART_USER, CURLUPART_PASSWORD, and CURLUPART_FRAGMENT before using the URL as Referer.
  • This is a privacy-sensitive information leak: credentials in URLs (though discouraged) are exposed to redirect targets via the Referer header.
  • The deterministic checks confirm the code at revision bd6b54ba1f55 (master on submission date) contains this flaw.
  • External source E1 confirms the security best practice that Referer headers should not expose credentials or fragments.
How this analysis ran 5 claims · 0 citations verified

Evidence check: judge quoted an external source that does not say that (https://www.ietf.org/ietf-ftp/rfc/inline-errata/rfc9421.html)

Checked against curl master on 2021-02-12 (bd6b54ba1f55). 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 · 1 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2106 tokens · $0.0002
3 · Evidence
1 code snippets
real curl source at bd6b54ba1f55 · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
8731 tokens · 10.1s · $0.0143
5 · Receipts
0 of 6 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 and patch. The issue is confirmed: Curl_follow in lib/transfer.c copies the full URL including credentials and fragment to the Referer header when --referer ';auto' is used. Your fix correctly uses the URL API to strip user, password, and fragment parts. This is a valid privacy-sensitive information leak. We will apply the fix.

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 supported

The current URL is copied as-is to the referrer header of the subsequent request when using --referer ';auto'

Read the reasoning

The provided patch shows the vulnerable code in Curl_follow does a direct strdup of data->change.url without stripping credentials or fragment.

lib/transfer.c:1583
C2location supported

in lib/transfer.c in the Curl_follow function

Read the reasoning

The patch diff header and context confirm the location is lib/transfer.c in the Curl_follow function.

lib/transfer.c:1567
C3trigger supported

using --referer ';auto' with a URL containing credentials or fragment

Read the reasoning

The http_auto_referer flag is set when ';auto' is used, and the vulnerable code executes when this flag is true with a URL containing credentials or fragment.

lib/transfer.c:1575
C4impact supported

credentials may appear in third party web server logs

Read the reasoning

The Referer header is sent to the next server in a redirect chain. When the URL contains user:pass credentials, they are included in the Referer header and can appear in third-party server logs. External source E1 confirms this is a recognized security concern.

lib/transfer.c:1583 source
C5reproduction supported

run curl -svLe ';auto' 'https://user:pass@curl.haxx.se#frag' and grep for Referer

Read the reasoning

The reproduction command uses --referer ';auto' with a URL containing credentials and fragment, which triggers the vulnerable code path.

lib/transfer.c:1583

What the report says about other projects

confirmed The Referer header should not expose credentials or URL fragments

“The Referer header should not expose credentials or URL fragments” source

Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: When using the `--referer ';auto'` feature the current URL is copied as-is to the referrer header of the subsequent request. The recommendation [1] is to strip these (along with the URL fragment). I can imagine this may, in rare cases, result in unwanted/unexpected disclosure of credentials (e.g. them appearing in 3rd party web server logs), though the overall chances seem low (also considering that ';auto', by hunch, is likely not a widely used curl feature). [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer#directives ## Steps To Reproduce: ``` $ curl -svLe ';auto' 'https://user:pass@curl.haxx.se#frag' 2>&1 >/dev/null | grep -i Referer: ``` ## Supporting Material/References: ``` $ curl -V curl 7.64.1 (x86_64-apple-darwin19.0) libcurl/7.64.1 (SecureTransport) LibreSSL/2.8.3 zlib/1.2.11 nghttp2/1.39.2 Release-Date: 2019-03-27 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS GSS-API HTTP2 HTTPS-proxy IPv6 Kerberos Largefile libz MultiSSL NTLM NTLM_WB SPNEGO SSL UnixSockets ``` ## Patch that fixes it ```diff diff --git a/lib/transfer.c b/lib/transfer.c index c3b2d11a2..f63aadbaf 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -1567,6 +1567,9 @@ CURLcode Curl_follow(struct Curl_easy *data, data->state.followlocation++; /* count location-followers */ if(data->set.http_auto_referer) { + CURLU *h; + char *url; + /* We are asked to automatically set the previous URL as the referer when we get the next URL. We pick the ->url field, which may or may not be 100% correct */ @@ -1576,7 +1579,40 @@ CURLcode Curl_follow(struct Curl_easy *data, data->change.referer_alloc = FALSE; } - data->change.referer = strdup(data->change.url); + /* Make a copy of the URL without the crenditals and fragment */ + h = curl_url(); + if(!h) + return CURLE_OUT_OF_MEMORY; + + uc = curl_url_set(h, CURLUPART_URL, data->change.url, 0); + if(uc) { + curl_url_cleanup(h); + return CURLE_OUT_OF_MEMORY; + } + uc = curl_url_set(h, CURLUPART_FRAGMENT, NULL, 0); + if(uc) { + curl_url_cleanup(h); + …

Source code

lib/urlapi.clines 1212–1372 · definition of curl_url_set (truncated)
1212CURLUcode curl_url_set(CURLU *u, CURLUPart what,
1213 const char *part, unsigned int flags)
1214{
1215 char **storep = NULL;
1216 long port = 0;
1217 bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0;
1218 bool plusencode = FALSE;
1219 bool urlskipslash = FALSE;
1220 bool appendquery = FALSE;
1221 bool equalsencode = FALSE;
1222
1223 if(!u)
1224 return CURLUE_BAD_HANDLE;
1225 if(!part) {
1226 /* setting a part to NULL clears it */
1227 switch(what) {
1228 case CURLUPART_URL:
1229 break;
1230 case CURLUPART_SCHEME:
1231 storep = &u->scheme;
1232 break;
1233 case CURLUPART_USER:
1234 storep = &u->user;
1235 break;
1236 case CURLUPART_PASSWORD:
1237 storep = &u->password;
1238 break;
1239 case CURLUPART_OPTIONS:
1240 storep = &u->options;
1241 break;
1242 case CURLUPART_HOST:
1243 storep = &u->host;
1244 break;
1245 case CURLUPART_ZONEID:
1246 storep = &u->zoneid;
1247 break;
1248 case CURLUPART_PORT:
1249 u->portnum = 0;
1250 storep = &u->port;
1251 break;
1252 case CURLUPART_PATH:
1253 storep = &u->path;
1254 break;
1255 case CURLUPART_QUERY:
1256 storep = &u->query;
1257 break;
1258 case CURLUPART_FRAGMENT:
1259 storep = &u->fragment;
1260 break;
1261 default:
1262 return CURLUE_UNKNOWN_PART;
1263 }
1264 if(storep && *storep) {
1265 Curl_safefree(*storep);
1266 }
1267 return CURLUE_OK;
1268 }
1269
1270 switch(what) {
1271 case CURLUPART_SCHEME:
1272 if(strlen(part) > MAX_SCHEME_LEN)
1273 /* too long */
1274 return CURLUE_MALFORMED_INPUT;
1275 if(!(flags & CURLU_NON_SUPPORT_SCHEME) &&
1276 /* verify that it is a fine scheme */
1277 !Curl_builtin_scheme(part))
1278 return CURLUE_UNSUPPORTED_SCHEME;
1279 storep = &u->scheme;
1280 urlencode = FALSE; /* never */
1281 break;
1282 case CURLUPART_USER:
1283 storep = &u->user;
1284 break;
1285 case CURLUPART_PASSWORD:
1286 storep = &u->password;
1287 break;
1288 case CURLUPART_OPTIONS:
1289 storep = &u->options;
1290 break;
1291 case CURLUPART_HOST:
1292 storep = &u->host;
1293 Curl_safefree(u->zoneid);
1294 break;
1295 case CURLUPART_ZONEID:
1296 storep = &u->zoneid;
1297 break;
1298 case CURLUPART_PORT:
1299 {
1300 char *endp;
1301 urlencode = FALSE; /* never */
1302 port = strtol(part, &endp, 10); /* Port number must be decimal */
1303 if((port <= 0) || (port > 0xffff))
1304 return CURLUE_BAD_PORT_NUMBER;
1305 if(*endp)
1306 /* weirdly provided number, not good! */
1307 return CURLUE_MALFORMED_INPUT;
1308 storep = &u->port;
1309 }
1310 break;
1311 case CURLUPART_PATH:
1312 urlskipslash = TRUE;
1313 storep = &u->path;
1314 break;
1315 case CURLUPART_QUERY:
1316 plusencode = urlencode;
1317 appendquery = (flags & CURLU_APPENDQUERY)?1:0;
1318 equalsencode = appendquery;
1319 storep = &u->query;
1320 break;
1321 case CURLUPART_FRAGMENT:
1322 storep = &u->fragment;
1323 break;
1324 case CURLUPART_URL: {
1325 /*
1326 * Allow a new URL to replace the existing (if any) contents.
1327 *
1328 * If the existing contents is enough for a URL, allow a relative URL to
1329 * replace it.
1330 */
1331 CURLUcode result;
1332 char *oldurl;
1333 char *redired_url;
1334 CURLU *handle2;
1335
1336 if(Curl_is_absolute_url(part, NULL, MAX_SCHEME_LEN + 1)) {
1337 handle2 = curl_url();
1338 if(!handle2)
1339 return CURLUE_OUT_OF_MEMORY;
1340 result = parseurl(part, handle2, flags);
1341 if(!result)
1342 mv_urlhandle(handle2, u);
1343 else
1344 curl_url_cleanup(handle2);
1345 return result;
1346 }
1347 /* extract the full "old" URL to do the redirect on */
1348 result = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1349 if(result) {
1350 /* couldn't get the old URL, just use the new! */
1351 handle2 = curl_url();
1352 if(!handle2)
1353 return CURLUE_OUT_OF_MEMORY;
1354 result = parseurl(part, handle2, flags);
1355 if(!result)
1356 mv_urlhandle(handle2, u);
1357 else
1358 curl_url_cleanup(handle2);
1359 return result;
1360 }
1361
1362 /* apply the relative part to create a new URL */
1363 redired_url = concat_url(oldurl, part);
1364 free(oldurl);
1365 if(!redired_url)
1366 return CURLUE_OUT_OF_MEMORY;
1367
1368 /* now parse the new URL */
1369 handle2 = curl_url();
1370 if(!handle2) {
1371 free(redired_url);
1372 return CURLUE_OUT_OF_MEMORY;