Summary
Any approved streamer can inject arbitrary HTML into /streamer and the homepage “Live streams” widget by placing markup in their Twitch/YouTube stream title. CSP is present and blocks inline script execution, but the issue is still a server-side HTML injection sink.
To trigger this, a Lichess account only needs to satisfy the normal streamer requirements and get approved. Per Streamer.canApply, that means an account older than 2 days with at least 15 games, or a verified/titled account. After moderator approval, once the streamer goes live, Lichess pulls the platform title and renders it into the UI as-is. No extra privileges are needed beyond a normal approved streamer profile.
Details
The title is taken from the upstream platform and kept as HTML:
// modules/streamer/src/main/YoutubeApi.scala
YoutubeStream(
item.snippet.channelId,
unescapeHtml(item.snippet.title),
…
)
...
// modules/streamer/src/main/TwitchApi.scala
case class HelixStream(..., title: Html, …)
case class TwitchStream(stream: HelixStream, …):
def status = stream.title
cleanStatus does not sanitize anything. It only trims the existing Html value:
// modules/streamer/src/main/model.scala
lazy val cleanStatus = status.map(s => removeMultibyteSymbols(s).trim)
Sink
The unsanitized title is rendered in both the streamer listing and the homepage widget:
// modules/streamer/src/main/ui/StreamerUi.scala
p(cls := "at")(trs.currentlyStreaming(strong(s.cleanStatus)))
Because cleanStatus is still an Html value, Scalatags renders it as raw HTML rather than escaping it. As a result, attacker-controlled markup from the Twitch/YouTube title is injected directly into the DOM in both locations.
PoC
Because Lichess has a strict CSP, this does not appear to be a straightforward full XSS in the usual sense. Inline scripts, event handlers, and similar easy execution paths are blocked.
That said, the injection is still dangerous because attacker-controlled HTML is rendered into the page, which opens the door to CSP-compatible gadget abuse. In particular, existing site JavaScript such as the tooltip behavior can potentially be abused to mislead users into clicking attacker-controlled UI or interacting with injected elements that appear legitimate.
As a simple proof of impact, we've used a <meta> refresh payload to force visitors on /streamer to be redirected off-site:
<meta http-equiv=refresh content='0;url=https://example.com'>
So while CSP makes full script execution harder, the issue is still exploitable for meaningful client-side impact, including forced navigation and UI redressing/phishing-style abuse through existing frontend gadgets.
A working PoC demonstrating the issue has been recorded:
PoC
Impact
Any approved streamer can control the HTML that gets rendered in /streamer and the homepage live widget through the stream title.
Credits
This finding was discovered and reported by XENOPS
Summary
Any approved streamer can inject arbitrary HTML into
/streamerand the homepage “Live streams” widget by placing markup in their Twitch/YouTube stream title. CSP is present and blocks inline script execution, but the issue is still a server-side HTML injection sink.To trigger this, a Lichess account only needs to satisfy the normal streamer requirements and get approved. Per
Streamer.canApply, that means an account older than 2 days with at least 15 games, or a verified/titled account. After moderator approval, once the streamer goes live, Lichess pulls the platform title and renders it into the UI as-is. No extra privileges are needed beyond a normal approved streamer profile.Details
The title is taken from the upstream platform and kept as HTML:
...
cleanStatusdoes not sanitize anything. It only trims the existingHtmlvalue:Sink
The unsanitized title is rendered in both the streamer listing and the homepage widget:
Because
cleanStatusis still anHtmlvalue, Scalatags renders it as raw HTML rather than escaping it. As a result, attacker-controlled markup from the Twitch/YouTube title is injected directly into the DOM in both locations.PoC
Because Lichess has a strict CSP, this does not appear to be a straightforward full XSS in the usual sense. Inline scripts, event handlers, and similar easy execution paths are blocked.
That said, the injection is still dangerous because attacker-controlled HTML is rendered into the page, which opens the door to CSP-compatible gadget abuse. In particular, existing site JavaScript such as the
tooltipbehavior can potentially be abused to mislead users into clicking attacker-controlled UI or interacting with injected elements that appear legitimate.As a simple proof of impact, we've used a
<meta>refresh payload to force visitors on/streamerto be redirected off-site:So while CSP makes full script execution harder, the issue is still exploitable for meaningful client-side impact, including forced navigation and UI redressing/phishing-style abuse through existing frontend gadgets.
A working PoC demonstrating the issue has been recorded:
PoC
Impact
Any approved streamer can control the HTML that gets rendered in /streamer and the homepage live widget through the stream title.
Credits
This finding was discovered and reported by XENOPS