CVE-2026-42197
Description
RELATE is a web-based courseware package. Versions prior to commit 555f0efb1c5bd7531c07cd73724d7e566a81f620 have a stored cross-site scripting vulnerability that allows any enrolled student to execute arbitrary JavaScript in an administrator's browser session, potentially leading to full admin account takeover. The get_user() method in ParticipationAdmin renders user-controlled input using mark_safe combined with Python's % string formatting. This bypasses Django\'s automatic HTML escaping entirely. The value returned by get_full_name is derived directly from the first_name and last_name fields of the User model. These fields are freely editable by any authenticated user through the profile page (/profile/) with no sanitization applied. When an admin views the Participation list in the Django admin panel, the unsanitized value is rendered directly into the HTML response, causing the injected script to execute in the admin's browser. Commit 555f0efb1c5bd7531c07cd73724d7e566a81f620 fixes the issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Stored XSS in RELATE courseware prior to commit 555f0efb allows any enrolled student to execute arbitrary JavaScript in an admin's browser via unsanitized user name fields.
Vulnerability
A stored cross-site scripting (XSS) vulnerability exists in RELATE versions prior to commit 555f0efb1c5bd7531c07cd73724d7e566a81f620 (affecting versions <=2024.1 [1]). The get_user() method in ParticipationAdmin (in course/admin.py) uses mark_safe combined with Python's % string formatting to render a user's full name as a link in the Django admin panel [2]. This bypasses Django's automatic HTML escaping. The get_full_name value is derived directly from the first_name and last_name fields of the User model, which are freely editable by any authenticated user via the profile page (/profile/) with no sanitization [1].
Exploitation
An attacker needs only a valid account enrolled in any course. They can set malicious JavaScript in their first_name or last_name fields through the profile page [1]. When an administrator views the Participation list in the Django admin panel, the unsanitized value is rendered directly into the HTML response, causing the injected script to execute in the admin's browser [1][2]. No additional privileges or user interaction beyond the admin viewing the list is required.
Impact
Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of an administrator's browser session. This can lead to full admin account takeover, including modification of course content, user data, and system configuration. The attacker gains the same privileges as the targeted admin user [1].
Mitigation
The fix is contained in commit 555f0efb1c5bd7531c07cd73724d7e566a81f620, which replaces the unsafe mark_safe and % formatting with Django's format_html to properly escape output [3]. Users should update to a version that includes this commit. No workaround is described in the available references. The CVE is not listed on CISA's Known Exploited Vulnerabilities (KEV) as of publication date.
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
1555f0efb1c5bFix XSS in ParticipationAdmin.get_user
1 file changed · +11 −14
course/admin.py+11 −14 modified@@ -350,22 +350,19 @@ def get_tags(self, obj): description=pgettext("real name of a user", "Name"), ordering="user__last_name", ) - def get_user(self, obj): + def get_user(self, obj: Participation): from django.conf import settings from django.urls import reverse - from django.utils.html import mark_safe - - return mark_safe(string_concat( - "<a href='%(link)s'>", "%(user_fullname)s", - "</a>" - ) % { - "link": reverse( - "admin:{}_change".format( - settings.AUTH_USER_MODEL.replace(".", "_").lower()), - args=(obj.user.id,)), - "user_fullname": obj.user.get_full_name( - force_verbose_blank=True), - }) + from django.utils.html import format_html + + return format_html( + "<a href='{}'>{}</a>", + reverse( + "admin:{}_change".format( + settings.AUTH_USER_MODEL.replace(".", "_").lower()), + args=(obj.user.id,)), + obj.user.get_full_name(force_verbose_blank=True), + ) list_display = ( "user",
Vulnerability mechanics
Root cause
"The `get_user()` method uses `mark_safe` with Python `%` string formatting, bypassing Django's auto-escaping and allowing unsanitized user-controlled `first_name`/`last_name` values to be rendered as raw HTML."
Attack vector
An authenticated student edits their `first_name` or `last_name` fields on the profile page (`/profile/`) to include malicious JavaScript, such as `
Affected code
The vulnerable code is in `course/admin.py` in the `ParticipationAdmin.get_user()` method [ref_id=1]. The original implementation used `mark_safe` combined with Python `%` string formatting to build an HTML anchor tag, which bypassed Django's automatic HTML escaping entirely [ref_id=1].
What the fix does
The patch replaces `mark_safe` with Django's `format_html` [patch_id=2749099]. `format_html` automatically HTML-escapes all positional arguments before inserting them into the template string, so the user-controlled `get_full_name()` value is safely escaped. This closes the XSS vector while preserving the intended link rendering behavior [ref_id=2].
Preconditions
- authAttacker must be an authenticated user enrolled in a course
- inputAttacker must edit their first_name or last_name via the /profile/ page
- networkAn administrator must view the Participation list in the Django admin panel
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.