From c209018077053ebbcca530a44a4068de9a3158dc Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 03:04:22 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL/H?= =?UTF-8?q?IGH]=20Fix=20SSRF=20bypass=20via=20ISATAP=20IPv6=20addresses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a check in the SSRF blocking logic to identify ISATAP addresses (where the 32 bits before the IPv4 address are 0x00005efe or 0x02005efe), manually unwrap the encapsulated IPv4 address, and validate it against the blocklist (e.g. rejecting loopback, private, etc.). Python's ipaddress module evaluates these addresses as is_global = True and does not natively unwrap them. Included a corresponding test suite check. Co-authored-by: ManupaKDU <95234271+ManupaKDU@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ test_testping1.py | 14 ++++++++++++++ testping1.py | 8 ++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index d3e2b0a..35d589d 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -75,3 +75,8 @@ **Vulnerability:** Python's `ipaddress.ip_address()` function accepts both `str` and `bytes`. When passing an extremely large bytes object (e.g., `b"A" * 10**8`), the module can take several seconds to raise a `ValueError` due to inefficient internal parsing logic, leading to a CPU exhaustion Denial of Service (DoS). **Learning:** Checking the length of `str` inputs before passing them to `ipaddress.ip_address()` is not sufficient to prevent DoS, as an attacker could pass a massive `bytes` object if the function accepts polymorphic types. **Prevention:** Always enforce strict length limits (e.g., <= 100 characters/bytes) on *both* `str` and `bytes` inputs before attempting to parse them using the `ipaddress` module. Use `isinstance(ip, (str, bytes))` and check `len()`. + +## 2024-05-11 - SSRF Bypass via ISATAP IPv6 Addresses +**Vulnerability:** The application was vulnerable to an SSRF bypass when passed ISATAP tunneling addresses (e.g., `2001:db8::5efe:127.0.0.1` or `2001:db8::200:5efe:127.0.0.1`), which encapsulate a blocked internal IPv4 address. +**Learning:** Python's `ipaddress` module evaluates these addresses as `is_global = True` and does not provide an `isatap` property to easily extract the embedded IPv4 address. +**Prevention:** To prevent SSRF bypasses via ISATAP (RFC 5214) 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. diff --git a/test_testping1.py b/test_testping1.py index 9f39c61..d6f136c 100644 --- a/test_testping1.py +++ b/test_testping1.py @@ -281,6 +281,20 @@ def test_is_reachable_ssrf_bypass_teredo(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_isatap(self, mock_call): + """Test is_reachable prevents SSRF bypass via ISATAP addresses.""" + ssrf_ips = [ + '2001:db8::5efe:127.0.0.1', + 'fe80::5efe:192.168.1.1', + '2001:db8::200:5efe:127.0.0.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_nat64_and_compat(self, mock_call): """Test is_reachable prevents SSRF bypass via NAT64 and IPv4-compatible addresses.""" diff --git a/testping1.py b/testping1.py index aa0ff6e..941e600 100644 --- a/testping1.py +++ b/testping1.py @@ -128,14 +128,18 @@ def is_reachable(ip, timeout=1): not t_cli.is_global or t_cli.is_multicast ) else: - # 🛡️ Sentinel: Unpack NAT64 (RFC 6052) and IPv4-compatible (RFC 4291) addresses manually - # as Python's ipaddress module does not natively unwrap them for SSRF checks. + # 🛡️ Sentinel: Unpack NAT64 (RFC 6052), IPv4-compatible (RFC 4291), and ISATAP (RFC 5214) + # addresses manually as Python's ipaddress module does not natively unwrap them for SSRF checks. ip_int = int(ip_obj) unwrapped = None if ip_int >> 32 == 0x0064ff9b0000000000000000: # NAT64 64:ff9b::/96 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: + isatap_identifier = (ip_int >> 32) & 0xFFFFFFFF + if isatap_identifier == 0x00005efe or isatap_identifier == 0x02005efe: # ISATAP + unwrapped = ipaddress.IPv4Address(ip_int & 0xFFFFFFFF) if unwrapped is not None: is_blocked = not unwrapped.is_global or unwrapped.is_multicast