""" SSRF 防护:检测 URL 是否指向内网/本机地址,禁止服务端请求。 """ import ipaddress import socket from urllib.parse import urlparse _PRIVATE_NETWORKS = [ ipaddress.ip_network('0.0.0.0/8'), ipaddress.ip_network('10.0.0.0/8'), ipaddress.ip_network('100.64.0.0/10'), ipaddress.ip_network('127.0.0.0/8'), ipaddress.ip_network('169.254.0.0/16'), ipaddress.ip_network('172.16.0.0/12'), ipaddress.ip_network('192.0.0.0/24'), ipaddress.ip_network('192.168.0.0/16'), ipaddress.ip_network('198.18.0.0/15'), ipaddress.ip_network('224.0.0.0/4'), ipaddress.ip_network('240.0.0.0/4'), ipaddress.ip_network('::1/128'), ipaddress.ip_network('fc00::/7'), ipaddress.ip_network('fe80::/10'), ] _LOCAL_HOSTNAMES = { 'localhost', 'localhost.localdomain', 'metadata.google.internal', 'metadata.azure.internal', '169.254.169.254', } def is_internal_url(url): """判断 URL 是否解析到内网/本机/保留地址。解析失败视为不可信返回 True。""" if not url or not isinstance(url, str): return True parsed = urlparse(url) if parsed.scheme not in ('http', 'https'): return True host = parsed.hostname if not host: return True host_lower = host.lower().rstrip('.') if host_lower in _LOCAL_HOSTNAMES: return True try: infos = socket.getaddrinfo(host, parsed.port or 80) except socket.gaierror: return True for info in infos: try: ip = ipaddress.ip_address(info[4][0]) except ValueError: continue if any(ip in network for network in _PRIVATE_NETWORKS): return True return False