59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
"""
|
|
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
|