curl · Report #3973135
CVE-2026-86409: stale SigV4 headers sent unintended
How it was found
Summary
Setting CURLOPT_AWS_SIGV4 to NULL returns CURLE_OK but does not disable SigV4 authentication. The handler in lib/setopt.c clears the parameter string (STRING_AWS_SIGV4) but never removes the CURLAUTH_AWS_SIGV4 bit from data->set.httpauth. A subsequent curl_easy_perform() on the same handle still sends an Authorization: AWS4-HMAC-SHA256 header to whatever URL is set next.
This contradicts the documentation in docs/libcurl/opts/CURLOPT_AWS_SIGV4.md:
Using this option multiple times makes the last set string override the previous ones. Set it to NULL to disable its use again.
Tested at commit c2676bf9e639e31e44b12a2cc82cf2046231b056.
Root cause
lib/setopt.c, lines 2061-2072:
case CURLOPT_AWS_SIGV4:
result = Curl_setstropt(data, STRING_AWS_SIGV4, ptr);
if(CURL_EASY_STR(data, STRING_AWS_SIGV4))
s->httpauth = CURLAUTH_AWS_SIGV4;
break;
When ptr is NULL, Curl_setstropt clears STRING_AWS_SIGV4, but the conditional only handles the non-NULL case. There is no else branch to clear CURLAUTH_AWS_SIGV4 from s->httpauth.
For comparison, CURLOPT_HTTPSIG in the same file handles this correctly:
if(arg)
s->httpauth = (uint32_t)CURLAUTH_HTTPSIG;
else
s->httpauth &= ~(uint32_t)CURLAUTH_HTTPSIG;
On the next curl_easy_perform(), lib/transfer.c:505 copies the stale data->set.httpauth (still containing CURLAUTH_AWS_SIGV4) into data->state.authhost.want. The authentication path in lib/http.c selects SigV4, and Curl_output_aws_sigv4() proceeds. Because the parameter string is now empty, parse_sigv4_params() in lib/http_aws_sigv4.c:820 silently falls back to "aws:amz" and derives region/service from the hostname.
Impact
An application that reuses an easy handle, retains CURLOPT_USERPWD credentials, relies on the documented NULL reset, and then performs a request to an attacker-influenced URL will unintentionally send a signed SigV4 Authorization header to that destination. The header exposes the access-key identifier and a request-bound signature. The secret key is not transmitted and the signature is bound to method, host, path, headers, payload, and timestamp, which constrains replay.
Affected versions
curl 7.75.0 through 8.21.0, plus 8.22.0 prereleases before c3c70440e8.
Reproduction
- Build an affected curl release, starting with 7.75.0, with AWS SigV4 authentication support enabled.
- Start an HTTP recording server that logs the request path and complete
Authorizationheader for several requests. - Create one libcurl easy handle and configure a write callback so response data does not affect the test.
- Set test credentials through
CURLOPT_USERPWD. Nonproduction values are sufficient because the receiving server only records the generated header. - Enable SigV4 by setting
CURLOPT_AWS_SIGV4to an explicit provider, region, and service string. - Point the handle at the recording server and perform a first request.
- Confirm that the first request contains an
Authorization: AWS4-HMAC-SHA256header with the configured access-key identifier and credential scope. - Set
CURLOPT_AWS_SIGV4toNULLon the same easy handle and confirm thatcurl_easy_setopt()returnsCURLE_OK. - Change the handle's URL to a second path or destination while retaining the existing
CURLOPT_USERPWDvalue. - Perform a second request with the same easy handle and inspect the recorded headers.
- Observe the vulnerable result: the second request still contains an AWS SigV4
Authorizationheader even though SigV4 was disabled according to the documented API. - Confirm that the parameter string itself was cleared by checking that the second signature uses curl's fallback provider or derives its region and service from the new hostname rather than using the original explicit values.
- As a control, set
CURLOPT_HTTPAUTHtoCURLAUTH_NONEand perform a third request. - Confirm that the control request contains no
Authorizationheader, demonstrating that stale authentication selection caused the second header. - Confirm a fix by testing a revision containing commit
c3c70440e8, or by clearing theCURLAUTH_AWS_SIGV4bit whenCURLOPT_AWS_SIGV4receivesNULL. - Repeat the sequence and verify that the first request is signed while both requests made after the
NULLreset contain no SigV4 authorization header.
Expected result
Setting CURLOPT_AWS_SIGV4 to NULL must disable SigV4 authentication. No subsequent request should contain a SigV4 Authorization header unless the application enables it again.
Vulnerable result
Curl clears the SigV4 parameter string but retains the authentication-selection bit. Reusing the easy handle therefore signs later requests with the retained credentials and fallback parameters.
Suggested fix
Add an else branch to clear the authentication bit when the parameter becomes NULL:
case CURLOPT_AWS_SIGV4:
result = Curl_setstropt(data, STRING_AWS_SIGV4, ptr);
if(CURL_EASY_STR(data, STRING_AWS_SIGV4))
s->httpauth = CURLAUTH_AWS_SIGV4;
else
s->httpauth &= ~CURLAUTH_AWS_SIGV4;
break;
A regression test should enable SigV4, reset the option to NULL, reuse the handle, and verify that no Authorization header is sent on the subsequent request.
Impact
Summary:
An attacker who can influence a request made after the documented SigV4 reset may cause the application to sign an unintended request with retained AWS credentials. This exposes the access-key identifier and request-bound signature and, for a compatible service and operation, could authorize unintended actions. The secret key itself is not disclosed, and replay is constrained by the signed request details and timestamp.