Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions Lib/test/test_urllib2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,46 @@ def request(conn, method, url, *pos, **kw):
fp = urllib.request.urlopen("http://python.org/path")
self.assertEqual(fp.geturl(), "http://python.org/path?query")

def redirect_with_sensitive_headers(self, from_url, to_url):
h = urllib.request.HTTPRedirectHandler()
o = h.parent = MockOpener()
req = Request(from_url)
req.add_header("Authorization", "Basic foo")
req.add_header("Cookie", "bar")
req.timeout = socket._GLOBAL_DEFAULT_TIMEOUT
h.http_error_302(req, MockFile(), 302, "",
MockHeaders({"location": to_url}))
return o.req.headers

def test_redirect_to_same_origin_with_sensitive_header(self):
for to_url in [
"http://example.com/index.html",
"http://example.com/other.html",
]:
with self.subTest(to_url):
headers = self.redirect_with_sensitive_headers(
"http://example.com/index.html", to_url)
self.assertIn("Authorization", headers)
self.assertIn("Cookie", headers)

def test_redirect_to_other_origin_with_sensitive_header(self):
# The origin includes the scheme, the host and the port, so
# credentials are not sent if any of them differs (gh-77842).
for from_url, to_url in [
# other host
("http://example.com/index.html", "http://cracker.com/index.html"),
# other port
("http://example.com/index.html", "http://example.com:8080/i.html"),
# downgrade from HTTPS to HTTP
("https://example.com/index.html", "http://example.com/index.html"),
# upgrade from HTTP to HTTPS
("http://example.com/index.html", "https://example.com/index.html"),
]:
with self.subTest(from_url=from_url, to_url=to_url):
headers = self.redirect_with_sensitive_headers(from_url, to_url)
self.assertNotIn("Authorization", headers)
self.assertNotIn("Cookie", headers)

def test_redirect_encoding(self):
# Some characters in the redirect target may need special handling,
# but most ASCII characters should be treated as already encoded
Expand Down
12 changes: 11 additions & 1 deletion Lib/urllib/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,12 +649,22 @@ def redirect_request(self, req, fp, code, msg, headers, newurl):
CONTENT_HEADERS = ("content-length", "content-type")
newheaders = {k: v for k, v in req.headers.items()
if k.lower() not in CONTENT_HEADERS}
return Request(newurl,
newrequest = Request(newurl,
method="HEAD" if m == "HEAD" else "GET",
headers=newheaders,
origin_req_host=req.origin_req_host,
unverifiable=True)

# Do not send credentials to other origin. The origin includes
# the scheme, so they are not sent if the connection is downgraded
# from HTTPS to HTTP either.
SENSITIVE_HEADERS = ("authorization", "cookie")
if (newrequest.type, newrequest.host) != (req.type, req.host):
newrequest.headers = {k: v for k, v in newrequest.headers.items()
if k.lower() not in SENSITIVE_HEADERS}

return newrequest

# Implementation note: To avoid the server sending us into an
# infinite loop, the request object needs to track what URLs we
# have already seen. Do this by adding a handler-specific
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:class:`urllib.request.HTTPRedirectHandler` no longer sends the
``Authorization`` and ``Cookie`` headers when it is redirected to other
origin. The origin includes the scheme, so they are no longer sent if the
connection is downgraded from HTTPS to HTTP either.
Loading