VYPR
Critical severity9.2NVD Advisory· Published Jun 19, 2026· Updated Jun 19, 2026

Craft CMS: Blind SSRF and Arbitrary JavaScript Injection via Host Header Poisoning in actionResourceJs

CVE-2026-55791

Description

1. Overview

Craft CMS is vulnerable to Server-Side Request Forgery (SSRF) and Arbitrary JavaScript Injection through the /actions/app/resource-js endpoint. By exploiting the default permissive trustedHosts configuration, an attacker can poison the Host or X-Forwarded-Host header to manipulate the application’s $baseUrl. This bypasses the endpoint’s internal URL validation, forcing the backend Guzzle client to fetch a malicious payload from an attacker-controlled server and reflect it to the client with a Content-Type: application/javascript header.

2. Vulnerability Mechanism (Root Cause) The vulnerability manifests when assetManager.cacheSourcePaths is set to false. The attack chain relies on three structural flaws and insecure defaults:

  • **A. Default Proxy Trust (trustedHosts):** Craft’s default GeneralConfig::$trustedHosts is set to ['any']. This allows an attacker to bypass front-end web server (Nginx/Apache) strict Host header validations by simply injecting an X-Forwarded-Host header. Yii2 will parse this and globally set $baseUrl to the attacker's domain.
  • **B. Insecure HTTP Client (actionResourceJs):** In AppController::actionResourceJs(), the str_starts_with($url, $baseUrl) validation is bypassed because $baseUrl is already poisoned by the attacker. The core then uses Craft::createGuzzleClient()->get($url). Unlike the GraphQL Asset fetcher, this Guzzle instance defaults to ALLOW_REDIRECTS => true.
  • C. Forced JS Content-Type: The response fetched from the attacker's server is blindly returned to the user via $this->asRaw() with the header Content-Type: application/javascript.

3. Attack Scenario & Impact (Proof of Exploitability) This endpoint acts as a proxy, taking remote, unverified content and serving it as valid JavaScript. While the direct SSRF allows for internal network probing, the most devastating impact occurs when caching layers are involved.

If the Craft CMS instance is behind a caching layer, this vulnerability leads directly to Web Cache Poisoning:

  1. An unauthenticated attacker sends the poisoned request.
  2. The caching layer caches the malicious JavaScript response for the legitimate /actions/app/resource-js URI.
  3. When an authenticated Administrator logs into the Control Panel, their browser loads the poisoned cached JavaScript (Stored XSS).
  4. The malicious script extracts window.Craft.csrfTokenValue and silently sends a POST request to /admin/actions/plugins/install-plugin, achieving 1-Click Remote Code Execution (RCE) via Session Riding.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Affected products

1

Patches

Vulnerability mechanics

Root cause

"The `actionResourceJs()` endpoint fetches arbitrary URLs via Guzzle and returns the response as JavaScript, with Host-header validation bypassable due to the default permissive `trustedHosts` configuration."

Attack vector

An unauthenticated attacker sends a request to `/actions/app/resource-js` with a crafted `url` parameter pointing to an attacker-controlled server. Because Craft's default `trustedHosts` configuration is set to `['any']`, the attacker can poison the `Host` or `X-Forwarded-Host` header to manipulate `$baseUrl`, bypassing the `str_starts_with($url, $baseUrl)` check. The backend Guzzle client then fetches the attacker's payload and reflects it with `Content-Type: application/javascript`. When a caching layer is present, this response can be cached and served to an authenticated administrator, leading to stored XSS and session-riding RCE. [ref_id=1]

Affected code

The vulnerability resides in `src/controllers/AppController.php` in the `actionResourceJs()` method, which is entirely removed by the patch. The method used `Craft::createGuzzleClient()->get($url)` to fetch arbitrary JavaScript resources and returned them with `Content-Type: application/javascript`. The patch also rewrites `src/web/assets/cp/src/js/Craft.js` to replace the jQuery-based `_appendHtml()` with native sequential script loading, removing the need for the proxy endpoint entirely.

What the fix does

The patch removes the entire `actionResourceJs()` method from `AppController.php`, eliminating the SSRF/JS-injection vector at its source. In `Craft.js`, the `_appendHtml()` function is rewritten to load scripts sequentially via native `<script>` element insertion with explicit `await` on `onload`/`onerror` events, and a `_queueAppendHtml()` method ensures that non-awaited call sites still execute in order. This removes the need for the proxy endpoint because cross-domain scripts are now loaded directly by the browser rather than proxied through the server. [patch_id=6633383]

Preconditions

  • configThe `assetManager.cacheSourcePaths` configuration must be set to `false`.
  • configThe default `trustedHosts` configuration must be set to `['any']` (the default).
  • networkThe attacker must be able to send HTTP requests to the Craft CMS instance.

Generated on Jun 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.