SSRF blocklist bypass via CGNAT range (100.64.0.0/10)
Describe the bug
reported via email on 2 June 2026 - no responses since.
I am reporting an incomplete SSRF fix in BentoML that allows unauthenticated network attackers to make the server issue outbound HTTP requests to CGNAT-range addresses (RFC 6598, 100.64.0.0/10), which the existing blocklist in make_safe_connect() does not cover.
Background: CVE-2025-54381 (GHSA-mrmq-3q62-6cc8) patched SSRF in file upload processing by adding make_safe_connect() to reject connections to private, loopback, and link-local IPs. The patch is correct for those ranges but does not include the CGNAT range 100.64.0.0/10.
Root cause: Python's ipaddress module does not classify 100.64.0.0/10 as private, loopback, or link-local, so the check in uri.py lines 88-95 passes these addresses unchallenged.
Affected code:
src/bentoml/_internal/utils/uri.py -- make_safe_connect(), lines 88-95: if ip.is_private or ip.is_loopback or ip.is_link_local: raise socket.gaierror(f"Blocked private IP address {host}")
This condition evaluates to False for 100.64.1.1 (CGNAT), allowing the connection.
Trigger paths: src/_bentoml_impl/serde.py -- MultipartSerde.ensure_file(), lines 206-210 src/_bentoml_impl/serde.py -- JSONSerde.parse_request(), lines 176-183
PoC (tested on commit 71f55b5e, bentoml 1.4.39, Linux/uvloop):
Service with PIL.Image.Image input:
curl -X POST http://localhost:3000/classify -F "img=http://100.64.1.1:80/internal"
Result: connection attempted (no "blocked" error), timeout after 5 seconds -- confirmed in server log as "httpx.ConnectTimeout" rather than the expected "Connection blocked due to insecure input URL".
For comparison, a private IP is correctly blocked:
curl -X POST http://localhost:3000/classify -F "img=http://192.168.0.1/internal" Response: {"error":"Connection blocked due to insecure input URL"}
Suggested fix: Extend the blocked condition to include is_reserved and to explicitly check for CGNAT:
BLOCKED_NETWORKS = [ipaddress.ip_network("100.64.0.0/10")] # RFC 6598 CGNAT if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or any(ip in n for n in BLOCKED_NETWORKS): raise socket.gaierror(...)
Impact: Allows unauthenticated attackers to probe and potentially read from internal services reachable at CGNAT addresses. In cloud environments (AWS, GCP, ISP-managed infrastructure) where CGNAT IPs are used for internal load balancers or service endpoints, this enables meaningful SSRF exploitation.
Affected versions: all versions through 1.4.39 (latest).
To reproduce
No response
Expected behavior
No response
Environment
1.4.39 on docker
Source: bentoml/BentoML