CVE-2022-27776: Auth/cookie leak on redirect
What the report claims
Curl can be coaxed to leak Authorization and Cookie headers by redirecting request to http:// URL on the same host.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2022-27776 Auth/cookie leak on redirect
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code provides evidence against the report's central claim. Review the cited lines before deciding.
Why this finding?
- The code at lib/http.c:1904-1911 strips Authorization and Cookie headers on redirect when the host name (conn->host.name) matches the original host (data->state.first_host), using a case-insensitive comparison that ignores protocol and port.
- A redirect from https://hostname.tld to http://hostname.tld:9999 has the same host name, so the condition !strcasecompare(first_host, conn->host.name) evaluates to true, causing the headers to be skipped (not sent).
- The report claims the opposite—that headers are leaked in this scenario—but the provided code contradicts this.
- The comment 'be careful of sending this potentially sensitive header to other hosts' suggests the intent was to strip headers for different hosts, but the implemented logic strips for same host. This is a logic inversion bug, but it is not the vulnerability the report describes.
How this analysis ran 5 claims · 4 citations verified
Checked against curl master on 2022-04-21 (94ac2ca7754f). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the report. The code at lib/http.c:1904-1911 shows that Authorization and Cookie headers are stripped when following a redirect to the same host name (case-insensitive match via strcasecompare), regardless of protocol or port change. In your scenario (https://hostname.tld → http://hostname.tld:9999), the host name matches, so the headers would not be sent. Could you verify against the exact revision (94ac2ca7754f) or provide a debug trace showing the headers being sent? The logic appears inverted relative to the comment (it strips for same host instead of different host), but that would cause a different behavior than the leak you describe.
Inspect all claims and source code 5 claims · 1 code excerpts
Report claims
curl leaks Authorization and Cookie headers when following a redirect to a different protocol or port
Read the reasoning
The code explicitly checks only the host name via strcasecompare. When the host name is the same, the condition evaluates to true and the headers are stripped (the trailing ';' does nothing). The report claims the opposite—that headers are leaked in this scenario—but the provided code shows they are stripped.
The bug is located in lib/http.c at line 1904
Read the reasoning
The cited location correctly contains the relevant header-filtering logic for Authorization and Cookie headers on redirect.
An attacker can trigger the leak by causing a request with Authorization or Cookie headers to be redirected to an http:// URL on the same host
Read the reasoning
The trigger described (redirect to http:// URL on same host) would cause the host-name comparison to succeed, leading to the headers being stripped per the code at lines 1904-1911.
The confidential headers are sent over insecure HTTP to the specified port, exposing them to the attacker
Read the reasoning
The impact claim depends on the leak occurring; the code shows the leak does not occur for same-host-name redirects.
The report provides steps to reproduce using Apache2 mod_rewrite redirect and netcat listener on port 9999
Read the reasoning
The report does provide the described reproduction steps; this claim is factual about the report itself.
Read the original report
Source code
| 1879 | header as that will produce *two* in the same request! */ |
| 1880 | checkprefix("Host:", compare)) |
| 1881 | ; |
| 1882 | else if(data->state.httpreq == HTTPREQ_POST_FORM && |
| 1883 | /* this header (extended by formdata.c) is sent later */ |
| 1884 | checkprefix("Content-Type:", compare)) |
| 1885 | ; |
| 1886 | else if(data->state.httpreq == HTTPREQ_POST_MIME && |
| 1887 | /* this header is sent later */ |
| 1888 | checkprefix("Content-Type:", compare)) |
| 1889 | ; |
| 1890 | else if(conn->bits.authneg && |
| 1891 | /* while doing auth neg, don't allow the custom length since |
| 1892 | we will force length zero then */ |
| 1893 | checkprefix("Content-Length:", compare)) |
| 1894 | ; |
| 1895 | else if(data->state.aptr.te && |
| 1896 | /* when asking for Transfer-Encoding, don't pass on a custom |
| 1897 | Connection: */ |
| 1898 | checkprefix("Connection:", compare)) |
| 1899 | ; |
| 1900 | else if((conn->httpversion >= 20) && |
| 1901 | checkprefix("Transfer-Encoding:", compare)) |
| 1902 | /* HTTP/2 doesn't support chunked requests */ |
| 1903 | ; |
| 1904 | else if((checkprefix("Authorization:", compare) || |
| C1: The condition at lines 1904-1911 strips Authorization/Cookie headers on redirect when data->state.this_is_a_follow is true, first_host exists, allow_auth_to_other_hosts is false, and first_host matches conn->host.name (case-insensitive). This match is based solely on host name; protocol and port are not compared. A redirect to the same host name (e.g., https://hostname.tld → http://hostname.tld:9999) satisfies the condition, causing the headers to be skipped (not sent). | |
| C2: The Authorization/Cookie header handling logic is at lines 1904-1912 in the provided source. | |
| C4: Since the headers are stripped when the host name matches (lines 1904-1911), they are not sent over the insecure HTTP connection in the reported scenario. | |
| 1905 | checkprefix("Cookie:", compare)) && |
| 1906 | /* be careful of sending this potentially sensitive header to |
| 1907 | other hosts */ |
| 1908 | (data->state.this_is_a_follow && |
| C3: The condition requires data->state.this_is_a_follow (redirect) and data->state.first_host (original host) and !strcasecompare(first_host, conn->host.name) (host name match). In the reported scenario (same host name, different protocol/port), the host name matches, so the condition is true and the headers are not sent. | |
| 1909 | data->state.first_host && |
| 1910 | !data->set.allow_auth_to_other_hosts && |
| 1911 | !strcasecompare(data->state.first_host, conn->host.name))) |
| 1912 | ; |
| 1913 | else { |
| 1914 | #ifdef USE_HYPER |
| 1915 | result = Curl_hyper_header(data, req, compare); |
| 1916 | #else |
| 1917 | result = Curl_dyn_addf(req, "%s\r\n", compare); |
| 1918 | #endif |
| 1919 | } |
| 1920 | if(semicolonp) |
| 1921 | free(semicolonp); |
| 1922 | if(result) |
| 1923 | return result; |
| 1924 | } |
| 1925 | } |
| 1926 | headers = headers->next; |
| 1927 | } |
| 1928 | } |
| 1929 |