Skip to content
Merged
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
21 changes: 21 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ dev

- \[Short description of non-trivial change.\]

2.27.1+security.2 (2026-05-28)
-------------------------------

**Security**

- [CVE-2024-35195] `Session` objects no longer reuse a connection pool created
with `verify=False` for subsequent requests expecting TLS verification to the
same host. Pool selection now keys on TLS settings.
(GHSA-9wx4-h78v-vm56)

- [CVE-2024-47081] `get_netrc_auth()` now uses `ri.hostname` instead of
`ri.netloc` for netrc lookups, preventing credential leakage to hosts that
share a netloc prefix with the legitimate target.
(GHSA-9hjg-9r4m-mvj7)

- [CVE-2026-25645] `extract_zipped_paths()` now extracts to a non-deterministic
path via `tempfile.mkstemp()` instead of a predictable `/tmp/<filename>` path,
preventing local pre-create attacks. Note: only affects direct callers of this
utility function, not standard requests usage.
(GHSA-gc5v-m9x4-r6x2)

2.27.1.1 (2023-10-12)
-------------------

Expand Down
2 changes: 1 addition & 1 deletion requests/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'https://requests.readthedocs.io'
__version__ = '2.27.1'
__version__ = '2.27.1+security.2'
__build__ = 0x022701
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
Expand Down
50 changes: 49 additions & 1 deletion requests/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,54 @@ def get_connection(self, url, proxies=None):

return conn

def _get_connection(self, request, verify, proxies=None):
"""Backport of CVE-2024-35195: select connection pool keyed on TLS
settings so a verify=False connection is never reused for a subsequent
request that expects TLS verification.

:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
:param verify: SSL verification setting (bool or CA bundle path).
:param proxies: (optional) Proxies dict.
:rtype: urllib3.ConnectionPool
"""
proxy = select_proxy(request.url, proxies)
parsed = urlparse(request.url)
scheme = parsed.scheme.lower()
host_params = {
'scheme': scheme,
'host': parsed.hostname,
'port': parsed.port,
}
pool_kwargs = {}
if verify is False:
pool_kwargs['cert_reqs'] = 'CERT_NONE'
else:
pool_kwargs['cert_reqs'] = 'CERT_REQUIRED'
if isinstance(verify, str):
pool_kwargs['ca_certs'] = verify

try:
if proxy:
proxy = prepend_scheme_if_needed(proxy, 'http')
proxy_url = parse_url(proxy)
if not proxy_url.host:
raise InvalidProxyURL(
"Please check proxy URL. It is malformed "
"and could be missing the host."
)
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_host(
pool_kwargs=pool_kwargs, **host_params
)
else:
conn = self.poolmanager.connection_from_host(
pool_kwargs=pool_kwargs, **host_params
)
except ValueError as e:
raise InvalidURL(e, request=request)

return conn

def close(self):
"""Disposes of any internal state.

Expand Down Expand Up @@ -410,7 +458,7 @@ def send(self, request, stream=False, timeout=None, verify=True, cert=None, prox
"""

try:
conn = self.get_connection(request.url, proxies)
conn = self._get_connection(request, verify, proxies)
except LocationValueError as e:
raise InvalidURL(e, request=request)

Expand Down
26 changes: 13 additions & 13 deletions requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,10 @@ def get_netrc_auth(url, raise_errors=False):

ri = urlparse(url)

# Strip port numbers from netloc. This weird `if...encode`` dance is
# used for Python 3.2, which doesn't support unicode literals.
splitstr = b':'
if isinstance(url, str):
splitstr = splitstr.decode('ascii')
host = ri.netloc.split(splitstr)[0]
# Backport of CVE-2024-47081: use hostname (strips port and userinfo)
# instead of manually parsing netloc, preventing netrc credential
# leakage to hosts that share a netloc prefix with the target.
host = ri.hostname

try:
_netrc = netrc(netrc_path).authenticators(host)
Expand Down Expand Up @@ -268,13 +266,15 @@ def extract_zipped_paths(path):
if member not in zip_file.namelist():
return path

# we have a valid zip archive and a valid member of that archive
tmp = tempfile.gettempdir()
extracted_path = os.path.join(tmp, member.split('/')[-1])
if not os.path.exists(extracted_path):
# use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition
with atomic_open(extracted_path) as file_handler:
file_handler.write(zip_file.read(member))
# Backport of CVE-2026-25645: extract to a non-deterministic location to
# prevent a local attacker from pre-creating a malicious file at a
# predictable path in the temp directory.
suffix = os.path.splitext(member.split('/')[-1])[-1]
fd, extracted_path = tempfile.mkstemp(suffix=suffix)
try:
os.write(fd, zip_file.read(member))
finally:
os.close(fd)
return extracted_path


Expand Down
Loading