GHSA-m6c8-jcw2-5r25: XSS
Summary
The Opencast Paella player renders caption cue text into innerHTML without escaping. The captions canvas clears captionsContainer.innerHTML and then appends each active cue with captionsContainer.innerHTML += cue, so HTML inside a WebVTT or DFXP cue becomes live DOM and executes in the Opencast origin.
The caption track is read from any media package element with a captions/ flavor and is served, with the player manifest, to anonymous viewers through /search/episode.json. The caption plugins that consume it are enabled in the default player configuration, the "Subtitles" upload that produces a captions/source track is active by default, and no caption processing step escapes the cue text.
A user who can upload a subtitle to an event and publish it stores the payload in the published caption file. Any viewer who opens the event and turns captions on runs the script.
Result: a non-admin content author stores JavaScript in a subtitle cue that executes in the browser session of every viewer who enables captions on that event, including anonymous viewers and authenticated staff.
Affected
opencast/opencast, engage-paella-player module. Supported release lines 19.x and 20.x are affected (and 18.x). Live-confirmed on 18.8 (Paella 7, paella-core 1.50.2) and 20.0 (Paella 8, paella-core 1.50.4); 19.5 ships the code-identical caption path (paella-core 1.50.4, same EpisodeConversor and default plugin config as 20.0). The captions canvas uses the same innerHTML += cue sink across these versions. Default configuration: the WebVTT and DFXP caption plugins are enabled: true in etc/ui-config/mhdefaultorg/paella7/config.json, the "Subtitles" upload option (captions/source, .vtt) is active in etc/listproviders/event.upload.asset.options.properties, and the fast workflow publishes captions/ to the engage player. Condition: an event with a caption track published to the engage player. No non-default flag required.
Root cause
The captions canvas appends each cue to captionsContainer.innerHTML in the bundled paella-core (served at /paellaN/ui/paella-player.js / the 20.x core chunk), so markup in a cue becomes live DOM. The caption entry is built from any media package element whose flavor matches captions/ at modules/engage-paella-player-7/src/js/EpisodeConversor.js:392, and /search/episode.json serves the manifest and the caption file to anonymous clients. The WebVTT and DFXP plugins that consume it are enabled by default at etc/ui-config/mhdefaultorg/paella7/config.json:571 and :574. The cue text is not HTML-escaped before assignment to innerHTML, and partial-process-uploaded-captions only cuts and tags the file, it never sanitizes it. Opencast sets neither a Content-Security-Policy nor an X-Content-Type-Options header, so the injected script runs without restriction.
Reproduction
Default config, default caption plugins enabled, a non-admin user with ROLEAPIEVENTSCREATE, ROLEAPIEVENTSTRACKEDIT, and ROLEUITASKSCREATE (no ROLEADMIN).
1. As the non-admin user, create an event, then upload a WebVTT subtitle as captions/source whose cue text is an XSS payload, and publish the event.
WEBVTT
00:00:00.000 --> 00:00:30.000 <img src=x onerror=document.title=window.xss=document.domain>
2. The anonymous search manifest then exposes the caption and serves the cue raw.
GET /search/episode.json?id=<event> "type":"captions/source", "url":".../static/.../x.vtt" GET .../static/.../x.vtt -> cue text returned verbatim
3. Open the event in the player as an anonymous viewer, open the captions menu, and select the track; the cue is written to innerHTML and the onerror handler runs.
Live-verified: on Opencast 18.8 (build 8705223) in Chrome, a non-admin author published the subtitle and an anonymous viewer enabled captions, rendering the cue as a live <img> node and setting window.xss and document.title to document.domain. On Opencast 20.0 (build d919405, Paella 8), the served core bundle contains the identical innerHTML += cue sink and the player loads the cue raw; the sink executes JavaScript in the engage origin when fed the player's own published caption file.
Impact
- JavaScript execution in the Opencast origin in the session of any viewer who enables captions on the event. - Anonymous viewers and authenticated staff are equally affected; an instructor or admin viewer exposes that session context to the script. - Session and CSRF-token theft, actions performed as the victim against the Opencast REST API. - Stored by a non-admin content author, triggered by viewing with captions on, no attacker authentication at view time.
Credit
Jan Kahmen, turingpoint (jan@turingpoint.de)
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/paella-coreto a version that resolves this vulnerability.Fixed in 1.50.6 - Upgrade
Upgrade
maven/org.opencastproject:opencast-engage-paella-player-7to a version that resolves this vulnerability.Fixed in 20.2 - Upgrade
Upgrade
maven/org.opencastproject:opencast-engage-paella-player-7to a version that resolves this vulnerability.Fixed in 19.7
Event History
Frequently Asked Questions
Who can exploit this vulnerability, and who is exposed?
A non-admin user who can upload subtitles to an event and publish it can store malicious script in a caption cue. Any viewer who opens that event and enables captions can be affected, including anonymous viewers and authenticated staff.
Are default Opencast deployments affected?
Yes. The caption plugins are enabled in the default player configuration, and the default Subtitles upload produces a captions/source track. The described caption-processing path does not escape cue text.
What user interaction is required for exploitation?
The attacker must be able to upload and publish a subtitle file containing malicious WebVTT or DFXP cue text. A viewer must open the affected event and turn captions on for the script to execute in the Opencast origin.
How can I identify potentially affected published content?
Review published events with caption tracks, particularly captions/source tracks created through the Subtitles upload workflow. Inspect their WebVTT or DFXP cue text for embedded HTML or script-like content, since cue text is rendered as live DOM.