CVE-2026-27127

EPSS 0.01%

Craft CMS has Cloud Metadata SSRF Protection Bypass via DNS Rebinding

Published: 2/23/2026Modified: 2/28/2026
Also known as:GHSA-gp2f-7wcm-5fhx

Description

## Summary The SSRF validation in Craft CMS’s GraphQL Asset mutation performs DNS resolution **separately** from the HTTP request. This Time-of-Check-Time-of-Use (TOCTOU) vulnerability enables DNS rebinding attacks, where an attacker’s DNS server returns different IP addresses for validation compared to the actual request. This is a bypass of the security fix for CVE-2025-68437 ([GHSA-x27p-wfqw-hfcc](https://github.com/craftcms/cms/security/advisories/GHSA-x27p-wfqw-hfcc)) that allows access to all blocked IPs, not just IPv6 endpoints. ## Severity Bypass of cloud metadata SSRF protection for all blocked IPs ## Required Permissions Exploitation requires GraphQL schema permissions for: - Edit assets in the `<VolumeName>` volume - Create assets in the `<VolumeName>` volume These permissions may be granted to: - Authenticated users with appropriate GraphQL schema access - Public Schema (if misconfigured with write permissions) --- ## Technical Details ### Vulnerable Code Flow The code at `src/gql/resolvers/mutations/Asset.php` performs two separate DNS lookups: ```php // VALIDATION PHASE: First DNS resolution at time T1 private function validateHostname(string $url): bool { $hostname = parse_url($url, PHP_URL_HOST); $ip = gethostbyname($hostname); // DNS Lookup #1 - Returns safe IP if (in_array($ip, [ '169.254.169.254', // AWS, GCP, Azure IMDS '169.254.170.2', // AWS ECS metadata '100.100.100.200', // Alibaba Cloud '192.0.0.192', // Oracle Cloud ])) { return false; // Check passes - IP looks safe } return true; } // ... time gap between validation and request ... // REQUEST PHASE: Second DNS resolution at time T2 (inside Guzzle) $response = $client->get($url); // DNS Lookup #2 - Guzzle resolves DNS AGAIN // Now returns 169.254.169.254! ``` ### Root Cause Two separate DNS lookups occur: 1. **Validation**: `gethostbyname()` in `validateHostname()` 2. **Request**: Guzzle's internal DNS resolution via libcurl An attacker controlling a DNS server can return different IPs for each query. ### Bypass Mechanism ``` +-----------------------------------------------------------------------------+ | Attacker's DNS Server: evil.attacker.com | +-----------------------------------------------------------------------------+ | Query 1 (Validation - T1): | | Request: A record for evil.attacker.com | | Response: 1.2.3.4 (safe IP, TTL: 0) | | Result: Validation PASSES | +-----------------------------------------------------------------------------+ | Query 2 (Guzzle Request - T2): | | Request: A record for evil.attacker.com | | Response: 169.254.169.254 (metadata IP, TTL: 0) | | Result: Request goes to blocked IP -> CREDENTIALS STOLEN | +-----------------------------------------------------------------------------+ ``` --- ## Target Endpoints via DNS Rebinding DNS rebinding allows access to all blocked IPs: | Target | Rebind To | Impact | |--------|-----------|--------| | **AWS IMDS** | `169.254.169.254` | IAM credentials, instance identity | | **AWS ECS** | `169.254.170.2` | Container credentials | | **GCP Metadata** | `169.254.169.254` | Service account tokens | | **Azure Metadata** | `169.254.169.254` | Managed identity tokens | | **Alibaba Cloud** | `100.100.100.200` | Instance credentials | | **Oracle Cloud** | `192.0.0.192` | Instance metadata | | **Internal Services** | `127.0.0.1`, `10.x.x.x` | Internal APIs, databases | --- ### Attack Scenario 1. Attacker sets up DNS server with alternating responses 2. Attacker sends mutation with `url: "http://evil.attacker.com/latest/meta-data/"` 3. First DNS query returns safe IP (e.g., `1.2.3.4`) → validation passes 4. Second DNS query returns metadata IP (`169.254.169.254`) → request to metadata 5. Attacker retrieves credentials from ANY cloud provider 6. **Attacker can now achieve code execution by creating new instances with their SSH key** --- ## Remediation ### Fix: DNS Pinning with CURLOPT_RESOLVE Pin the DNS resolution - use the same resolved IP for both validation and request: ```php private function validateHostname(string $url): bool { $hostname = parse_url($url, PHP_URL_HOST); // Resolve once $ip = gethostbyname($hostname); // Validate the resolved IP if (in_array($ip, [ '169.254.169.254', '169.254.170.2', '100.100.100.200', '192.0.0.192', ])) { return false; } // Store for later use $this->pinnedDNS[$hostname] = $ip; return true; } // When making the request - CRITICAL: Use pinned IP protected function makeRequest(string $url): ResponseInterface { $hostname = parse_url($url, PHP_URL_HOST); $ip = $this->pinnedDNS[$hostname] ?? null; $options = []; if ($ip) { // Force Guzzle/curl to use the SAME IP we validated $options['curl'] = [ CURLOPT_RESOLVE => [ "$hostname:80:$ip", "$hostname:443:$ip" ] ]; } return $this->client->get($url, $options); } ``` ### Alternative: Single Resolution with Immediate Use ```php // Resolve to IP and use IP directly in URL $ip = gethostbyname($hostname); if (in_array($ip, $blockedIPs)) { return false; } // Make request directly to IP with Host header $client->get("http://$ip" . parse_url($url, PHP_URL_PATH), [ 'headers' => [ 'Host' => $hostname ] ]); ``` ### Additional Mitigations | Mitigation | Description | |------------|-------------| | DNS Pinning (CURLOPT_RESOLVE) | Force same IP for validation and request | | Single IP-based request | Use resolved IP directly in URL | | Implement IMDSv2 | Requires token header (infrastructure-level) | | Network egress filtering | Block metadata IPs at network level | --- ## Resources - https://github.com/craftcms/cms/commit/a4cf3fb63bba3249cf1e2882b18a2d29e77a8575 - [GHSA-x27p-wfqw-hfcc](https://github.com/craftcms/cms/security/advisories/GHSA-x27p-wfqw-hfcc) - Original SSRF vulnerability (CVE-2025-68437) - [DNSrebinder](https://github.com/mogwailabs/DNSrebinder) - Lightweight Python DNS server for testing DNS rebinding vulnerabilities; responds with legitimate IP for first N queries, then rebinds to target IP - [Singularity DNS Rebinding Tool](https://github.com/nccgroup/singularity) - [rbndr DNS Rebinding Service](https://github.com/taviso/rbndr) - [DNS Rebinding Attacks Explained](https://unit42.paloaltonetworks.com/dns-rebinding/) - [CURLOPT_RESOLVE Documentation](https://curl.se/libcurl/c/CURLOPT_RESOLVE.html) - OWASP SSRF Prevention Cheat Sheet

Affected packages (1)

CVSS scores

SourceVersionSeverityVector
osvCVSS 4.0CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N

References (10)