{"id":"CVE-2026-77615","aliases":["EUVD-2026-82473","GHSA-m6c8-jcw2-5r25"],"status":"published","title":"Paella Player: Stored XSS via caption cue text","description":"Paella Player is a set of libraries to create a multi stream video player. Prior to Paella Player 2.12.11 (as used in Opencast prior to 19.7 and 20.2), there is a potential XSS attack though closed captions cue text. This vulnerability is fixed in 2.12.11.","severity":{"version":"3.1","score":8.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00559,"percentile":0.47013,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-79"],"published":"2026-09-17T20:51:26Z","modified":"2026-11-04T02:47:56Z","fixes":[{"ecosystem":"Maven","package":"org.opencastproject:opencast-engage-paella-player-7","fixed_in":["19.7","20.2"],"sources":["ghsa","osv"]},{"ecosystem":"npm","package":"paella-core","fixed_in":["1.50.6"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Maven","package":"org.opencastproject:opencast-engage-paella-player-7","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"19.7"}]}]},{"source":"ghsa","ecosystem":"Maven","package":"org.opencastproject:opencast-engage-paella-player-7","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"20.0"},{"fixed":"20.2"}]}]},{"source":"ghsa","ecosystem":"npm","package":"paella-core","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.50.6"}]}]},{"source":"osv","ecosystem":"Maven","package":"org.opencastproject:opencast-engage-paella-player-7","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"19.7"}]}],"versions":["12.0","12.1","12.11","12.12","12.13","12.2","12.3","12.5","12.6","12.7","12.8","12.9","13.0","13.1","13.10","13.11","13.12","13.2","13.4","13.6","13.7","13.8","14.0","14.1","14.10","14.11","14.12","14.13","14.2","14.3","14.5","14.6","14.7","14.8","14.9","15.0","15.1","15.10","15.11","15.12","15.13","15.2","15.3","15.4","15.5","15.6","15.7","15.8","15.9","16.0","16.1","16.10","16.2","16.3","16.4","16.5","16.6","16.7","16.8","16.9","18.5"]},{"source":"osv","ecosystem":"Maven","package":"org.opencastproject:opencast-engage-paella-player-7","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"20.0"},{"fixed":"20.2"}]}]},{"source":"osv","ecosystem":"npm","package":"paella-core","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.50.6"}]}]}],"products":[{"vendor":"opencast","product":"opencast"},{"vendor":"polimediaupv","product":"paella-player"}],"references":[{"url":"https://github.com/opencast/opencast/security/advisories/GHSA-m6c8-jcw2-5r25","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/opencast/opencast/pull/7736","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/opencast/opencast/commit/701682c635f668228c3e8fb7b4564b3294788e40","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/polimediaupv/paella-core/commit/94a36490808ac5a1f60a0745d71ec9253f6d206b","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/polimediaupv/paella-core/commit/9b2f14ec4cf55efaf4c045c77a5ed8f5ec559ab4","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/polimediaupv/paella-player/commit/6fe4af7306044198c8e91e2e7f4128428b83cf03","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/opencast/opencast/releases/tag/19.7","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/opencast/opencast/releases/tag/20.2","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/polimediaupv/paella-player/blob/a1b6c42467938a00a4b4d0b8c68435cd4f9d2a16/repos/paella-core/CHANGELOG.md?plain=1#L21","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-77615","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-m6c8-jcw2-5r25"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/77xxx/CVE-2026-77615.json","tags":["advisory"]},{"url":"https://github.com/opencast/opencast","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-77615","title":"Paella Player: Stored XSS via caption cue text","description":"Paella Player is a set of libraries to create a multi stream video player. Prior to Paella Player 2.12.11 (as used in Opencast prior to 19.7 and 20.2), there is a potential XSS attack though closed captions cue text. This vulnerability is fixed in 2.12.11.","status":"published","published":"2026-09-17T20:51:26.147Z","modified":"2026-09-22T01:50:02.242Z","cvss":[{"version":"3.1","score":8.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N","level":"high","assessor":"cna"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:20:15.712761Z"}],"euvd":[{"id":"EUVD-2026-82473","description":"Paella Player is a set of libraries to create a multi stream video player. Prior to Paella Player 2.12.11 (as used in Opencast prior to 19.7 and 20.2), there is a potential XSS attack though closed captions cue text. This vulnerability is fixed in 2.12.11.","status":"published","published":"2026-09-17T20:51:26Z","modified":"2026-09-22T01:50:02Z","cvss":[{"version":"3.1","score":8.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T17:28:03.79222Z"}],"ghsa":[{"id":"GHSA-m6c8-jcw2-5r25","title":"Opencast: Stored XSS in Paella player via WebVTT/DFXP caption cue text","description":"## Summary\n\nThe 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.\n\nThe 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.\n\nA 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.\n\nResult: 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.\n\n## Affected\n\nopencast/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.\nDefault 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.\nCondition: an event with a caption track published to the engage player. No non-default flag required.\n\n## Root cause\n\nThe 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.\n\n## Reproduction\n\nDefault 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`).\n\n1. 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.\n\n```\nWEBVTT\n\n00:00:00.000 --\u003e 00:00:30.000\n\u003cimg src=x onerror=document.title=window.__xss=document.domain\u003e\n```\n\n2. The anonymous search manifest then exposes the caption and serves the cue raw.\n\n```\nGET /search/episode.json?id=\u003cevent\u003e\n\"type\":\"captions/source\", \"url\":\".../static/.../x.vtt\"\nGET .../static/.../x.vtt  -\u003e  cue text returned verbatim\n```\n\n3. 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.\n\nLive-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 `\u003cimg\u003e` 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.\n\n## Impact\n\n- JavaScript execution in the Opencast origin in the session of any viewer who enables captions on the event.\n- Anonymous viewers and authenticated staff are equally affected; an instructor or admin viewer exposes that session context to the script.\n- Session and CSRF-token theft, actions performed as the victim against the Opencast REST API.\n- Stored by a non-admin content author, triggered by viewing with captions on, no attacker authentication at view time.\n\n## Credit\n\nJan Kahmen, [turingpoint](https://www.turingpoint.de) (jan@turingpoint.de)","status":"published","published":"2026-09-18T13:11:59Z","modified":"2026-09-18T13:12:03Z","cvss":[{"version":"3.1","score":8.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:41:18.357514Z"}],"nvd":[{"id":"CVE-2026-77615","description":"Paella Player is a set of libraries to create a multi stream video player. Prior to Paella Player 2.12.11 (as used in Opencast prior to 19.7 and 20.2), there is a potential XSS attack though closed captions cue text. This vulnerability is fixed in 2.12.11.","status":"published","published":"2026-09-17T21:17:38.557Z","modified":"2026-09-24T21:25:27.05Z","cvss":[{"version":"3.1","score":8.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:12:49.70655Z"}],"osv":[{"id":"CVE-2026-77615","title":"Paella Player: Stored XSS via caption cue text","description":"Paella Player is a set of libraries to create a multi stream video player. Prior to Paella Player 2.12.11 (as used in Opencast prior to 19.7 and 20.2), there is a potential XSS attack though closed captions cue text. This vulnerability is fixed in 2.12.11.","status":"published","published":"2026-09-17T20:51:26.147Z","modified":"2026-11-04T02:47:56.982382941Z","cvss":[{"version":"3.1","score":8.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-79"],"updated_at":"2026-11-04T02:52:28.3084Z"},{"id":"GHSA-m6c8-jcw2-5r25","title":"Opencast: Stored XSS in Paella player via WebVTT/DFXP caption cue text","description":"## Summary\n\nThe 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.\n\nThe 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.\n\nA 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.\n\nResult: 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.\n\n## Affected\n\nopencast/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.\nDefault 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.\nCondition: an event with a caption track published to the engage player. No non-default flag required.\n\n## Root cause\n\nThe 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.\n\n## Reproduction\n\nDefault 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`).\n\n1. 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.\n\n```\nWEBVTT\n\n00:00:00.000 --\u003e 00:00:30.000\n\u003cimg src=x onerror=document.title=window.__xss=document.domain\u003e\n```\n\n2. The anonymous search manifest then exposes the caption and serves the cue raw.\n\n```\nGET /search/episode.json?id=\u003cevent\u003e\n\"type\":\"captions/source\", \"url\":\".../static/.../x.vtt\"\nGET .../static/.../x.vtt  -\u003e  cue text returned verbatim\n```\n\n3. 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.\n\nLive-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 `\u003cimg\u003e` 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.\n\n## Impact\n\n- JavaScript execution in the Opencast origin in the session of any viewer who enables captions on the event.\n- Anonymous viewers and authenticated staff are equally affected; an instructor or admin viewer exposes that session context to the script.\n- Session and CSRF-token theft, actions performed as the victim against the Opencast REST API.\n- Stored by a non-admin content author, triggered by viewing with captions on, no attacker authentication at view time.\n\n## Credit\n\nJan Kahmen, [turingpoint](https://www.turingpoint.de) (jan@turingpoint.de)","status":"published","published":"2026-09-18T13:11:59Z","modified":"2026-09-18T13:30:05.758061179Z","cvss":[{"version":"3.1","score":8.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:46:51.463834Z"}]}}
