The fix for CVE-2025-35939 in craftcms/cms introduced a strip_tags() call in src/web/User.php to sanitize return URLs before they are stored in the session. However, strip_tags() only removes HTML tags (angle brackets) -- it does not inspect or filter URL schemes. Payloads like javascript:alert(document.cookie) contain no HTML tags and pass through strip_tags() completely unmodified, enabling reflected XSS when the return URL is rendered in an href attribute.
The patched code in is:
public function setReturnUrl($url): void
{
parent::setReturnUrl(strip_tags($url));
}
strip_tags() removes HTML tags (e.g., <script>, <img>) from a string, but it is not a URL sanitizer. When the sanitized return URL is subsequently rendered in an href attribute context (e.g., <a href="{{ returnUrl }}">), the following dangerous payloads survive strip_tags() completely unmodified:
javascript: protocol URLs -- javascript:alert(document.cookie) contains no HTML tags, so strip_tags() returns it verbatim. When placed in an href, clicking the link executes the JavaScript.
data: URIs -- data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg== uses Base64 encoding and contains no tags at all, bypassing strip_tags() entirely.
Protocol-relative URLs -- //evil.com/steal contains no tags and is passed through unchanged. When rendered as an href, the browser resolves it relative to the current page’s protocol, redirecting the user to an attacker-controlled domain.
The core issue is that strip_tags() operates on HTML syntax (angle brackets) while the threat model here requires URL scheme validation. These are fundamentally different security concerns.
Reflected XSS via crafted return URL. An attacker constructs a malicious link such as https://target.example.com/craft/?returnUrl=javascript:alert(document.cookie) and sends it to a victim. The attack flow is:
setReturnUrl() with the attacker-controlled value.strip_tags() processes the URL but finds no HTML tags -- it passes through unchanged.href attribute (e.g., a "Return" or "Continue" link).javascript:alert(document.cookie) executes in the context of the Craft CMS origin.This enables:
document.cookie)fetch() to an attacker-controlled server{
"cwe_ids": [
"CWE-116",
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-11T00:26:13Z",
"nvd_published_at": "2026-03-11T18:16:24Z",
"severity": "MODERATE"
}