Monitor vulnerabilities like this one. Sign up free to get alerted when software you use is affected.
6.9

CraftCMS allows attackers to inject malicious JavaScript code

GHSA-fvwq-45qv-xvhv CVE-2026-31859
Summary

A vulnerability in CraftCMS allows an attacker to inject malicious JavaScript code into the website, potentially stealing user data. This can happen when a user clicks on a specially crafted link. To fix this issue, update CraftCMS to the latest version.

What to do
  • Update craftcms cms to version 4.17.3.
  • Update craftcms cms to version 5.9.7.
Affected software
VendorProductAffected versionsFix available
craftcms cms > 4.15.3 , <= 4.17.2 4.17.3
craftcms cms > 5.7.5 , <= 5.9.6 5.9.7
Original title
CraftCMS vulnerable to reflective XSS via incomplete return URL sanitization
Original description
### Summary

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.

### Details
The patched code in is:

```php
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:

1. **`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.

2. **`data:` URIs** -- `data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==` uses Base64 encoding and contains no tags at all, bypassing `strip_tags()` entirely.

3. **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.

### Impact

**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:

1. Victim clicks the link, visiting the Craft CMS site.
2. The application calls `setReturnUrl()` with the attacker-controlled value.
3. `strip_tags()` processes the URL but finds no HTML tags -- it passes through unchanged.
4. The URL is stored in the session and later rendered in an `href` attribute (e.g., a "Return" or "Continue" link).
5. When the victim clicks that link, `javascript:alert(document.cookie)` executes in the context of the Craft CMS origin.

This enables:
- **Session hijacking** via cookie theft (`document.cookie`)
- **Data exfiltration** via `fetch()` to an attacker-controlled server
- **Phishing** by redirecting to a lookalike domain (protocol-relative URL)
- **CSRF** by performing actions on behalf of the authenticated user
Vulnerability type
CWE-79 Cross-site Scripting (XSS)
CWE-116
Published: 11 Mar 2026 · Updated: 14 Mar 2026 · First seen: 11 Mar 2026