SSRF 过滤器绕过: is_global 丢失 IPv6 过渡地址 (NAT64/IPv4-compat) → 云元数据
作者: Koustav-github创建于 2026年9月17日更新于 2026年9月17日
- This is actually a bug report. - [ ] I am not getting good LLM Results - [ ] I have tried asking for help in the community on discord or discussions and have not received a response. - [X] I have tried searching the documentation and have not found an answer. What Model are you using? - [x] Other: N/A — provider-agnostic. The bug is in the remote-media SSRF guard (
instructor/v2/core/remote.py), independent of the model/provider. Describe the bug The remote-media fetch guard added in #2569 validates addresses withipaddress.is_globalin_validate_public_address: if not parsed.is_global or parsed.is_multicast: raise RemoteFetchError(...) CPython classifies IPv6 transition addresses that embed an internal IPv4 as globally routable, so the guard passes them. On a host with NAT64/DNS64 routing (IPv6-only AWS/GCP subnets, many mobile/corporate networks) or dual-stack, the request is delivered to the embedded IPv4 — loopback, RFC1918, or the cloud metadata endpoint169.254.169.254— enabling SSRF and IAM-credential theft. | Host | Really targets | Current guard | ------------------- | ------------------------------ | -------------------- |64:ff9b::a9fe:a9fe| 169.254.169.254 (metadata) | ❌ accepted |64:ff9b::7f00:1| 127.0.0.1 | ❌ accepted |64:ff9b::a00:1| 10.0.0.1 | ❌ accepted |::a9fe:a9fe| 169.254.169.254 | ❌ accepted |::ffff:169.254.169.254| 169.254.169.254 | ✅ blocked |::1/127.0.0.1| loopback | ✅ blocked | The most important vector is the64:ff9b::/96NAT64 well-known prefix, which real DNS64/NAT64 deployments use. The existing tests cover::1but never the transition prefixes. (Same class as pydantic-ai CVE-2026-46678 and the fixes in ragflow/lightrag/homebox.) To Reproduce Python from instructor.v2.core import remote remote._validate_public_url("http://[64:ff9b::a9fe:a9fe]/latest/meta-data/") # -> returns None (accepted) instead of raising RemoteFetchError Python from instructor.v2.core.multimodal import Image img = Image.from_url("http://[64:ff9b::a9fe:a9fe]/x.png") # accepted On a NAT64 host, img.to_anthropic()/.to_genai() then fetch the metadata IP. Expected behavior The guard should judge an IPv6 transition address by the IPv4 it embeds, not by the wrapper. Unwrap IPv4-mapped, 6to4, Teredo, NAT64 (64:ff9b::/96, 64:ff9b:1::/48) and the deprecated IPv4-compatible ::/96 form, and reject the request when the embedded IPv4 is non-global. A NAT64 wrapper around a public IPv4 must stay allowed so IPv6-only egress keeps working. Screenshots N/A
内容来源: 567-labs/instructor