Timing Attack Vulnerability in curl Digest Authentication via Non-Constant-Time String Comparison
What the report claims
A timing attack vulnerability exists in curl's Digest Authentication implementation due to non-constant-time string comparison (strcmp()) when comparing authentication algorithms in digest.c line 360, allowing attackers to determine supported authentication algorithms through response time analysis.
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
The available evidence does not settle the report. A maintainer should investigate.
See the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The compared 'algorithm' string is provided by the server in the WWW-Authenticate HTTP header, which is transmitted in plaintext. Any party able to measure curl's processing time can also read the header directly.
- No secret value is being compared; the check merely verifies the server offers the single algorithm curl supports (md5-sess). A timing attack reveals no information not already public.
- The project policy does not list timing attacks on public values as security issues, and this case does not cross a security boundary.
How this analysis ran 5 claims · 5 citations verified
Evidence check: judge said likely_invalid without a code citation contradicting a central claim
Checked against curl master on 2025-09-18 (ce354d0f4d25). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
Thank you for the report. The strcmp() at digest.c:360 compares the 'algorithm' value parsed from the server's WWW-Authenticate header, which is sent in cleartext. Since the algorithm is public, a timing difference reveals no secret information an observer cannot already read from the HTTP response. This does not cross a security boundary in curl's threat model.
Inspect all claims and source code 5 claims · 2 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
non-constant-time string comparison (strcmp()) when comparing authentication algorithms
Read the reasoning
The code at line 360 indeed uses strcmp() for string comparison, which is not constant-time.
digest.c line 360
Read the reasoning
The report correctly identifies the location.
response time analysis of algorithm comparisons
Read the reasoning
strcmp() does have data-dependent timing, so response time analysis could theoretically detect differences.
determine supported authentication algorithms on the server
Read the reasoning
The 'algorithm' string comes from the server's WWW-Authenticate header, which is sent in plaintext. An attacker who can observe timing can also simply read the header. No secret is being protected.
PoC script showing timing differences between MD5, MD5-sess, and SHA-1 algorithms
Read the reasoning
PoC script not provided in materials; cannot verify its methodology or results.
Read the original report (excerpt)
Source code
| 335 | char HA2_hex[2 * MD5_DIGEST_LEN + 1]; |
| 336 | char resp_hash_hex[2 * MD5_DIGEST_LEN + 1]; |
| 337 | char nonce[64]; |
| 338 | char realm[128]; |
| 339 | char algorithm[64]; |
| 340 | char qop_options[64]; |
| 341 | int qop_values; |
| 342 | char cnonce[33]; |
| 343 | char nonceCount[] = "00000001"; |
| 344 | char method[] = "AUTHENTICATE"; |
| 345 | char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; |
| 346 | char *spn = NULL; |
| 347 | |
| 348 | /* Decode the challenge message */ |
| 349 | CURLcode result = auth_decode_digest_md5_message(chlg, |
| C4: algorithm is parsed from server's challenge message (chlg) | |
| 350 | nonce, sizeof(nonce), |
| 351 | realm, sizeof(realm), |
| 352 | algorithm, |
| C4: algorithm buffer filled by auth_decode_digest_md5_message from server response | |
| 353 | sizeof(algorithm), |
| 354 | qop_options, |
| 355 | sizeof(qop_options)); |
| 356 | if(result) |
| 357 | return result; |
| 358 | |
| 359 | /* We only support md5 sessions */ |
| 360 | if(strcmp(algorithm, "md5-sess") != 0) |
| C1: Uses strcmp(algorithm, "md5-sess") for comparison | |
| C2: Location of the strcmp call | |
| C3: strcmp returns early on mismatch, creating timing differences | |
| 361 | return CURLE_BAD_CONTENT_ENCODING; |
| 362 | |
| 363 | /* Get the qop-values from the qop-options */ |
| 364 | result = auth_digest_get_qop_values(qop_options, &qop_values); |
| 365 | if(result) |
| 366 | return result; |
| 367 | |
| 368 | /* We only support auth quality-of-protection */ |
| 369 | if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) |
| 370 | return CURLE_BAD_CONTENT_ENCODING; |
| 371 | |
| 372 | /* Generate 32 random hex chars, 32 bytes + 1 null-termination */ |
| 373 | result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce)); |
| 374 | if(result) |
| 375 | return result; |
| 376 | |
| 377 | /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ |
| 378 | ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); |
| 379 | if(!ctxt) |
| 380 | return CURLE_OUT_OF_MEMORY; |
| 381 | |
| 382 | Curl_MD5_update(ctxt, (const unsigned char *) userp, |
| 383 | curlx_uztoui(strlen(userp))); |
| 384 | Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); |
| 385 | Curl_MD5_update(ctxt, (const unsigned char *) realm, |
| 129 | int Curl_timestrcmp(const char *a, const char *b) |
| 130 | { |
| 131 | int match = 0; |
| 132 | int i = 0; |
| 133 | |
| 134 | if(a && b) { |
| 135 | while(1) { |
| 136 | match |= a[i]^b[i]; |
| 137 | if(!a[i] || !b[i]) |
| 138 | break; |
| 139 | i++; |
| 140 | } |
| 141 | } |
| 142 | else |
| 143 | return a || b; |
| 144 | return match; |
| 145 | } |