CVE-2024-37899 (published 2024-06-20T23:15Z)

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. When an admin disables a user account, the user\'s profile is executed with the admin\'s rights. This allows a user to place malicious code in the user profile before getting an admin to disable the user account. To reproduce, as a user without script nor programming rights, edit the about section of your user profile and add `{{groovy}}services.logging.getLogger("attacker").error("Hello from Groovy!"){{/groovy}}`.\nAs an admin, go to the user profile and click the "Disable this account" button. Then, reload the page. If the logs show `attacker - Hello from Groovy!` then the instance is vulnerable. This has been patched in XWiki 14.10.21, 15.5.5, 15.10.6 and 16.0.0. Users are advised to upgrade. There are no known workarounds for this vulnerability.\n\n### Workarounds\nWe\'re not aware of any workaround except upgrading.\n\n### References\n* https://jira.xwiki.org/browse/XWIKI-21611\n* https://github.com/xwiki/xwiki-platform/commit/f89c8f47fad6e5cc7e68c69a7e0acde07f5eed5a\n


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: pjeanjean
author_date: 2024-01-16 12:49:59+01:00
msg: XWIKI-21611: Set right author when disabling/enabling an account

* Set only the original metadata author in XWikiUser.setDisabled
* Use the same disable/enable action everywhere
* Remove no longer needed request parameters
repository: repo_url: https://github.com/xwiki/xwiki-platform
repo_name: xwiki/xwiki-platform
description: The XWiki platform
repo_language: Java
owner: xwiki
file_changes: filename: XWikiUser.java
change_type: ModificationType.MODIFY
diff: @@ -31,6 +31,8 @@
 import org.xwiki.model.reference.EntityReferenceSerializer;
 import org.xwiki.model.reference.LocalDocumentReference;
 import org.xwiki.model.reference.WikiReference;
+import org.xwiki.user.UserReference;
+import org.xwiki.user.UserReferenceResolver;
 
 import com.xpn.xwiki.XWiki;
 import com.xpn.xwiki.XWikiContext;
@@ -71,6 +73,8 @@
 
     private ContextualLocalizationManager localization;
 
+    private UserReferenceResolver<DocumentReference> documentReferenceUserReferenceResolver;
+
     private Logger logger = LoggerFactory.getLogger(XWikiUser.class);
 
     private String fullName;
@@ -204,6 +208,15 @@ private EntityReferenceSerializer<String> getLocalEntityReferenceSerializer()
         return localEntityReferenceSerializer;
     }
 
+    private UserReferenceResolver<DocumentReference> getDocumentReferenceUserReferenceResolver()
+    {
+        if (this.documentReferenceUserReferenceResolver == null) {
+            this.documentReferenceUserReferenceResolver =
+                Utils.getComponent(UserReferenceResolver.TYPE_DOCUMENT_REFERENCE, "document");
+        }
+        return this.documentReferenceUserReferenceResolver;
+    }
+
     private ContextualLocalizationManager getLocalization()
     {
         if (this.localization == null) {
@@ -337,7 +350,9 @@ public void setDisabled(boolean disable, XWikiContext context)
                 XWikiDocument userdoc = getUserDocument(context);
                 userdoc.setIntValue(getUserClassReference(userdoc.getDocumentReference().getWikiReference()),
                     ACTIVE_PROPERTY, activeFlag);
-                userdoc.setAuthorReference(context.getUserReference());
+                UserReference userReference =
+                    getDocumentReferenceUserReferenceResolver().resolve(context.getUserReference());
+                userdoc.getAuthors().setOriginalMetadataAuthor(userReference);
                 context.getWiki().saveDocument(userdoc,
                     localizePlainOrKey("core.users." + (disable ? "disable" : "enable") + ".saveComment"), context);
             } catch (XWikiException e) {

programming_language: Java
filename: XWikiUserTest.java
change_type: ModificationType.MODIFY
diff: @@ -19,12 +19,17 @@
  */
 package com.xpn.xwiki.user.api;
 
+import javax.inject.Named;
+
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mock;
 import org.xwiki.localization.ContextualLocalizationManager;
+import org.xwiki.model.document.DocumentAuthors;
 import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.test.junit5.mockito.MockComponent;
 import org.xwiki.test.mockito.MockitoComponentManager;
+import org.xwiki.user.UserReferenceResolver;
 
 import com.xpn.xwiki.XWikiException;
 import com.xpn.xwiki.doc.XWikiDocument;
@@ -56,21 +61,29 @@
     @InjectMockitoOldcore
     private MockitoOldcore mockitoOldcore;
 
+    @MockComponent
+    @Named("document")
+    private UserReferenceResolver<DocumentReference> documentReferenceUserReferenceResolver;
+
     @Mock
     private XWikiDocument userDocument;
 
-    private DocumentReference userClassReference = new DocumentReference("xwiki", "XWiki", "XWikiUsers");
+    @Mock
+    private DocumentAuthors authors;
+
+    private final DocumentReference userClassReference = new DocumentReference("xwiki", "XWiki", "XWikiUsers");
 
-    private DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "Foo");
+    private final DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "Foo");
 
     @BeforeEach
     public void setup(MockitoComponentManager componentManager) throws Exception
     {
-        when(mockitoOldcore.getSpyXWiki().getDocument(userReference, mockitoOldcore.getXWikiContext()))
-            .thenReturn(userDocument);
-        when(userDocument.getDocumentReference()).thenReturn(userReference);
-        when(userDocument.getDocumentReferenceWithLocale()).thenReturn(userReference);
-        when(userDocument.clone()).thenReturn(userDocument);
+        when(this.mockitoOldcore.getSpyXWiki().getDocument(this.userReference, this.mockitoOldcore.getXWikiContext()))
+            .thenReturn(this.userDocument);
+        when(this.userDocument.getDocumentReference()).thenReturn(this.userReference);
+        when(this.userDocument.getDocumentReferenceWithLocale()).thenReturn(this.userReference);
+        when(this.userDocument.getAuthors()).thenReturn(this.authors);
+        when(this.userDocument.clone()).thenReturn(this.userDocument);
         componentManager.registerMockComponent(ContextualLocalizationManager.class, "default");
     }
 
@@ -95,38 +108,42 @@ public void createWithNullFullName()
     @Test
     public void isDisabled()
     {
-        XWikiUser user = new XWikiUser(userReference);
-        when(userDocument.getIntValue(userClassReference, XWikiUser.ACTIVE_PROPERTY, 1)).thenReturn(1);
-        assertFalse(user.isDisabled(mockitoOldcore.getXWikiContext()));
+        XWikiUser user = new XWikiUser(this.userReference);
+        when(this.userDocument.getIntValue(this.userClassReference, XWikiUser.ACTIVE_PROPERTY, 1)).thenReturn(1);
+        assertFalse(user.isDisabled(this.mockitoOldcore.getXWikiContext()));
 
-        when(userDocument.getIntValue(userClassReference, XWikiUser.ACTIVE_PROPERTY, 1)).thenReturn(0);
-        assertTrue(user.isDisabled(mockitoOldcore.getXWikiContext()));
+        when(this.userDocument.getIntValue(this.userClassReference, XWikiUser.ACTIVE_PROPERTY, 1)).thenReturn(0);
+        assertTrue(user.isDisabled(this.mockitoOldcore.getXWikiContext()));
 
         user = new XWikiUser((DocumentReference) null);
-        assertFalse(user.isDisabled(mockitoOldcore.getXWikiContext()));
+        assertFalse(user.isDisabled(this.mockitoOldcore.getXWikiContext()));
 
         user = new XWikiUser(XWikiRightService.SUPERADMIN_USER_FULLNAME);
-        assertFalse(user.isDisabled(mockitoOldcore.getXWikiContext()));
+        assertFalse(user.isDisabled(this.mockitoOldcore.getXWikiContext()));
     }
 
     @Test
     public void setDisabledFalseNormalUser() throws XWikiException
     {
-        XWikiUser user = new XWikiUser(userReference);
-        user.setDisabled(false, mockitoOldcore.getXWikiContext());
-        verify(userDocument, times(1)).setIntValue(userClassReference, XWikiUser.ACTIVE_PROPERTY, 1);
-        verify(mockitoOldcore.getSpyXWiki(), times(1))
-            .saveDocument(same(userDocument), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        XWikiUser user = new XWikiUser(this.userReference);
+        user.setDisabled(false, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, times(1)).setIntValue(this.userClassReference, XWikiUser.ACTIVE_PROPERTY, 1);
+        verify(this.mockitoOldcore.getSpyXWiki(), times(1))
+            .saveDocument(same(this.userDocument), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
+        verify(this.authors, times(1)).setOriginalMetadataAuthor(any());
+        verify(this.authors, never()).setEffectiveMetadataAuthor(any());
     }
 
     @Test
     public void setDisabledTrueNormalUser() throws XWikiException
     {
-        XWikiUser user = new XWikiUser(userReference);
-        user.setDisabled(true, mockitoOldcore.getXWikiContext());
-        verify(userDocument, times(1)).setIntValue(userClassReference, XWikiUser.ACTIVE_PROPERTY, 0);
-        verify(mockitoOldcore.getSpyXWiki(), times(1))
-            .saveDocument(same(userDocument), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        XWikiUser user = new XWikiUser(this.userReference);
+        user.setDisabled(true, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, times(1)).setIntValue(this.userClassReference, XWikiUser.ACTIVE_PROPERTY, 0);
+        verify(this.mockitoOldcore.getSpyXWiki(), times(1))
+            .saveDocument(same(this.userDocument), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
+        verify(this.authors, times(1)).setOriginalMetadataAuthor(any());
+        verify(this.authors, never()).setEffectiveMetadataAuthor(any());
     }
 
     @Test
@@ -134,68 +151,68 @@ public void setDisabledGuestOrSuperadminUser() throws XWikiException
     {
         // With guest user we never save anything
         XWikiUser user = new XWikiUser((DocumentReference) null);
-        user.setDisabled(true, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
-
-        user.setDisabled(false, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        user.setDisabled(true, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
+
+        user.setDisabled(false, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
 
         // With superadmin user we never save anything
         user = new XWikiUser(XWikiRightService.SUPERADMIN_USER_FULLNAME);
-        user.setDisabled(true, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
-
-        user.setDisabled(false, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        user.setDisabled(true, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
+
+        user.setDisabled(false, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
     }
 
     @Test
     public void isEmailChecked()
     {
-        XWikiUser user = new XWikiUser(userReference);
-        when(userDocument.getIntValue(userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 1)).thenReturn(1);
-        assertTrue(user.isEmailChecked(mockitoOldcore.getXWikiContext()));
+        XWikiUser user = new XWikiUser(this.userReference);
+        when(this.userDocument.getIntValue(this.userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 1)).thenReturn(1);
+        assertTrue(user.isEmailChecked(this.mockitoOldcore.getXWikiContext()));
 
-        when(userDocument.getIntValue(userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 1)).thenReturn(0);
-        assertFalse(user.isEmailChecked(mockitoOldcore.getXWikiContext()));
+        when(this.userDocument.getIntValue(this.userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 1)).thenReturn(0);
+        assertFalse(user.isEmailChecked(this.mockitoOldcore.getXWikiContext()));
 
         user = new XWikiUser((DocumentReference) null);
-        assertTrue(user.isEmailChecked(mockitoOldcore.getXWikiContext()));
+        assertTrue(user.isEmailChecked(this.mockitoOldcore.getXWikiContext()));
 
         user = new XWikiUser(XWikiRightService.SUPERADMIN_USER_FULLNAME);
-        assertTrue(user.isEmailChecked(mockitoOldcore.getXWikiContext()));
+        assertTrue(user.isEmailChecked(this.mockitoOldcore.getXWikiContext()));
     }
 
     @Test
     public void setEmailCheckedFalseNormalUser() throws XWikiException
     {
-        XWikiUser user = new XWikiUser(userReference);
-        user.setEmailChecked(false, mockitoOldcore.getXWikiContext());
-        verify(userDocument, times(1)).setIntValue(userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 0);
-        verify(mockitoOldcore.getSpyXWiki(), times(1))
-            .saveDocument(same(userDocument), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        XWikiUser user = new XWikiUser(this.userReference);
+        user.setEmailChecked(false, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, times(1)).setIntValue(this.userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 0);
+        verify(this.mockitoOldcore.getSpyXWiki(), times(1))
+            .saveDocument(same(this.userDocument), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
     }
 
     @Test
     public void setEmailCheckedTrueNormalUser() throws XWikiException
     {
-        XWikiUser user = new XWikiUser(userReference);
-        user.setEmailChecked(true, mockitoOldcore.getXWikiContext());
-        verify(userDocument, times(1)).setIntValue(userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 1);
-        verify(mockitoOldcore.getSpyXWiki(), times(1))
-            .saveDocument(same(userDocument), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        XWikiUser user = new XWikiUser(this.userReference);
+        user.setEmailChecked(true, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, times(1)).setIntValue(this.userClassReference, XWikiUser.EMAIL_CHECKED_PROPERTY, 1);
+        verify(this.mockitoOldcore.getSpyXWiki(), times(1))
+            .saveDocument(same(this.userDocument), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
     }
 
     @Test
@@ -203,30 +220,30 @@ public void setEmailCheckedGuestOrSuperadminUser() throws XWikiException
     {
         // With guest user we never save anything
         XWikiUser user = new XWikiUser((DocumentReference) null);
-        user.setEmailChecked(true, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
-
-        user.setEmailChecked(false, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        user.setEmailChecked(true, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
+
+        user.setEmailChecked(false, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
 
         // With superadmin user we never save anything
         user = new XWikiUser(XWikiRightService.SUPERADMIN_USER_FULLNAME);
-        user.setEmailChecked(true, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
-
-        user.setEmailChecked(false, mockitoOldcore.getXWikiContext());
-        verify(userDocument, never())
-            .setIntValue(same(userClassReference), any(String.class), any(Integer.class));
-        verify(mockitoOldcore.getSpyXWiki(), never())
-            .saveDocument(any(XWikiDocument.class), any(String.class), same(mockitoOldcore.getXWikiContext()));
+        user.setEmailChecked(true, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
+
+        user.setEmailChecked(false, this.mockitoOldcore.getXWikiContext());
+        verify(this.userDocument, never())
+            .setIntValue(same(this.userClassReference), any(String.class), any(Integer.class));
+        verify(this.mockitoOldcore.getSpyXWiki(), never())
+            .saveDocument(any(XWikiDocument.class), any(String.class), same(this.mockitoOldcore.getXWikiContext()));
     }
 }

programming_language: Java
filename: getusers.vm
change_type: ModificationType.MODIFY
diff: @@ -128,16 +128,18 @@
       'doc_hasdelete': $hasDelete,
       'doc_delete_url': $user.getURL('delete'),
       'doc_hasdisable': $hasDisable,
-      'doc_disable_url': $user.getURL('save', $escapetool.url({
-        "${userClassName}_0_active": 0,
-        'comment': $services.localization.render('core.users.disable.saveComment'),
-        'form_token': $services.csrf.token
+      'doc_disable_url': $xwiki.getURL('XWiki.XWikiUserProfileSheet', 'get', $escapetool.url({
+        'outputSyntax': 'plain',
+        'action': 'disable',
+        'userId': $user.documentReference,
+        'csrf': $services.csrf.token
       })),
       'doc_hasenable': $hasEnable,
-      'doc_enable_url': $user.getURL('save', $escapetool.url({
-        "${userClassName}_0_active": 1,
-        'comment': $services.localization.render('core.users.enable.saveComment'),
-        'form_token': $services.csrf.token
+      'doc_enable_url': $xwiki.getURL('XWiki.XWikiUserProfileSheet', 'get', $escapetool.url({
+        'outputSyntax': 'plain',
+        'action': 'enable',
+        'userId': $user.documentReference,
+        'csrf': $services.csrf.token
       })),
       'name': "#displayUserAliasWithAvatar($user.documentReference $disabled)",
       'first_name': $userObject.getValue('first_name'),

programming_language: PowerShell