Multiple stored XSS vectors in the Statamic control panel, rooted in Vue rendering admin-editable fields without the v-pre directive. Because Vue compiles mustache {{ }} expressions client-side, unescaped stored input is treated as executable template code - a {{this.constructor.constructor('alert(document.cookie)')()}} payload runs in an admin’s browser. Six control-panel sections (collections, navigation, taxonomies, asset containers, globals, forms) were affected on versions ≤4.42.0. Part II chains this into full Superadmin takeover.
TL;DR
Statamic’s Control Panel uses Vue.js to render many admin views. Vue compiles and evaluates mustache expressions like {{ ... }} in the browser - so {{7*7}} becomes 49 in the client-side DOM while the server still stores the literal {{7*7}}.
Because of this, unescaped user input inside {{ }} can be treated as executable template code in the browser when the component isn’t protected with v-pre.
My research proved, that it is possible to trigger an xss in such an environment, by simply using the following payload:
{{this.constructor.constructor('alert(document.cookie)')()}}
When injected and saved into an input field, that string is compiled and executed in the browser.
Local setup
- Statamic CMS: 4.42.0
- Framework: Laravel (via Laravel Sail)
- Runtime: Docker Desktop on macOS (local instance)
- DB: MySQL
Affected endpoints The following Control Panel sections were vulnerable to stored XSS (in versions up to 4.42.0):
- /collections
- /navigation
- /taxonomies
- /asset-containers
- /globals
- /forms
Silent Policy Adjustment (lex retro non agit)
After the vulnerability was responsibly disclosed, the maintainers referred to it as “an attack against oneself,” implying that they viewed it as a self-inflicted behavior rather than a legitimate security flaw resulting from the complete absence of input sanitization or output encoding. An interesting fact is that the vendor did not contest previous CVE assignments - such as GHSA-8jjh-j3c2-cjcv (CVE-2023-48701) and GHSA-vqxq-hvxw-9mv9 (CVE-2024-24570) - which addressed similar issues. This inconsistency suggests that, while comparable vulnerabilities were previously acknowledged as legitimate, this particular case was downplayed by the vendor :-(
My submission ultimately led to Statamic silently modifying the SECURITY.md file in release 4.43.0 (see the diff below) https://github.com/statamic/cms/commit/10157b806c3b4e0e80ebb0941d66ffd1864e2df5
### Qualifying Vulnerabilities ### Potentially Qualifying Vulnerabilities
-[Cross-Site Scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) -[Cross-Site Request Forgery (CSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
### Non-Qualifying Vulnerabilities... - Bugs that rely on an unlikely user interaction (i.e. the user effectively attacking themselves)After this modification I received the following message:
[…] that to us does not qualify as per our Security Policy (see Non-Qualifying Vulnerabilities item 1). If you do open a CVE report, just know that we will contest it. Do what you feel is right and we’ll do the same. All this said, we still appreciate the report, we’re just trying to do right by our user base by not creating FUD and pushing down unnecessary work across the community upgrading client sites and forcing an upgrade only to fix a non-critical issue.
In other words, the order of events was such that the Security Policy was modified first, and only after this change did the maintainers use it to disqualify my vulnerability - contrary to the old Roman principle lex retro non agit (“the law does not operate retroactively”). —>
Refusing to Let It Slide: Escalation and Takeover
Even though it was a simple XSS, without losing momentum, I decided to push further and transform this basic vulnerability into something far more impactful. Through methodical chaining of weaknesses, I was able to escalate privileges and achieve a complete Superadmin account takeover - the next blogpost documents the exploitation steps and required conditions.
References
https://github.com/statamic/cms/pull/9256
https://github.com/statamic/cms/commit/990a748335f45abd848eed6f0250d7278f30244e
