Craft has a save_images_Asset graphql mutation can be abused to exfiltrate AWS credentials of underlying host
Description
Craft CMS is a content management system. In Craft versions 3.5.0 through 4.16.17 and 5.0.0-RC1 through 5.8.21, the save_images_Asset GraphQL mutation can be abused to fetch internal URLs by providing a domain name that resolves to an internal IP address, bypassing hostname validation. When a non-image file extension such as .txt is allowed, downstream image validation is bypassed, which can allow an authenticated attacker with permission to use save_images_Asset to retrieve sensitive data such as AWS instance metadata credentials from the underlying host. This issue is patched in versions 4.16.18 and 5.8.22.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Craft CMS GraphQL mutation save_images_Asset allows SSRF to internal hosts, bypassing hostname validation, potentially leaking AWS instance metadata credentials.
Craft CMS versions 3.5.0 through 4.16.17 and 5.0.0-RC1 through 5.8.21 contain a server-side request forgery (SSRF) vulnerability in the save_images_Asset GraphQL mutation [1][2]. The mutation is designed to fetch an image from a user-supplied URL, performing a basic hostname check to ensure the hostname is not an IP address but failing to guard against domain names that resolve to internal IP addresses [2]. This allows an attacker to supply a URL pointing to a domain they control which resolves to a sensitive internal IP, such as the AWS instance metadata endpoint at 169.254.169.254 [2]. The code uses parse_url and filter_var with FILTER_VALIDATE_DOMAIN and FILTER_VALIDATE_IP to reject IP addresses, but does not resolve the domain to check its resolved IP [2]. Additionally, if the attacker provides a filename with a non-image extension (e.g., .txt), downstream image validation is bypassed, allowing the fetched content to be saved as an asset and made publicly accessible [1][2].
To exploit this vulnerability, an authenticated attacker must have permission to use the save_images_Asset mutation [1]. The attacker sets up a domain with a DNS A record pointing to an internal IP such as 169.254.169.254 (AWS metadata) and invokes the mutation with a URL like http://attacker.domain/latest/meta-data/iam/security-credentials and a filename like foo.txt [2]. Craft then fetches the internal URL on behalf of the attacker and stores the response as an asset, making the sensitive data downloadable [2]. The hostname validation is bypassed because a legitimate domain name is provided, and the image validation is bypassed because the .txt extension is allowed if configured [2]. The attack surface relies on the ability to trick the server into making a request to an internal resource and storing the result.
The primary impact is the potential exfiltration of sensitive information from the underlying cloud instance, such as AWS instance metadata credentials (e.g., IAM role credentials) [1][2]. An attacker could use these credentials to gain unauthorized access to AWS resources. This is a critical SSRF vulnerability that can lead to privilege escalation and lateral movement in cloud environments [1][2]. The vulnerability is patched in Craft CMS versions 4.16.18 and 5.8.22 [1][2][3][4]. The fix introduces a validateHostname method that checks against well-known cloud metadata domains and IPs [4]. Users should update their installations immediately; if upgrading is not possible, restricting the save_images_Asset mutation or blocking internal IP ranges in network configurations may serve as temporary mitigations.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
craftcms/craftPackagist | >= 5.0.0-RC1, < 5.8.22 | 5.8.22 |
craftcms/craftPackagist | >= 3.5.0, < 4.16.18 | 4.16.18 |
Affected products
2Patches
12 files changed · +42 −6
CHANGELOG.md+1 −0 modified@@ -5,6 +5,7 @@ - The `utils/fix-field-layout-uids` command now checks for duplicate top-level field layout UUIDs. ([#18193](https://github.com/craftcms/cms/pull/18193)) - Fixed a bug where all plugin settings were being saved to the project config, rather than just posted settings. ([craftcms/commerce#4006](https://github.com/craftcms/commerce/issues/4006)) - Fixed a bug where custom selects could be positioned incorrectly after the window was resized. ([#18179](https://github.com/craftcms/cms/issues/18179)) +- Fixed an SSRF vulnerability. (GHSA-96pq-hxpw-rgh8) ## 4.16.17 - 2025-12-0421
src/gql/resolvers/mutations/Asset.php+41 −6 modified@@ -242,12 +242,7 @@ protected function handleUpload(AssetElement $asset, array $fileInformation): bo } elseif (!empty($fileInformation['url'])) { $url = $fileInformation['url']; - // make sure the hostname is alphanumeric and not an IP address - $hostname = parse_url($url, PHP_URL_HOST); - if ( - !filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) || - filter_var($hostname, FILTER_VALIDATE_IP) - ) { + if (!$this->validateHostname($url)) { throw new UserError("$url contains an invalid hostname."); } @@ -283,6 +278,46 @@ protected function handleUpload(AssetElement $asset, array $fileInformation): bo return true; } + private function validateHostname(string $url): bool + { + // make sure the hostname is alphanumeric and not an IP address + $hostname = parse_url($url, PHP_URL_HOST); + if ( + !filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) || + filter_var($hostname, FILTER_VALIDATE_IP) + ) { + return false; + } + + // Check against well-known cloud metadata domains/IPs + // h/t https://gist.github.com/BuffaloWill/fa96693af67e3a3dd3fb + if (in_array($hostname, [ + 'kubernetes.default', + 'kubernetes.default.svc', + 'kubernetes.default.svc.cluster.local', + 'metadata', + 'metadata.google.internal', + 'metadata.packet.net', + ])) { + return false; + } + + // make sure the hostname doesn’t resolve to a known cloud metadata IP + $ip = gethostbyname($hostname); + + if (in_array($ip, [ + '169.254.169.254', + '169.254.170.2', + '169.254.169.254', + '100.100.100.200', + '192.0.0.192', + ])) { + return false; + } + + return true; + } + /** * Create the guzzle client. *
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-96pq-hxpw-rgh8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25492ghsaADVISORY
- github.com/craftcms/cms/commit/e838a221df2ab15cd54248f22fc8355d47df29ffghsax_refsource_MISCWEB
- github.com/craftcms/cms/releases/tag/4.16.18ghsaWEB
- github.com/craftcms/cms/releases/tag/5.8.22ghsax_refsource_MISCWEB
- github.com/craftcms/cms/security/advisories/GHSA-96pq-hxpw-rgh8ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.