-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ClientSession behavior] Authorization header dropped during HTTP redirect #5783
Comments
Thanks for the detailed write-up! This indeed sounds like it was designed with security in mind. We'll need to see what the RFCs say about the scenario you described before making any decisions on changing anything. |
It was added in PR #2328 as a fix for the #1699. #1699 is about two different hosts. Moreover, Andrew added a test for this case: aiohttp/tests/test_client_functional.py Lines 2336 to 2345 in 09ac1cb
Seems like it was designed with the different hosts in mind, and |
Fair enough. PRs with a better regression test (per https://pganssle-talks.github.io/xfail-lightning/) followed by the fix is welcome. |
There are some valid cases where a user gets redirected to a different host for OAuth. I think it would be very useful if aiohttp could at least make this behavior optional. |
In that case, disable automatic redirection, copy whatever headers you need, and make a new request. It's just a few lines of code. I think it's wrong for us to bake in such potential for a security vulnerability in the core of the framework. |
I had this issue where the server redirected HTTP to HTTPS, aiohttp dropped the Authorization header. |
I suspect that allowing HTTP -> HTTPS redirect would be OK, if a simple change. But, it obviously makes sense to start your request with HTTPS anyway. |
Send the same header to a different host? That doesn't sound safe or reasonable at all. aiohttp wouldn't know of the header semantics of the second server. Plus, the security point still stands. What does RFC say? Why can't the users redirect manually, after carefully examining the previous response and their app context? |
So urllib3 follows the RFC: urllib3/urllib3#1346 So the PR that resolved this issue was likely incorrect to begin with. The clients that implement this correctly, use a realm check that takes into account all of schema, hostname and port as the RFC says that the header should be dropped if the canonical root URI is different. And I understand why — a different port means that a different web server program may end up receiving the request. P.S. Note that cURL is not always RFC-compliant because it's low-level, and the original example may mean that it adds all the user-requested headers to all the requests w/o analyzing them. @Dreamsorcerer it appears this behavior should be hidden behind an |
Oh, I thought they were talking about same-host (forgot that this is what we already do).
Could do, though I'm not too worried when it's http -> https with the same host. The urllib3 one you linked has a configurable list of headers to drop, so unsafe behaviour can be done by setting it to an empty list. |
🐞 Describe the behavior
When using
aiohttp.ClientSession
to make a GET request that carries anAuthorization
header, the header is silently dropped whenever the remote server responds with HTTP 3xx redirections to the initial request.📋 Versions
python 3.8.1 aiohttp 3.7.4.post0 multidict 4.7.6 yarl 1.5.1
💡 To Reproduce
Script to reproduce the behavior using the Discord HTTP API and a non-sensitive OAuth2 access token (note that the access tokens expires on Jun 14 2021, although the behavior should still be observable through
Response.request_info.headers
):Save as
redirect.py
and runOutput:
💡 Expected behavior
Provided that the authorization info is valid, I expect that the remote server not return an HTTP 401 Unauthorized response due to
aiohttp
dropping the auth header.📋 Additional context
This was raised a year ago in #4568.
These are the specific lines causing this behavior for
3.7.4.post0
:aiohttp/aiohttp/client.py
Lines 610 to 612 in 184274d
For the test script, the first URL is HTTPS, for which Discord returns HTTP 200 directly, but for the second, HTTP version of the API, Discord issues an HTTP 301 first to redirect the client to the HTTPS version.
aiohttp
then proceeds to drop theAuthorization
header because it is being redirected to a different origin (from http://discord.com to https://discord.com).This seems to be done for security considerations, to prevent sending the authorization info to a different location. Notably this is also the behavior for the
requests
library (see this SO and this PR onrequests
).However, for
aiohttp
, this is undocumented and is therefore surprising for developers, especially in this scenario when the remote server would like to simply upgrade from HTTP to HTTPS but stay on the same host. It is also surprising because it is different than the behaviors seen on major browsers (even though the goal ofaiohttp
is not to emulate browser behaviors). For browsers, the Fetch API spec decides that the same request info including non-forbidden headers should be reused for all redirections (see issue 553 at whatwg/fetch).For
aiohttp
, my opinion is that it seems less surprising to at least keep the header when the redirect is to the same host/authority as in the previous request, and is done to upgrade from HTTP to HTTPS (http://discord.com
tohttps://discord.com
), instead of dropping it whenever the origin changes (emulatingcurl
, see below):and to perhaps provide some warning when it happens, or to document it somewhere.
I set up a test server at
https://aiohttp-issue4568.tonywu.org/
that unconditionally redirects any request tohttps://discord.com/api/users/@me
. It is setup to respond correctly to CORS preflights.On Chrome and Firefox, open the console while at
https://discord.com
, then run the following:The fetch will succeed without receiving an HTTP 401, meaning the Authorization header survived through redirections.
Safari on the other hand has the same behavior and drops the Authorization header.
curl
seems to be more specific: it seems to throw out the header when it is redirected to a different host, but keep it when it is upgrading on the same host:The text was updated successfully, but these errors were encountered: