diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 41ade79..a506e02 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -10,3 +10,7 @@ **Vulnerability:** The `is_reachable` utility function validated if an IP was correctly formatted, but did not restrict the semantic destination. This allowed Server-Side Request Forgery (SSRF) where the scanner could be manipulated into pinging loopback (`127.0.0.1`), link-local (e.g., AWS metadata `169.254.169.254`), or multicast addresses. **Learning:** Input validation is not just about syntax (like `ipaddress.ip_address`), but also about semantics and intended use. Even a simple `ping` utility can become an SSRF vector if it allows querying internal or restricted network ranges. **Prevention:** Always implement explicit allow-lists or block-lists for network destinations based on business logic. Use built-in library properties (like `ip_obj.is_loopback`, `ip_obj.is_link_local`) to robustly filter out non-routable or restricted administrative IP ranges before attempting network requests. +## 2024-05-24 - [Extended Server-Side Request Forgery Prevention in Utils] +**Vulnerability:** Even when blocking standard internal ranges (loopback, link-local, multicast, unspecified), other reserved IPs like the broadcast address (`255.255.255.255`) could still be targeted. Pinging broadcast addresses can lead to amplification attacks or unintended network noise. +**Learning:** Python's `ipaddress` module separates `is_multicast` from `is_reserved` (which includes broadcast addresses). A comprehensive SSRF defense must cover all non-standard routing destinations. +**Prevention:** Extend network block-lists to include `ip_obj.is_reserved` to catch broadcast addresses and other IETF-reserved network ranges that shouldn't be targeted in a standard scan. diff --git a/test_testping1.py b/test_testping1.py index 66a64ec..9af957a 100644 --- a/test_testping1.py +++ b/test_testping1.py @@ -98,8 +98,8 @@ def test_is_reachable_subprocess_timeout(self, mock_call): @patch('testping1.subprocess.call') def test_is_reachable_ssrf_prevention(self, mock_call): - """Test is_reachable prevents SSRF by rejecting loopback, multicast, etc.""" - ssrf_ips = ['127.0.0.1', '169.254.169.254', '224.0.0.1', '0.0.0.0'] + """Test is_reachable prevents SSRF by rejecting loopback, multicast, reserved, etc.""" + ssrf_ips = ['127.0.0.1', '169.254.169.254', '224.0.0.1', '0.0.0.0', '255.255.255.255'] for ip in ssrf_ips: with self.assertLogs(level='ERROR') as log: self.assertFalse(is_reachable(ip)) diff --git a/testping1.py b/testping1.py index d64f7db..ecea5df 100644 --- a/testping1.py +++ b/testping1.py @@ -37,8 +37,9 @@ def is_reachable(ip, timeout=1): return False # 🛡️ Sentinel: Prevent Server-Side Request Forgery (SSRF) - # Block loopback, link-local, multicast, and unspecified addresses from being pinged. - if ip_obj.is_loopback or ip_obj.is_link_local or ip_obj.is_multicast or ip_obj.is_unspecified: + # Block loopback, link-local, multicast, unspecified, and reserved addresses from being pinged. + # reserved addresses include the broadcast address (255.255.255.255) + if ip_obj.is_loopback or ip_obj.is_link_local or ip_obj.is_multicast or ip_obj.is_unspecified or ip_obj.is_reserved: logging.error(f"IP address not allowed for scanning: {ip}") return False