# Craft CMS has Cloud Metadata SSRF Protection Bypass via DNS Rebinding

Craft CMS GraphQL Asset mutation validates SSRF hostnames with a separate DNS lookup from Guzzle's request, enabling DNS rebinding to reach cloud metadata IPs and steal credentials.

- Payout: Research
- Severity: high
- Program: Craft CMS
- Found by: RajChowdhury240, rlarabee
- Disclosed: 2026-02-23
- CVE: CVE-2026-27127
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-gp2f-7wcm-5fhx
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-gp2f-7wcm-5fhx
- Tags: area:web domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:graphql tech:php technique:incomplete-fix technique:race-window vuln:access-control vuln:ssrf

**Target:** GraphQL Asset mutation resolver (src/gql/resolvers/mutations/Asset.php) fetching remote asset URLs
**Root cause:** validateHostname() resolves the hostname via gethostbyname() and checks the IP, but Guzzle/libcurl performs its own independent DNS resolution when the request is made, so an attacker-controlled DNS server can return a safe IP at validation time and a blocked IP at request time.
**Steps:**
1. Obtain GraphQL schema permissions to create/edit assets in a volume (or abuse a public schema misconfigured with write perms).
2. Stand up an attacker DNS server returning a safe IP (e.g. `1.2.3.4`, TTL 0) on the first query and `169.254.169.254` on subsequent queries (tools: DNSrebinder, Singularity, rbndr).
3. Send the asset mutation with `url: "http://evil.attacker.com/latest/meta-data/"`.
4. First DNS query (validation) returns safe IP so validateHostname() passes; second DNS query (Guzzle) returns the metadata IP.
5. Read the metadata response returned/stored by the asset fetch to exfiltrate IAM/instance credentials.
**How found:** Noticed the fix for CVE-2025-68437 validated the hostname with a separate gethostbyname() call rather than pinning the resolved IP used by the HTTP client, implying a TOCTOU gap exploitable via DNS rebinding.
**Exploitation:** Attacker with asset write GraphQL permissions points an asset URL at their rebinding domain; validation resolves to a benign IP, then Guzzle re-resolves to 169.254.169.254 (or 169.254.170.2, 100.100.100.200, 192.0.0.192, 127.0.0.1, 10.x.x.x) and fetches cloud metadata, returning credentials the attacker then uses to create instances with their SSH key for code execution.
**Escalation:** Metadata credentials (IAM role / managed identity / service account tokens) escalate SSRF to cloud account compromise and RCE via new instances with attacker SSH keys.
**Insight:** When auditing SSRF fixes, check whether validation resolves DNS independently of the HTTP client (Guzzle/libcurl); any separate gethostbyname()/getaddrinfo() before the request is rebindable unless the IP is pinned via CURLOPT_RESOLVE or the request targets the resolved IP directly.

