CVE-2023-46244: Incorrect Authorization (CWE-863)
value: XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. In affected versions it's possible for a user to write a script in which any velocity content is executed with the right of any other document content author. Since this API require programming right and the user does not have it, the expected result is `$doc.document.authors.contentAuthor` (not executed script), unfortunately with the security vulnerability it is possible for the attacker to get `XWiki.superadmin` which shows that the title was executed with the right of the unmodified document. This has been patched in XWiki versions 14.10.7 and 15.2RC1. Users are advised to upgrade. There are no known workarounds for this vulnerability.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 8.8
Published: 2023-11-07T19:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Java
Files changed: xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/AbstractDocumentTitleDisplayer.java, xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/test/java/org/xwiki/display/internal/DocumentTitleDisplayerTest.java, xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AllITs.java, xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/ScriptAuthorIT.java
Commit date: 2023-03-09 17:19:46 +0100
Commit message: XWIKI-20624: Improve title display for modified documents

Diff: diff --git a/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/AbstractDocumentTitleDisplayer.java b/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/AbstractDocumentTitleDisplayer.java
index 0d62797ae4fc8..46a508a01c9e9 100644
--- a/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/AbstractDocumentTitleDisplayer.java
+++ b/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/AbstractDocumentTitleDisplayer.java
@@ -174,11 +174,11 @@ private XDOM displayTitle(DocumentModelBridge document, DocumentDisplayerParamet
             try {
                 String title = rawTitle;
                 // Evaluate the title only if the document has script rights, otherwise use the raw title.
                 if (authorizationManager.hasAccess(Right.SCRIPT, document.getContentAuthorReference(),
                     document.getDocumentReference())) {
-                    title = evaluateTitle(rawTitle, document.getDocumentReference(), parameters);
+                    title = evaluateTitle(rawTitle, document, parameters);
                 }
                 return parseTitle(title);
             } catch (Exception e) {
                 logger.warn("Failed to interpret title of document [{}].", document.getDocumentReference(), e);
             }
@@ -224,16 +224,16 @@ protected XDOM parseTitle(String title)
      * @param title the title to evaluate
      * @param documentReference a reference to the document whose title is evaluated
      * @param parameters display parameters
      * @return the result of evaluating the Velocity script from the given title
      */
-    protected String evaluateTitle(String title, DocumentReference documentReference,
+    protected String evaluateTitle(String title, DocumentModelBridge document,
         DocumentDisplayerParameters parameters)
     {
         StringWriter writer = new StringWriter();
         String namespace = defaultEntityReferenceSerializer.serialize(parameters.isTransformationContextIsolated()
-            ? documentReference : documentAccessBridge.getCurrentDocumentReference());
+            ? document.getDocumentReference() : documentAccessBridge.getCurrentDocumentReference());
 
         // Get the velocity engine
         VelocityEngine velocityEngine;
         try {
             velocityEngine = this.velocityManager.getVelocityEngine();
@@ -247,15 +247,15 @@ protected String evaluateTitle(String title, DocumentReference documentReference
         EntityReference currentWikiReference = this.modelContext.getCurrentEntityReference();
         try {
             if (parameters.isExecutionContextIsolated()) {
                 backupObjects = new HashMap<>();
                 // The following method call also clones the execution context.
-                documentAccessBridge.pushDocumentInContext(backupObjects, documentReference);
+                documentAccessBridge.pushDocumentInContext(backupObjects, document);
                 // Pop the document from the context only if the push was successful!
                 canPop = true;
                 // Make sure to synchronize the context wiki with the context document's wiki.
-                modelContext.setCurrentEntityReference(documentReference.getWikiReference());
+                modelContext.setCurrentEntityReference(document.getDocumentReference().getWikiReference());
             }
             velocityEngine.evaluate(velocityManager.getVelocityContext(), writer, namespace, title);
         } catch (Exception e) {
             throw new RuntimeException(e);
         } finally {
diff --git a/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/test/java/org/xwiki/display/internal/DocumentTitleDisplayerTest.java b/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/test/java/org/xwiki/display/internal/DocumentTitleDisplayerTest.java
index 46a55546ea4f5..d861c4f4f394a 100644
--- a/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/test/java/org/xwiki/display/internal/DocumentTitleDisplayerTest.java
+++ b/xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/test/java/org/xwiki/display/internal/DocumentTitleDisplayerTest.java
@@ -49,10 +49,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -132,11 +133,11 @@ public void whenSettingTheContextDocumentTheContextWikiIsAlsoSet() throws Except
         params.setExecutionContextIsolated(true);
 
         this.mocker.getComponentUnderTest().display(document, params);
 
         // Check that the context is set.
-        verify(dab).pushDocumentInContext(any(), eq(documentReference));
+        verify(dab).pushDocumentInContext(any(), same(document));
         verify(modelContext).setCurrentEntityReference(documentReference.getWikiReference());
 
         // Check that the context is restored.
         verify(dab).popDocumentFromContext(any());
         verify(modelContext).setCurrentEntityReference(currentWikiReference);
diff --git a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AllITs.java b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AllITs.java
index e988380eb6e84..1d80483feb47e 100644
--- a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AllITs.java
+++ b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AllITs.java
@@ -186,6 +186,12 @@ class NestedTextAreaIT extends TextAreaIT
     @Nested
     @DisplayName("Sheet system Tests")
     class NestedSheetIT extends SheetIT
     {
     }
+
+    @Nested
+    @DisplayName("Script author Tests")
+    class NestedScriptAuthorIT extends ScriptAuthorIT
+    {
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/ScriptAuthorIT.java b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/ScriptAuthorIT.java
new file mode 100644
index 0000000000000..5bd1f78767187
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/ScriptAuthorIT.java
@@ -0,0 +1,73 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.xwiki.flamingo.test.docker;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.test.docker.junit5.TestReference;
+import org.xwiki.test.docker.junit5.UITest;
+import org.xwiki.test.ui.TestUtils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Make sure a script end up being executed by the right author in various use cases.
+ *
+ * @version $Id$
+ */
+@UITest
+class ScriptAuthorIT
+{
+    @BeforeAll
+    public void setup(TestUtils setup)
+    {
+        setup.loginAsSuperAdmin();
+    }
+
+    @Test
+    @Order(1)
+    void renderTitleInModifiedDocument(TestUtils setup, TestReference reference) throws Exception
+    {
+        // Give a page programming right
+        setup.loginAsSuperAdmin();
+        DocumentReference programmingReference = reference;
+        setup.rest().savePage(programmingReference);
+
+        // Write a script without programming right
+        setup.createUser("renderTitleInModifiedDocument", "renderTitleInModifiedDocument", null);
+        setup.setGlobalRights(null, "renderTitleInModifiedDocument", "script", true);
+        setup.login("renderTitleInModifiedDocument", "renderTitleInModifiedDocument");
+        DocumentReference scriptReference =
+            new DocumentReference("Script", programmingReference.getLastSpaceReference());
+        StringBuilder source = new StringBuilder();
+        source.append("{{velocity}}\n");
+        source.append("#set($main = $xwiki.getDocument('" + setup.serializeReference(programmingReference) + "'))\n");
+        source.append("$main.setTitle('$doc.document.authors.contentAuthor')\n");
+        source.append("$main.getPlainTitle()\n");
+        source.append("{{/velocity}}");
+        setup.rest().savePage(scriptReference, source.toString(), "sheet title");
+
+        StringBuilder result = new StringBuilder();
+        result.append("<p>$doc.document.authors.contentAuthor</p>");
+        assertEquals(result.toString(), setup.executeAndGetBodyAsString(scriptReference, null));
+    }
+}

Vulnerable code:                     title = evaluateTitle(rawTitle, document.getDocumentReference(), parameters);
    protected String evaluateTitle(String title, DocumentReference documentReference,
            ? documentReference : documentAccessBridge.getCurrentDocumentReference());
                documentAccessBridge.pushDocumentInContext(backupObjects, documentReference);
                modelContext.setCurrentEntityReference(documentReference.getWikiReference());
        verify(dab).pushDocumentInContext(any(), eq(documentReference));
Fixed code:                     title = evaluateTitle(rawTitle, document, parameters);
    protected String evaluateTitle(String title, DocumentModelBridge document,
            ? document.getDocumentReference() : documentAccessBridge.getCurrentDocumentReference());
                documentAccessBridge.pushDocumentInContext(backupObjects, document);
                modelContext.setCurrentEntityReference(document.getDocumentReference().getWikiReference());
import static org.mockito.ArgumentMatchers.same;
        verify(dab).pushDocumentInContext(any(), same(document));

    @Nested
    @DisplayName("Script author Tests")
    class NestedScriptAuthorIT extends ScriptAuthorIT
    {
    }
/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.flamingo.test.docker;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.test.docker.junit5.TestReference;
import org.xwiki.test.docker.junit5.UITest;
import org.xwiki.test.ui.TestUtils;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Make sure a script end up being executed by the right author in various use cases.
 *
 * @version $Id$
 */
@UITest
class ScriptAuthorIT
{
    @BeforeAll
    public void setup(TestUtils setup)
    {
        setup.loginAsSuperAdmin();
    }

    @Test
    @Order(1)
    void renderTitleInModifiedDocument(TestUtils setup, TestReference reference) throws Exception
    {
        // Give a page programming right
        setup.loginAsSuperAdmin();
        DocumentReference programmingReference = reference;
        setup.rest().savePage(programmingReference);

        // Write a script without programming right
        setup.createUser("renderTitleInModifiedDocument", "renderTitleInModifiedDocument", null);
        setup.setGlobalRights(null, "renderTitleInModifiedDocument", "script", true);
        setup.login("renderTitleInModifiedDocument", "renderTitleInModifiedDocument");
        DocumentReference scriptReference =
            new DocumentReference("Script", programmingReference.getLastSpaceReference());
        StringBuilder source = new StringBuilder();
        source.append("{{velocity}}\n");
        source.append("#set($main = $xwiki.getDocument('" + setup.serializeReference(programmingReference) + "'))\n");
        source.append("$main.setTitle('$doc.document.authors.contentAuthor')\n");
        source.append("$main.getPlainTitle()\n");
        source.append("{{/velocity}}");
        setup.rest().savePage(scriptReference, source.toString(), "sheet title");

        StringBuilder result = new StringBuilder();
        result.append("<p>$doc.document.authors.contentAuthor</p>");
        assertEquals(result.toString(), setup.executeAndGetBodyAsString(scriptReference, null));
    }
}