Skip to content

security(core): escape "<" in the res.expose script island (24.05) - #7950

Open
ar2rsawseen wants to merge 1 commit into
release.24.05from
backport/expose-script-island-xss-2405
Open

security(core): escape "<" in the res.expose script island (24.05)#7950
ar2rsawseen wants to merge 1 commit into
release.24.05from
backport/expose-script-island-xss-2405

Conversation

@ar2rsawseen

Copy link
Copy Markdown
Member

Backport of #7949 to release.24.05.

frontend/express/libs/express-expose.js serializes the exposed countlyGlobal object into an inline <script> block. It neutralized only the exact </script> sequence, but the HTML tokenizer also ends a script at </script >, </script/> and other whitespace/slash spellings, so an application name containing one broke out. Because a global admin's dashboard lists every app, an app admin of one app could store such a name and have it execute in any global admin's session — an app-admin-to-global-admin escalation. Medium under SECURITY.md.

Fix (identical to #7949): escape every < as < in both serialization paths — string values and object keys (escape_js_string). < parses back to <, so values read from the exposed object are unchanged. Also render the active-app name with .text() instead of .html() in countly.template.js (the only .html() sink fed by an app name).

Verified on this branch: no raw < survives for any breakout spelling in values or keys, and an eval round-trip reproduces the input object byte-for-byte and matches the previous serializer output (no dashboard-visible change).

🤖 Generated with Claude Code

…S via app name)

Backport of #7949 to release.24.05.

The dashboard serialises the exposed countlyGlobal object into an inline
<script> block. The serialiser only neutralised the exact sequence
"</script>", but the HTML tokeniser also ends a script element at
"</script >", "</script/>" and other whitespace/slash spellings, so an
application name containing one broke out of the script block. An app admin
of a single app could store such a name; any global admin who then loaded
the dashboard (which lists every app) executed the attacker's markup in
their own session, escalating an app-admin account to global-admin control.

Escape every "<" as < in both serialisation paths (string values and
object keys). < parses back to "<", so every value read from the exposed
object is unchanged; verified by an eval round-trip that reproduces the
input object identically and matches the previous serialiser output.

Also render the active-app name with .text() instead of .html() in
countly.template.js, an independent DOM sink for the same value.

Reported through the security bug bounty programme (received 2026-08-17).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant