CVE-2023-46242 (published 2023-11-07T19:15Z)

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. In affected versions it's possible to execute a content with the right of any user via a crafted URL. A user must have `programming` privileges in order to exploit this vulnerability. This issue has been patched in XWiki 14.10.7 and 15.2RC1. Users are advised to upgrade. There are no known workarounds for for this vulnerability.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Thomas Mortagne
author_date: 2023-03-16 16:53:19+01:00
msg: XWIKI-20386: CSRF privilege escalation/RCE via the edit action
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: EditAction.java
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xwiki.component.annotation.Component;
+import org.xwiki.csrf.CSRFToken;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.rendering.syntax.Syntax;
 import org.xwiki.user.UserReference;
@@ -58,6 +59,9 @@ public class EditAction extends XWikiAction
     @Named("document")
     private UserReferenceResolver<DocumentReference> documentReferenceUserReferenceResolver;
 
+    @Inject
+    private CSRFToken csrf;
+
     /**
      * Default constructor.
      */
@@ -116,19 +120,29 @@ protected XWikiDocument prepareEditedDocument(XWikiContext context) throws XWiki
         updateDocumentTitleAndContentFromRequest(editedDocument, context);
         editedDocument.readAddedUpdatedAndRemovedObjectsFromForm(editForm, context);
 
-        // If the metadata is modified, modify the effective metadata author
-        if (editedDocument.isMetaDataDirty()) {
-            UserReference userReference =
-                this.documentReferenceUserReferenceResolver.resolve(context.getUserReference());
-            editedDocument.getAuthors().setEffectiveMetadataAuthor(userReference);
-            editedDocument.getAuthors().setOriginalMetadataAuthor(userReference);
-        }
+        // Check if the document in modified
+        if (editedDocument.isMetaDataDirty() || editedDocument.isContentDirty()) {
+            // If the document is modified make sure a valid CSRF is provided
+            String token = context.getRequest().getParameter("form_token");
+            if (!this.csrf.isTokenValid(token)) {
+                // or make the document restricted
+                editedDocument.setRestricted(true);
+            }
 
-        // If the content is modified, modify the content author
-        if (editedDocument.isContentDirty()) {
-            UserReference userReference =
-                this.documentReferenceUserReferenceResolver.resolve(context.getUserReference());
-            editedDocument.getAuthors().setContentAuthor(userReference);
+            // If the metadata is modified, modify the effective metadata author
+            if (editedDocument.isMetaDataDirty()) {
+                UserReference userReference =
+                    this.documentReferenceUserReferenceResolver.resolve(context.getUserReference());
+                editedDocument.getAuthors().setEffectiveMetadataAuthor(userReference);
+                editedDocument.getAuthors().setOriginalMetadataAuthor(userReference);
+            }
+
+            // If the content is modified, modify the content author
+            if (editedDocument.isContentDirty()) {
+                UserReference userReference =
+                    this.documentReferenceUserReferenceResolver.resolve(context.getUserReference());
+                editedDocument.getAuthors().setContentAuthor(userReference);
+            }
         }
 
         // Set the current user as creator, author and contentAuthor when the edited document is newly created to avoid

programming_language: Java
filename: EditActionTest.java
change_type: ModificationType.MODIFY
diff: @@ -19,11 +19,14 @@
  */
 package com.xpn.xwiki.web;
 
+import java.util.Map;
+
 import javax.inject.Named;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mock;
+import org.xwiki.csrf.CSRFToken;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.store.TemporaryAttachmentSessionsManager;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
@@ -39,6 +42,7 @@
 import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;
 import com.xpn.xwiki.test.reference.ReferenceComponentList;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -75,6 +79,9 @@
     @Named("document")
     private UserReferenceSerializer<DocumentReference> documentReferenceUserReferenceSerializer;
 
+    @MockComponent
+    private CSRFToken csrf;
+
     @Mock
     private XWikiRequest request;
 
@@ -85,6 +92,9 @@ public void beforeEach()
         when(this.documentReferenceUserReferenceSerializer.serialize(USER_REFERENCE)).thenReturn(USER_DOCUMENT_REFERENCE);
 
         this.oldcore.getXWikiContext().setUserReference(USER_DOCUMENT_REFERENCE);
+
+        this.oldcore.getXWikiContext().setRequest(new XWikiServletRequestStub.Builder().
+            setRequestParameters(Map.of("form_token", new String[] {"tokenvalue"})).build());
     }
 
     private String initAndRenderAction() throws XWikiException
@@ -141,7 +151,18 @@ void documentAuthorsWhenDocumentExist() throws XWikiException
     }
 
     @Test
-    void documentAuthorsWhenDocumentExistAndContentIsModified() throws XWikiException
+    void documentAuthorsWhenDocumentExistAndContentIsModifiedAndInvalidValidCSRF() throws XWikiException
+    {
+        documentAuthorsWhenDocumentExistAndContentIsModified(false);
+    }
+
+    @Test
+    void documentAuthorsWhenDocumentExistAndContentIsModifiedAndValidCSRF() throws XWikiException
+    {
+        documentAuthorsWhenDocumentExistAndContentIsModified(true);
+    }
+
+    void documentAuthorsWhenDocumentExistAndContentIsModified(boolean validToken) throws XWikiException
     {
         XWikiDocument document = this.oldcore.getSpyXWiki().getDocument(new DocumentReference("wiki", "space", "page"),
             this.oldcore.getXWikiContext());
@@ -158,6 +179,8 @@ void documentAuthorsWhenDocumentExistAndContentIsModified() throws XWikiExceptio
 
         when(this.request.getParameter("content")).thenReturn("modified content");
 
+        when(this.csrf.isTokenValid("tokenvalue")).thenReturn(validToken);
+
         initAndRenderAction();
 
         document = this.oldcore.getXWikiContext().getDoc();
@@ -166,5 +189,6 @@ void documentAuthorsWhenDocumentExistAndContentIsModified() throws XWikiExceptio
         assertSame(OTHERUSER_REFERENCE, document.getAuthors().getCreator());
         assertSame(OTHERUSER_REFERENCE, document.getAuthors().getEffectiveMetadataAuthor());
         assertSame(OTHERUSER_REFERENCE, document.getAuthors().getOriginalMetadataAuthor());
+        assertEquals(!validToken, document.isRestricted());
     }
 }

programming_language: Java