From a90bcfbe313445692292c7769253ff06d601f290 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 02:32:50 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20Fi?= =?UTF-8?q?x=20SSRF=20bypass=20via=20SIIT=20(IPv4-translated)=20addresses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: HIGH 💡 Vulnerability: Python's `ipaddress` module evaluates SIIT (IPv4-translated) addresses (e.g., `::ffff:0:a.b.c.d`) as `is_global = True` and does not expose them via the `ipv4_mapped` property. This allows an attacker to bypass standard SSRF filters by embedding loopback or private IPv4 addresses within the SIIT IPv6 structure, which the host OS network stack may then natively unwrap and route. 🎯 Impact: Attackers could bypass SSRF protections to scan or interact with internal/private IPs and the loopback interface on the host machine. 🔧 Fix: Updated the manual IPv6 unpacking block in `testping1.py` to identify the SIIT prefix by checking if the high 96 bits equal `0xffff0000` (`ip_int >> 32 == 0xffff0000`). When matched, the embedded 32-bit IPv4 address is extracted and correctly subjected to the SSRF blocklist validation. ✅ Verification: Added a test case `test_is_reachable_ssrf_bypass_siit` that ensures `::ffff:0:127.0.0.1` and `::ffff:0:192.168.1.1` are correctly rejected. Verified by running the test suite (`python3 -m unittest test_testping1.py`). Co-authored-by: ManupaKDU <95234271+ManupaKDU@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ test_testping1.py | 11 +++++++++++ testping1.py | 2 ++ 3 files changed, 18 insertions(+) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 90ea24d..010d6a4 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -80,3 +80,8 @@ **Vulnerability:** Python's `ipaddress` module evaluates ISATAP addresses (e.g., `2001:db8::5efe:127.0.0.1`) as `is_global = True` and lacks a native `is_isatap` property. This allows attackers to bypass SSRF validations by embedding private or reserved IPv4 addresses inside ISATAP IPv6 structures. **Learning:** `ipaddress` module does not intrinsically unwrap and validate all forms of IPv6 encapsulation. **Prevention:** To prevent SSRF bypasses via ISATAP tunneling addresses, manually identify and unwrap the embedded IPv4 address. Extract the 32-bit ISATAP identifier using `(ip_int >> 32) & 0xFFFFFFFF` and check for `0x00005efe` or `0x02005efe`, then validate the underlying IPv4 address against SSRF rules. + +## 2025-05-18 - SSRF Bypass via SIIT (IPv4-translated) Addresses +**Vulnerability:** Attackers could bypass SSRF IP blocklists using SIIT (Stateless IP/ICMP Translation, RFC 2765) addresses. The format `::ffff:0:a.b.c.d` (using the `::ffff:0:0:0/96` prefix) evaluates as `is_global = True` in Python's `ipaddress` module and is NOT caught by the `ipv4_mapped` property. If an attacker passes such an address, the OS networking stack might route it directly to the embedded IPv4 target, bypassing internal security restrictions. +**Learning:** Python's `ipaddress` module only natively extracts standard IPv4-mapped addresses (`::ffff:a.b.c.d`), failing to recognize or unwrap SIIT IPv4-translated addresses. +**Prevention:** Always manually unwrap SIIT addresses by checking if the high 96 bits of the IPv6 integer match the SIIT prefix (`ip_int >> 32 == 0xffff0000`). If so, extract the underlying 32-bit IPv4 address using bitwise operations (`ip_int & 0xFFFFFFFF`) and validate it against the SSRF blocklist. diff --git a/test_testping1.py b/test_testping1.py index 622fbbd..17fd0a1 100644 --- a/test_testping1.py +++ b/test_testping1.py @@ -79,6 +79,17 @@ def test_is_reachable_ssrf_bypass_ipv4_mapped(self, mock_call): self.assertIn("IP address not allowed for scanning", log.output[0]) mock_call.assert_not_called() + @patch('testping1.subprocess.call') + def test_is_reachable_ssrf_bypass_siit(self, mock_call): + """Test is_reachable prevents SSRF bypass via SIIT (IPv4-translated) addresses.""" + # ::ffff:0:a.b.c.d encapsulates an IPv4 address + ssrf_ips = ['::ffff:0:127.0.0.1', '::ffff:0:192.168.1.1'] + for ip in ssrf_ips: + with self.assertLogs(level='ERROR') as log: + self.assertFalse(is_reachable(ip)) + self.assertIn("IP address not allowed for scanning", log.output[0]) + mock_call.assert_not_called() + @patch('testping1.subprocess.call') def test_is_reachable_ssrf_bypass_isatap(self, mock_call): """Test is_reachable prevents SSRF bypass via ISATAP tunneling addresses.""" diff --git a/testping1.py b/testping1.py index 9393a6b..4566891 100644 --- a/testping1.py +++ b/testping1.py @@ -143,6 +143,8 @@ def is_reachable(ip, timeout=1): unwrapped = None if ip_int >> 32 == 0x0064ff9b0000000000000000: # NAT64 64:ff9b::/96 unwrapped = ipaddress.IPv4Address(ip_int & 0xFFFFFFFF) + elif ip_int >> 32 == 0xffff0000: # SIIT (IPv4-translated) ::ffff:0:a.b.c.d + unwrapped = ipaddress.IPv4Address(ip_int & 0xFFFFFFFF) elif ip_int < 2**32 and ip_int not in (0, 1): # IPv4-compatible ::w.x.y.z unwrapped = ipaddress.IPv4Address(ip_int) else: