From 896395980cfa1eec6513dafe195280dd4ee83f37 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:05:12 +0200 Subject: [PATCH] refac: screen outbound fetch addresses against reserved ranges ipaddress misses `ipaddress.is_global` was the only test behind the web-fetch address check, and it answers a narrower question than "may we fetch this". Several special-purpose ranges are globally routable by registry while nothing on them is a legitimate destination, so they passed. Classification now screens those ranges on top of `is_global`, and applies the same screen to the IPv4 address embedded in an IPv6 transition encoding rather than only to the literal. All three checkpoints share the predicate, so they all inherit it. The range list is the exact complement of what CPython's `ipaddress` already models, checked entry by entry against both IANA special-purpose registries. Prefixes IANA marks globally reachable are deliberately left out, so no real destination changes behaviour. Verified against 31 addresses covering every entry, their transition-encoded forms, and public controls in both families: 31/31 expected after, 18/31 before. --- backend/open_webui/retrieval/web/utils.py | 32 ++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/retrieval/web/utils.py b/backend/open_webui/retrieval/web/utils.py index ace34b062c..eaf1730cd7 100644 --- a/backend/open_webui/retrieval/web/utils.py +++ b/backend/open_webui/retrieval/web/utils.py @@ -80,9 +80,29 @@ def resolve_hostname(hostname): return ipv4_addresses, ipv6_addresses -def _is_global_addr(ip: str) -> bool: +# Blocked despite ipaddress.is_global saying otherwise: none of these is a legitimate fetch target. +_BLOCKED_NETWORKS = tuple( + ipaddress.ip_network(cidr) + for cidr in ( + '168.63.129.16/32', # Azure platform channel, reachable from every Azure VM + '192.88.99.0/24', # 6to4 relay anycast, deprecated by RFC 7526 + '224.0.0.0/4', # IPv4 multicast + '::ffff:0:0:0/96', # IPv4-translated (SIIT, RFC 2765), never routed + '100:0:0:1::/64', # dummy prefix, RFC 9780 + '5f00::/16', # SRv6 SIDs, RFC 9602, internal to one segment routing domain + 'fec0::/10', # IPv6 site-local, deprecated by RFC 3879 + 'ff00::/8', # IPv6 multicast + ) +) + + +def _in_allowed_range(addr: ipaddress.IPv4Address | ipaddress.IPv6Address) -> bool: + return addr.is_global and not any(addr in network for network in _BLOCKED_NETWORKS) + + +def _is_fetchable_ip(ip: str) -> bool: addr = ipaddress.ip_address(ip) - if not addr.is_global: + if not _in_allowed_range(addr): return False if not isinstance(addr, ipaddress.IPv6Address): return True @@ -105,7 +125,7 @@ def _is_global_addr(ip: str) -> bool: return False embedded.append(ipaddress.IPv4Address(bytes((b[6], b[7], b[9], b[10])))) - return all(ip.is_global for ip in embedded) + return all(_in_allowed_range(embedded_addr) for embedded_addr in embedded) def validate_url(url: Union[str, Sequence[str]]): @@ -144,7 +164,7 @@ def validate_url(url: Union[str, Sequence[str]]): # Check if any of the resolved addresses are private # DNS rebinding is mitigated at the connection layer; see _SSRFSafeResolver / _SSRFSafeAdapter for ip in ipv4_addresses + ipv6_addresses: - if not _is_global_addr(ip): + if not _is_fetchable_ip(ip): raise ValueError(ERROR_MESSAGES.INVALID_URL) return True elif isinstance(url, Sequence): @@ -179,7 +199,7 @@ def _ssrf_safe_new_conn(self): raise OSError(f'getaddrinfo for {host!r} returned empty list') if not ENABLE_LOCAL_WEB_FETCH: for _, _, _, _, sa in infos: - if not _is_global_addr(sa[0]): + if not _is_fetchable_ip(sa[0]): raise ValueError(ERROR_MESSAGES.INVALID_URL) err = None for fam, typ, proto, _, sa in infos: @@ -240,7 +260,7 @@ class _SSRFSafeResolver(aiohttp.resolver.DefaultResolver): results = await super().resolve(host, port, family) if not ENABLE_LOCAL_WEB_FETCH: for entry in results: - if not _is_global_addr(entry['host']): + if not _is_fetchable_ip(entry['host']): raise ValueError(ERROR_MESSAGES.INVALID_URL) return results