fix: block private image proxy targets

This commit is contained in:
jxxghp
2026-05-24 07:18:51 +08:00
parent 0273adc61c
commit 0b7854a0af
4 changed files with 191 additions and 2 deletions

View File

@@ -1,3 +1,5 @@
import ipaddress
import socket
from hashlib import sha256
from pathlib import Path
from typing import List, Optional, Set, Union
@@ -73,13 +75,52 @@ class SecurityUtils:
return False
@staticmethod
def is_safe_url(url: str, allowed_domains: Union[Set[str], List[str]], strict: bool = False) -> bool:
def _is_global_hostname(hostname: str) -> bool:
"""
判断主机名解析结果是否全部为公网地址。
图片代理会访问用户可控的 URL这里必须在 allowlist 命中前后都排除
私有、回环、链路本地、保留地址等非公网目标,避免通过 DNS 或字面量 IP
绕过域名白名单访问内网服务。
"""
if not hostname:
return False
try:
return ipaddress.ip_address(hostname).is_global
except ValueError:
pass
try:
address_infos = socket.getaddrinfo(hostname, None, type=socket.SOCK_STREAM)
except socket.gaierror:
return False
if not address_infos:
return False
for address_info in address_infos:
try:
address = ipaddress.ip_address(address_info[4][0])
except ValueError:
return False
if not address.is_global:
return False
return True
@staticmethod
def is_safe_url(
url: str,
allowed_domains: Union[Set[str], List[str]],
strict: bool = False,
block_private: bool = False,
) -> bool:
"""
验证URL是否在允许的域名列表中包括带有端口的域名
:param url: 需要验证的 URL
:param allowed_domains: 允许的域名集合,域名可以包含端口
:param strict: 是否严格匹配一级域名(默认为 False允许多级域名
:param block_private: 是否拦截解析到非公网地址的 URL防止 SSRF
:return: 如果URL合法且在允许的域名列表中返回 True否则返回 False
"""
try:
@@ -99,6 +140,9 @@ class SecurityUtils:
if not netloc:
return False
if block_private and not SecurityUtils._is_global_hostname(parsed_url.hostname or ""):
return False
# 检查每个允许的域名
allowed_domains = {d.lower() for d in allowed_domains}
for domain in allowed_domains: