Skip to content

Commit 7274b7f

Browse files
Merge pull request #59 from Zektopic/sentinel/fix-ipv6-scope-id-typeerror-7143134631899879133
🛡️ Sentinel: [HIGH] Fix DoS via unhandled TypeError in IPv6 scope_id validation
2 parents 1647f08 + 9c10854 commit 7274b7f

3 files changed

Lines changed: 11 additions & 11 deletions

File tree

.jules/sentinel.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@
4040
**Vulnerability:** Attackers could bypass SSRF IP blocklists (e.g., checking `is_link_local` to block 169.254.169.254) by passing the equivalent IPv4-mapped IPv6 address (e.g., `::ffff:169.254.169.254`).
4141
**Learning:** Python's `ipaddress` module does not apply all IPv4 boolean property checks to IPv4-mapped IPv6 objects. For example, `is_link_local` and `is_unspecified` return `False` for their mapped equivalents, allowing malicious inputs to bypass validation while the OS networking stack natively routes the packet to the IPv4 target.
4242
**Prevention:** To prevent SSRF bypasses via IPv4-mapped IPv6 addresses, explicitly unwrap the mapped IPv4 address using `getattr(ip_obj, 'ipv4_mapped', None)` and apply security validation checks directly to the underlying `IPv4Address` object if it exists.
43+
## 2025-02-14 - Unhandled TypeError and repr() crash via IPv6 scope_id
44+
**Vulnerability:** A Denial of Service (DoS) vulnerability existed due to an unhandled `TypeError`. If an `IPv6Address` object was provided or mutated such that its `scope_id` was an integer (e.g., `ip._scope_id = 123`), the `SCOPE_ID_REGEX.fullmatch()` call would raise a `TypeError`, crashing the worker thread. Furthermore, the fallback `repr(ip)` call in the exception block would *also* crash with a `TypeError` internally within the `ipaddress` module (`TypeError: can only concatenate str (not "int") to str`), making it impossible to even log the error securely.
45+
**Learning:** The Python `ipaddress` module's internal implementation does not strongly enforce that `scope_id` is a string after instantiation, and its `__repr__` method relies on string concatenation that fails if `scope_id` is an integer. When passing object properties to functions expecting strings (like `re.fullmatch`), or when relying on standard formatting functions like `repr()`, untrusted or manually constructed objects can trigger deep, internal unhandled exceptions.
46+
**Prevention:** Explicitly verify `type(ip_obj.scope_id) is str` before passing it to regular expressions. Do not blindly trust `repr()` on complex objects if they can be placed into invalid states; provide a safe manual formatting fallback (`f"{ip_obj.__class__.__name__}('{ip_obj.compressed}%{ip_obj.scope_id}')"`) when handling such edge cases.

plan.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
1. **Optimize polymorphic timeout validation**
2-
- In `testping1.py`, I will add a fast-path `if type(timeout) is int:` to bypass redundant string length checks and `try...except` block parsing on the hot-path when `timeout` is already an integer.
3-
2. **Run tests**
4-
- I will run `python3 -m unittest test_testping1.py` to ensure all functionality works as expected.
5-
3. **Pre-commit step**
6-
- I will use the `pre_commit_instructions` tool to run and verify all required pre-commit checks before submission.
7-
4. **Submit PR**
8-
- I will create a PR with the title "⚡ Bolt: [performance improvement]".
1+
1. Add type checking for `scope_id` in `testping1.py` before executing the regex matching, to prevent `TypeError` exceptions from unhandled types, avoiding a potential Denial of Service (DoS) issue via application crashes.
2+
2. Complete pre-commit steps to ensure proper testing, verification, review, and reflection are done.
3+
3. Submit the changes.

testping1.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ def is_reachable(ip, timeout=1):
8181
# the scope_id of IPv6 addresses. If unhandled, this can lead to argument
8282
# injection in the subprocess call or log injection.
8383
if getattr(ip_obj, 'scope_id', None):
84-
if not SCOPE_ID_REGEX.fullmatch(ip_obj.scope_id):
84+
if type(ip_obj.scope_id) is not str or not SCOPE_ID_REGEX.fullmatch(ip_obj.scope_id):
8585
try:
86-
safe_ip = repr(ip)
87-
except ValueError:
86+
# Need to handle case where scope_id is an int and repr() fails inside ipaddress module
87+
safe_ip = repr(ip) if type(ip_obj.scope_id) is str else f"{ip_obj.__class__.__name__}('{ip_obj.compressed}%{ip_obj.scope_id}')"
88+
except (ValueError, TypeError):
8889
safe_ip = "<unrepresentable>"
8990
logging.error(f"Invalid IPv6 scope ID: {safe_ip}")
9091
return False

0 commit comments

Comments
 (0)