From 1f5fe1b237a69a92adcb68544f5b51045e7e3d9b Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 26 Jun 2026 16:45:20 +0200 Subject: [PATCH] SSRF protection: Edge case handling for CGNAT and v4/v6 mapping (Z#23236468) (#6260) * SSRF protection: Edge case handling for CGNAT and v4/v6 mapping (Z#23236468) * SMTP SSRF protection: Edge case handling for CGNAT and v4/v6 mapping (#6264) --------- Co-authored-by: pajowu --- src/pretix/base/email.py | 5 +++++ src/pretix/helpers/monkeypatching.py | 3 ++- src/tests/base/test_mail.py | 3 +++ src/tests/helpers/test_urllib.py | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py index b6a61e8a9..db3ee6299 100644 --- a/src/pretix/base/email.py +++ b/src/pretix/base/email.py @@ -57,6 +57,8 @@ logger = logging.getLogger('pretix.base.email') T = TypeVar("T", bound=EmailBackend) +_cgnat_net = ipaddress.ip_network('100.64.0.0/10') + def test_custom_smtp_backend(backend: T, from_addr: str) -> None: try: @@ -253,12 +255,15 @@ def create_connection(address, timeout=socket.getdefaulttimeout(), if not getattr(settings, "MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS", False): ip_addr = ipaddress.ip_address(sa[0]) + check_ip4 = ip_addr.ipv4_mapped if getattr(ip_addr, "ipv4_mapped", None) else ip_addr if ip_addr.is_multicast: raise socket.error(f"Request to multicast address {sa[0]} blocked") if ip_addr.is_loopback or ip_addr.is_link_local: raise socket.error(f"Request to local address {sa[0]} blocked") if ip_addr.is_private: raise socket.error(f"Request to private address {sa[0]} blocked") + if check_ip4 in _cgnat_net: + raise socket.error(f"Request to RFC 6598 address {sa[0]} blocked") sock = None try: diff --git a/src/pretix/helpers/monkeypatching.py b/src/pretix/helpers/monkeypatching.py index 2e309255d..39150e334 100644 --- a/src/pretix/helpers/monkeypatching.py +++ b/src/pretix/helpers/monkeypatching.py @@ -151,13 +151,14 @@ def monkeypatch_urllib3_ssrf_protection(): if not getattr(settings, "ALLOW_HTTP_TO_PRIVATE_NETWORKS", False): ip_addr = ipaddress.ip_address(sa[0]) + check_ip4 = ip_addr.ipv4_mapped if getattr(ip_addr, "ipv4_mapped", None) else ip_addr if ip_addr.is_multicast: raise HTTPError(f"Request to multicast address {sa[0]} blocked") if ip_addr.is_loopback or ip_addr.is_link_local: raise HTTPError(f"Request to local address {sa[0]} blocked") if ip_addr.is_private: raise HTTPError(f"Request to private address {sa[0]} blocked") - if ip_addr in _cgnat_net: + if check_ip4 in _cgnat_net: raise HTTPError(f"Request to RFC 6598 address {sa[0]} blocked") sock = None diff --git a/src/tests/base/test_mail.py b/src/tests/base/test_mail.py index 581710f48..9a5a9e66a 100644 --- a/src/tests/base/test_mail.py +++ b/src/tests/base/test_mail.py @@ -602,10 +602,13 @@ PRIVATE_IPS_RES = [ [(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('127.1.1.1', 443))], [(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('192.168.5.3', 443))], [(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('224.0.0.1', 443))], + [(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('100.64.0.1', 443))], + [(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('100.100.100.100', 443))], [(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('::1', 443, 0, 0))], [(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('fe80::1', 443, 0, 0))], [(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('ff00::1', 443, 0, 0))], [(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('fc00::1', 443, 0, 0))], + [(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('::ffff:100.64.0.1', 443, 0, 0))], ] diff --git a/src/tests/helpers/test_urllib.py b/src/tests/helpers/test_urllib.py index b50c12916..37841c591 100644 --- a/src/tests/helpers/test_urllib.py +++ b/src/tests/helpers/test_urllib.py @@ -43,6 +43,8 @@ def test_private_ip_blocked(): requests.get("https://10.0.0.1", timeout=0.1) with pytest.raises(HTTPError, match="Request to RFC 6598 address.*"): requests.get("https://100.100.100.100", timeout=0.1) + with pytest.raises(HTTPError, match="Request to RFC 6598 address.*"): + requests.get("https://[::ffff:100.64.0.1]", timeout=0.1) @pytest.mark.django_db @@ -58,6 +60,7 @@ def test_private_ip_blocked(): [(AF_INET6, SOCK_STREAM, 6, '', ('fe80::1', 443, 0, 0))], [(AF_INET6, SOCK_STREAM, 6, '', ('ff00::1', 443, 0, 0))], [(AF_INET6, SOCK_STREAM, 6, '', ('fc00::1', 443, 0, 0))], + [(AF_INET6, SOCK_STREAM, 6, "", ("::ffff:100.64.0.1", 443, 0, 0))], ]) def test_dns_resolving_to_local_blocked(res): with mock.patch('socket.getaddrinfo') as mock_addr: