diff --git a/HISTORY.md b/HISTORY.md index e4fd945dd8..d2879c5437 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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/` 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) ------------------- diff --git a/requests/__version__.py b/requests/__version__.py index e973b03b5f..b6453cf583 100644 --- a/requests/__version__.py +++ b/requests/__version__.py @@ -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' diff --git a/requests/adapters.py b/requests/adapters.py index fe22ff450e..3f77866d56 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -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 ` 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. @@ -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) diff --git a/requests/utils.py b/requests/utils.py index 153776c7f3..0ac98e6ace 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -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) @@ -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