package/python3: add patch for CVE-2026-8328

This fixes the following vulnerability:

- CVE-2026-8328:
    The ftpcp() function in Lib/ftplib.py was not updated when
    CVE-2021-4189 was fixed. While makepasv() was patched to replace
    server-supplied PASV host addresses with the actual peer address
    (getpeername()[0]), ftpcp() still calls parse227() directly and passes
    the raw attacker-controllable IP address and port to
    target.sendport(). This patch is related to CVE-2021-4189.
    https://www.cve.org/CVERecord?id=CVE-2026-8328

Signed-off-by: Titouan Christophe <titouan.christophe@mind.be>
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
This commit is contained in:
Titouan Christophe
2026-06-08 23:20:50 +02:00
committed by Thomas Perale
parent fce79a74f1
commit 4a741d4b1a
2 changed files with 120 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
From b026be60f7e023719a4d8de89ae3c3248b7c5d40 Mon Sep 17 00:00:00 2001
From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com>
Date: Wed, 13 May 2026 10:33:43 -0700
Subject: [PATCH] gh-87451: Apply CVE-2021-4189 PASV fix to ftplib.ftpcp()
(GH-149648)
ftpcp() called parse227() directly and passed the source server's
self-reported PASV IPv4 address to the target server's PORT command,
bypassing the CVE-2021-4189 fix that was applied only to FTP.makepasv().
A malicious source FTP server could use this to redirect the target
server's data connection to an arbitrary host:port (SSRF).
ftpcp() now uses the source server's actual peer address, honoring the
existing trust_server_pasv_ipv4_address opt-out, the same as makepasv().
Thanks to Qi Ding at Aurascape AI for the report. (GHSA-w8c5-q2xf-gf7c)
(cherry picked from commit eac4fe3b2c77693790a5ef7dfab127c1fee81bf9)
Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com>
Upstream: https://github.com/python/cpython/pull/149795
CVE: CVE-2026-8328
Signed-off-by: Titouan Christophe <titouan.christophe@mind.be>
---
Lib/ftplib.py | 11 +++++-
Lib/test/test_ftplib.py | 36 ++++++++++++++++++-
...6-05-10-18-05-32.gh-issue-87451.XkKB6M.rst | 6 ++++
3 files changed, 51 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst
diff --git a/Lib/ftplib.py b/Lib/ftplib.py
index 10c5d1ea08ab115..463da58de85d721 100644
--- a/Lib/ftplib.py
+++ b/Lib/ftplib.py
@@ -883,7 +883,16 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
type = 'TYPE ' + type
source.voidcmd(type)
target.voidcmd(type)
- sourcehost, sourceport = parse227(source.sendcmd('PASV'))
+ # Don't trust the IPv4 address the source server advertises in its PASV
+ # reply: a malicious source could otherwise point the target's data
+ # connection at an arbitrary host (SSRF). A caller that needs the old
+ # behavior can set trust_server_pasv_ipv4_address on the source FTP
+ # object. See FTP.makepasv(), which applies the same rule.
+ untrusted_host, sourceport = parse227(source.sendcmd('PASV'))
+ if source.trust_server_pasv_ipv4_address:
+ sourcehost = untrusted_host
+ else:
+ sourcehost = source.sock.getpeername()[0]
target.sendport(sourcehost, sourceport)
# RFC 959: the user must "listen" [...] BEFORE sending the
# transfer request.
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
index 204a77d14f03a50..7542f015f78c421 100644
--- a/Lib/test/test_ftplib.py
+++ b/Lib/test/test_ftplib.py
@@ -16,7 +16,7 @@
except ImportError:
ssl = None
-from unittest import TestCase, skipUnless
+from unittest import mock, TestCase, skipUnless
from test import support
from test.support import threading_helper
from test.support import socket_helper
@@ -1142,6 +1142,40 @@ def testTimeoutDirectAccess(self):
ftp.close()
+class TestFtpcpSecurity(TestCase):
+ """ftpcp() must not trust the host a source server advertises in PASV.
+
+ A malicious source server can otherwise redirect the target server's
+ data connection to an arbitrary host:port (SSRF), so ftpcp() uses the
+ source server's actual peer address instead, the same as FTP.makepasv().
+ """
+
+ def _make_pair(self, *, advertised_host, real_host, trust=False):
+ source = mock.Mock(spec=ftplib.FTP)
+ source.trust_server_pasv_ipv4_address = trust
+ source.sock.getpeername.return_value = (real_host, 21)
+ # PASV replies give the host as comma-separated octets, not dotted.
+ advertised = advertised_host.replace('.', ',')
+ source.sendcmd.side_effect = lambda cmd: (
+ f'227 Entering Passive Mode ({advertised},1,2).'
+ if cmd == 'PASV' else '150 ok')
+ target = mock.Mock(spec=ftplib.FTP)
+ target.sendcmd.return_value = '150 ok'
+ return source, target
+
+ def test_ftpcp_ignores_untrusted_pasv_host(self):
+ source, target = self._make_pair(advertised_host='10.0.0.5',
+ real_host='198.51.100.7')
+ ftplib.ftpcp(source, 'a', target, 'b')
+ target.sendport.assert_called_once_with('198.51.100.7', 258)
+
+ def test_ftpcp_trust_server_pasv_ipv4_address(self):
+ source, target = self._make_pair(advertised_host='10.0.0.5',
+ real_host='198.51.100.7', trust=True)
+ ftplib.ftpcp(source, 'a', target, 'b')
+ target.sendport.assert_called_once_with('10.0.0.5', 258)
+
+
class MiscTestCase(TestCase):
def test__all__(self):
not_exported = {
diff --git a/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst b/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst
new file mode 100644
index 000000000000000..21a79c3e0e7db74
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst
@@ -0,0 +1,6 @@
+The :mod:`ftplib` module's undocumented ``ftpcp`` function no longer trusts
+the IPv4 address value returned from the source server in response to the
+``PASV`` command by default, completing the fix for CVE-2021-4189. As with
+:class:`ftplib.FTP`, the former behavior can be re-enabled by setting the
+``trust_server_pasv_ipv4_address`` attribute on the source :class:`ftplib.FTP`
+instance to ``True``. Thanks to Qi Deng at Aurascape AI for the report.

View File

@@ -17,6 +17,8 @@ PYTHON3_CPE_ID_PRODUCT = python
PYTHON3_IGNORE_CVES += CVE-2026-3276
# 0014-tarfile-data_filter-validate-written-link.patch
PYTHON3_IGNORE_CVES += CVE-2026-7774
# 0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch
PYTHON3_IGNORE_CVES += CVE-2026-8328
# This host Python is installed in $(HOST_DIR), as it is needed when
# cross-compiling third-party Python modules.