{"id":"GHSA-fvwq-45qv-xvhv","summary":"CraftCMS vulnerable to reflective XSS via incomplete return URL sanitization","details":"### Summary\n\nThe 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.\n\n### Details\nThe patched code in is:\n\n```php\npublic function setReturnUrl($url): void\n{\n    parent::setReturnUrl(strip_tags($url));\n}\n```\n\n`strip_tags()` removes HTML tags (e.g., `\u003cscript\u003e`, `\u003cimg\u003e`) 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., `\u003ca href=\"{{ returnUrl }}\"\u003e`), the following dangerous payloads survive `strip_tags()` completely unmodified:\n\n1. **`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.\n\n2. **`data:` URIs** -- `data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==` uses Base64 encoding and contains no tags at all, bypassing `strip_tags()` entirely.\n\n3. **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.\n\nThe 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.\n\n### Impact\n\n**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:\n\n1. Victim clicks the link, visiting the Craft CMS site.\n2. The application calls `setReturnUrl()` with the attacker-controlled value.\n3. `strip_tags()` processes the URL but finds no HTML tags -- it passes through unchanged.\n4. The URL is stored in the session and later rendered in an `href` attribute (e.g., a \"Return\" or \"Continue\" link).\n5. When the victim clicks that link, `javascript:alert(document.cookie)` executes in the context of the Craft CMS origin.\n\nThis enables:\n- **Session hijacking** via cookie theft (`document.cookie`)\n- **Data exfiltration** via `fetch()` to an attacker-controlled server\n- **Phishing** by redirecting to a lookalike domain (protocol-relative URL)\n- **CSRF** by performing actions on behalf of the authenticated user","aliases":["CVE-2026-31859"],"modified":"2026-03-13T10:56:24.729727Z","published":"2026-03-11T00:26:13Z","database_specific":{"cwe_ids":["CWE-116","CWE-79"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-03-11T00:26:13Z","nvd_published_at":"2026-03-11T18:16:24Z"},"references":[{"type":"WEB","url":"https://github.com/craftcms/cms/security/advisories/GHSA-fvwq-45qv-xvhv"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-31859"},{"type":"WEB","url":"https://github.com/craftcms/cms/commit/cc9921c14897ee2b592a431c2356af8a04ce4cfe"},{"type":"PACKAGE","url":"https://github.com/craftcms/cms"}],"affected":[{"package":{"name":"craftcms/cms","ecosystem":"Packagist","purl":"pkg:composer/craftcms/cms"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.15.3"},{"fixed":"4.17.3"}]}],"versions":["4.15.3","4.15.4","4.15.5","4.15.6","4.15.6.1","4.15.6.2","4.15.7","4.16.0","4.16.1","4.16.10","4.16.11","4.16.12","4.16.13","4.16.14","4.16.15","4.16.16","4.16.17","4.16.18","4.16.19","4.16.2","4.16.3","4.16.4","4.16.5","4.16.6","4.16.6.1","4.16.7","4.16.8","4.16.9","4.16.9.1","4.17.0","4.17.0-beta.1","4.17.0-beta.2","4.17.1","4.17.2"],"database_specific":{"last_known_affected_version_range":"\u003c= 4.17.2","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-fvwq-45qv-xvhv/GHSA-fvwq-45qv-xvhv.json"}},{"package":{"name":"craftcms/cms","ecosystem":"Packagist","purl":"pkg:composer/craftcms/cms"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.7.5"},{"fixed":"5.9.7"}]}],"versions":["5.7.10","5.7.11","5.7.5","5.7.6","5.7.7","5.7.8","5.7.8.1","5.7.8.2","5.7.9","5.8.0","5.8.1","5.8.10","5.8.11","5.8.12","5.8.13","5.8.13.1","5.8.13.2","5.8.14","5.8.15","5.8.16","5.8.17","5.8.18","5.8.19","5.8.2","5.8.20","5.8.21","5.8.22","5.8.23","5.8.3","5.8.4","5.8.5","5.8.6","5.8.7","5.8.8","5.8.9","5.9.0","5.9.0-beta.1","5.9.0-beta.2","5.9.1","5.9.2","5.9.3","5.9.4","5.9.5","5.9.6"],"database_specific":{"last_known_affected_version_range":"\u003c= 5.9.6","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-fvwq-45qv-xvhv/GHSA-fvwq-45qv-xvhv.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N"}]}