# Opencast: Stored XSS in Paella player via WebVTT/DFXP caption cue text

Paella player writes WebVTT/DFXP caption cue text to innerHTML unescaped, so a non-admin subtitle uploader stores XSS that fires for any viewer enabling captions.

- Payout: Research
- Severity: high
- Program: Opencast
- Found by: kah-ja
- Disclosed: 2026-09-18
- CVE: CVE-2026-77615
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-m6c8-jcw2-5r25
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-m6c8-jcw2-5r25
- Tags: area:web domain:web impact:ato impact:integrity kind:research platform:ghsa sev:high tech:java technique:chained technique:source-review vuln:xss

**Target:** Paella player captions canvas (paella-core) in Opencast engage player
**Root cause:** The captions canvas clears `_captionsContainer.innerHTML` then appends each active cue via `innerHTML += cue` without escaping, so markup in a WebVTT/DFXP cue becomes live DOM. Caption tracks are built from any `captions/*` media package element and served to anonymous clients via `/search/episode.json`.
**Steps:**
1. As a non-admin user with ROLE_API_EVENTS_CREATE/TRACK_EDIT/TASKS_CREATE, create an event and upload a WebVTT as `captions/source` with cue text `<img src=x onerror=document.title=window.__xss=document.domain>`, then publish.
2. Confirm anonymous manifest exposes the caption: `GET /search/episode.json?id=<event>` returns `"type":"captions/source", "url":".../static/.../x.vtt"`.
3. Fetch the caption file: `GET .../static/.../x.vtt` returns cue text verbatim.
4. Open the event in the player as anonymous viewer, open captions menu, select the track; cue is written to innerHTML and onerror fires.
**How found:** Reporter traced the caption rendering path: identified the `innerHTML += cue` sink in bundled paella-core, then followed the data flow from `EpisodeConversor.js:392` (any `captions/*` flavor) through `/search/episode.json` to the default-enabled WebVTT/DFXP plugins, and confirmed no sanitization step and no CSP/X-Content-Type-Options headers.
**Exploitation:** Attacker needs only content-author rights to upload a subtitle and publish. The payload persists in the published caption file, served to anonymous viewers. Any viewer (anonymous or authenticated staff/admin) who enables captions on that event executes the script in the Opencast origin, enabling session/CSRF-token theft and actions against the REST API as the victim.
**Escalation:** Non-admin author -> stored payload -> executes in admin/instructor viewer session, yielding session and CSRF token theft and API actions as victim.
**Insight:** Subtitle/caption tracks are attacker-controllable text rendered by media players; check player caption canvases for innerHTML sinks and confirm whether caption upload is available to low-privilege content authors and served to anonymous viewers via the search manifest.

