GHSA-m6c8-jcw2-5r25

Suggest an improvement
Source
https://github.com/advisories/GHSA-m6c8-jcw2-5r25
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-m6c8-jcw2-5r25/GHSA-m6c8-jcw2-5r25.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-m6c8-jcw2-5r25
Aliases
Published
2026-09-18T13:11:59Z
Modified
2026-09-18T13:30:05Z
Severity
  • 8.7 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N CVSS Calculator
Summary
Opencast: Stored XSS in Paella player via WebVTT/DFXP caption cue text
Details

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/mh_default_org/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/mh_default_org/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 ROLE_API_EVENTS_CREATE, ROLE_API_EVENTS_TRACK_EDIT, and ROLE_UI_TASKS_CREATE (no ROLE_ADMIN).

  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>
  1. 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
  1. 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)

Database specific
{
    "cwe_ids": [
        "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-18T13:11:59Z",
    "nvd_published_at": "2026-09-17T21:17:38Z",
    "severity": "HIGH"
}
References

Affected packages

Maven
org.opencastproject:opencast-engage-paella-player-7

Package

Name
org.opencastproject:opencast-engage-paella-player-7
View open source insights on deps.dev
Purl
pkg:maven/org.opencastproject/opencast-engage-paella-player-7

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
19.7

Affected versions

12.*
12.0
12.1
12.2
12.3
12.5
12.6
12.7
12.8
12.9
12.11
12.12
12.13
13.*
13.0
13.1
13.2
13.4
13.6
13.7
13.8
13.10
13.11
13.12
14.*
14.0
14.1
14.2
14.3
14.5
14.6
14.7
14.8
14.9
14.10
14.11
14.12
14.13
15.*
15.0
15.1
15.2
15.3
15.4
15.5
15.6
15.7
15.8
15.9
15.10
15.11
15.12
15.13
16.*
16.0
16.1
16.2
16.3
16.4
16.5
16.6
16.7
16.8
16.9
16.10
18.*
18.5

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-m6c8-jcw2-5r25/GHSA-m6c8-jcw2-5r25.json"
org.opencastproject:opencast-engage-paella-player-7

Package

Name
org.opencastproject:opencast-engage-paella-player-7
View open source insights on deps.dev
Purl
pkg:maven/org.opencastproject/opencast-engage-paella-player-7

Affected ranges

Type
ECOSYSTEM
Events
Introduced
20.0
Fixed
20.2

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-m6c8-jcw2-5r25/GHSA-m6c8-jcw2-5r25.json"
npm
paella-core

Package

Name
paella-core
View open source insights on deps.dev
Purl
pkg:npm/paella-core

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
1.50.6

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-m6c8-jcw2-5r25/GHSA-m6c8-jcw2-5r25.json"