# compliance-trestle has an URLSecurityValidator SSRF allowlist bypass via IPv4-mapped IPv6 and 0.0.0.0

compliance-trestle's URLSecurityValidator SSRF allowlist misses IPv4-mapped IPv6 literals and 0.0.0.0, letting malicious OSCAL profile hrefs reach cloud metadata and loopback services.

- Payout: Research
- Severity: high
- Program: compliance-trestle
- Found by: tonghuaroot
- Disclosed: 2026-08-12
- CVE: CVE-2026-52776
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-h47f-gmjp-m7rr
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-h47f-gmjp-m7rr
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:python technique:parser-differential technique:source-review vuln:access-control vuln:ssrf

**Target:** URLSecurityValidator in trestle/core/remote/security.py used by HTTPSFetcher/SFTPFetcher
**Root cause:** Blocked-network membership checks compare IPv6Address objects against IPv4Network ranges, which silently returns False instead of raising, so IPv4-mapped IPv6 literals never match; 0.0.0.0/8 is also absent from ALWAYS_BLOCKED_NETWORKS and METADATA_HOSTNAMES is an exact-string set.
**Steps:**
1. Install `pip install compliance-trestle==4.0.3` and start a local dual-stack HTTP sentinel on `::` with IPV6_V6ONLY=0.
2. Instantiate `URLSecurityValidator(block_private_ips=True)`.
3. Confirm negative controls are blocked: `https://169.254.169.254:PORT/`, `https://127.0.0.1:PORT/`, `https://metadata.google.internal:PORT/`, `https://10.0.0.1:PORT/`.
4. Submit bypass URLs: `https://[::ffff:169.254.169.254]:PORT/latest/meta-data/iam/security-credentials/admin`, `https://0.0.0.0:PORT/admin`, `https://[::ffff:127.0.0.1]:PORT/admin`, `https://[::ffff:10.0.0.1]:PORT/admin`; all pass `validate_url()`.
5. Deliver the URL via a malicious OSCAL profile `imports[*].href` or `back-matter.resources[*].rlinks[*].href` so HTTPSFetcher/SFTPFetcher calls `requests.get` on it.
**How found:** Code review of the validator's `ip_addr in network` membership check: noticed Python's `in` on mixed IPv4/IPv6 types returns False rather than raising, then enumerated equivalent address representations (IPv4-mapped IPv6, 0.0.0.0) that resolve to blocked hosts but evade the predicates; verified with a dual-stack sentinel and negative controls.
**Exploitation:** Attacker supplies an OSCAL profile/catalog whose href points at a bypass URL; when the victim runs `trestle href add`/`import`/`assemble`/`author`, HTTPSFetcher validates (passes) then fetches, returning IMDS credentials or loopback admin responses into the cached fetch output.
**Escalation:** Chaining the SSRF with IMDS role credentials yields cloud account access; 0.0.0.0 reaches Docker/Kubelet/etcd admin endpoints on any interface.
**Insight:** When auditing SSRF allowlists, test every equivalent encoding of a blocked address: `[::ffff:a.b.c.d]`, `0.0.0.0`, `[::]`, and check whether the code canonicalizes via `.ipv4_mapped` before membership tests — Python's `IPv6Address in IPv4Network` silently returns False, so a passing validator proves nothing without cross-version test cases.

## Proof of concept (copied exactly from the original report)

**Bypass URLs passed to validator**

```
attempt("BYPASS-1: IPv4-mapped IPv6 cloud-metadata", f"https://[::ffff:169.254.169.254]:{PORT}/latest/meta-data/iam/security-credentials/admin", True)
attempt("BYPASS-2: 0.0.0.0 reaches localhost", f"https://0.0.0.0:{PORT}/admin", True)
attempt("BYPASS-3: IPv4-mapped IPv6 loopback", f"https://[::ffff:127.0.0.1]:{PORT}/admin", True)
attempt("BYPASS-4: IPv4-mapped IPv6 RFC 1918", f"https://[::ffff:10.0.0.1]:{PORT}/admin", True)
```

