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
| Vendor | Product | Affected versions | Fix 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
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