From eab6c29779957f33bb4bd84759cfdd9a60d8c4f2 Mon Sep 17 00:00:00 2001 From: mbiuki Date: Mon, 29 Jun 2026 12:32:59 -0400 Subject: [PATCH] fix(security): HTML-escape user names in admin Users panel to stop stored XSS (private-issues#651) User first/last names are attacker-controllable and were rendered as HTML in the admin Users panel, enabling stored XSS that runs in the admin session. - Add an escaping formatter to the users DataGrid name/email columns (list rows). - Render the profile-detail full name and user id via textContent instead of innerHTML. This is the keystone fix that breaks the reported XSS->RCE chain at the render sink; server-side name validation is added separately in the UserAPIImpl path. Co-Authored-By: Claude Opus 4.8 --- .../ext/useradmin/view_users_js_inc.jsp | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/dotCMS/src/main/webapp/html/portlet/ext/useradmin/view_users_js_inc.jsp b/dotCMS/src/main/webapp/html/portlet/ext/useradmin/view_users_js_inc.jsp index 5352e19397c..2638d4f13c8 100644 --- a/dotCMS/src/main/webapp/html/portlet/ext/useradmin/view_users_js_inc.jsp +++ b/dotCMS/src/main/webapp/html/portlet/ext/useradmin/view_users_js_inc.jsp @@ -171,9 +171,21 @@ }; var usersStore = new dojo.data.ItemFileReadStore({data: usersData}); + // HTML-escape user-controlled grid values so a name/email containing markup + // renders as text instead of executing (stored XSS). See dotCMS/private-issues#651. + var escapeGridHtml = function (value) { + if (value === null || value === undefined) { return ""; } + return String(value) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + }; + var usersGridLayout = [[ - { name: nameColumn, field: 'name', width:'50%' }, - { name: emailColumn, field: 'email', width:'50%' }, + { name: nameColumn, field: 'name', width:'50%', formatter: escapeGridHtml }, + { name: emailColumn, field: 'email', width:'50%', formatter: escapeGridHtml }, ]]; //Initialization kicking the loading of users @@ -418,7 +430,7 @@ dijit.byId('userId').attr('value', user.id); dijit.byId('userId').setDisabled(true); } else { - dojo.byId('userIdValue').innerHTML = user.id; + dojo.byId('userIdValue').textContent = user.id; dojo.byId('userId').value = user.id; } @@ -457,7 +469,9 @@ dijit.byId('password').attr('value', '********'); dijit.byId('passwordCheck').attr('value', '********'); - dojo.query(".fullUserName").forEach(function (elem) { elem.innerHTML = user.name; }); + // Render the user's name as text, never as HTML, to prevent stored XSS + // (first/last name are user-controlled). See dotCMS/private-issues#651. + dojo.query(".fullUserName").forEach(function (elem) { elem.textContent = user.name; }); userChanged = false; newUser = false;