open-uri
open-uri copied to clipboard
Enhance security by removing Authorization header on HTTP redirects
When redirects occur, the Authorization header containing authorization information is transferred to the redirect destination host. This can expose sensitive credentials to unintended hosts, posing a security risk.
I will introduce a case where the Authorization header is unintentionally transferred during redirection.
Case
The issue occurs when downloading GitHub Actions Artifacts using the GitHub REST API.
- https://docs.github.com/ja/rest/actions/artifacts#download-an-artifact
The process is as follows.
- Authorize using the Authorization header.
- Redirect to a URL containing a Shared Access Signature (SAS) for downloading the artifact.
- Download the artifact.
During the redirect in step 2, the Authorization header from step 1 is retained and transferred to the redirect destination. This results in an authorization error due to the redirection destination misinterpreting the Authorization header content as the SAS.
Reproduce code
Here is a Ruby code example that demonstrates the problem, resulting in a 403 error because the Authorization header does not match the expected signature format.
require 'open-uri'
uri = ENV['ARTIFACT_URL']
access_token = ENV['GITHUB_ACCESS_TOKEN']
URI(uri).open("Authorization" => "token #{access_token}")
$ ARTIFACT_URL="https://api.github.com/repos/:owner/sandbox-github-actions-artifacts-v4/actions/artifacts/:artifact_id/zip" \
GITHUB_ACCESS_TOKEN="your_access_token" \
ruby download_artifact.rb
/home/kodama/.rbenv/versions/3.3.2/lib/ruby/3.3.0/open-uri.rb:376:in `open_http': 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. (OpenURI::HTTPError)
Workaround
When redirecting without transferring the Authorization header content, the download succeeds without errors. This means the Authorization header is retained during redirects.
require 'open-uri'
uri = ENV['ARTIFACT_URL']
access_token = ENV['GITHUB_ACCESS_TOKEN']
headers = {
"Authorization" => "token #{access_token}",
redirect: false
}
loop do
begin
URI.open(uri, headers)
break
rescue OpenURI::HTTPRedirect => redirect
headers.delete("Authorization")
pp uri = redirect.uri
end
end
$ ARTIFACT_URL="https://api.github.com/repos/:owner/sandbox-github-actions-artifacts-v4/actions/artifacts/:artifact_id/zip" \
GITHUB_ACCESS_TOKEN="your_access_token" \
ruby download_artifact.rb
#<URI::HTTPS https://productionresultssa11.blob.core.windows.net/actions-results/xxx>
Expected Improvement
We aim to improve security by the Authorization header is removed during redirects if necessary, preventing its inadvertent transfer to the redirect destination host.
By the Fetch specification, it recommends removing the Authorization header on cross-origin redirects.
- https://fetch.spec.whatwg.org/#http-redirect-fetch
Supplemental Information
Examples of software or specifications that do not transfer the Authorization header on redirect:
Curl
Curl does not transfer the Authorization header on redirects and this issue is reported as a CVE.
- https://github.com/curl/curl/commit/af32cd3859336ab963591ca0df9b1e33a7ee066b
- https://curl.se/docs/CVE-2018-1000007.html
GO
Go removes the Authorization header when redirecting to different hosts but retains it if the redirect is to the same host.
- https://github.com/golang/go/blob/b3040679ad0eccaaadb825ed8c0704086ecc23eb/src/net/http/client.go#L41-L49
- https://go-review.googlesource.com/c/go/+/28930
Proposed Solutions
We are considering two approaches regarding the handling of the Authorization header during redirects.
- Always remove the Authorization header when redirecting.
- Remove the Authorization header when redirect to cross-origin.
This PR implements approach 2.
based on the Fetch API specification, ensuring better compatibility and security standards.