CVE-2023-50722: Cross-Site Request Forgery (CSRF) (CWE-352)
value: XWiki Platform is a generic wiki platform. Starting in 2.3 and prior to versions 14.10.15, 15.5.2, and 15.7-rc-1, there is a reflected XSS or also direct remote code execution vulnerability in the code for displaying configurable admin sections. The code that can be passed through a URL parameter is only executed when the user who is visiting the crafted URL has edit right on at least one configuration section. While any user of the wiki could easily create such a section, this vulnerability doesn't require the attacker to have an account or any access on the wiki. It is sufficient to trick any admin user of the XWiki installation to visit the crafted URL. This vulnerability allows full remote code execution with programming rights and thus impacts the confidentiality, integrity and availability of the whole XWiki installation. This has been fixed in XWiki 14.10.15, 15.5.2 and 15.7RC1. The patch can be manually applied to the document `XWiki.ConfigurableClass`.

CWE 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.
CVSS v3 base score: 8.8
Published: 2023-12-15T19:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: XML
Files changed: xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/main/resources/XWiki/ConfigurableClass.xml, xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/test/java/org/xwiki/administration/ConfigurableClassPageTest.java
Commit date: 2023-08-02 17:08:18 +0200
Commit message: XWIKI-21167: Improve escaping of space parameter in ConfigurableClass

Diff: diff --git a/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/main/resources/XWiki/ConfigurableClass.xml b/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/main/resources/XWiki/ConfigurableClass.xml
index 83d02641e8456..4c856dc7a2981 100644
--- a/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/main/resources/XWiki/ConfigurableClass.xml
+++ b/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/main/resources/XWiki/ConfigurableClass.xml
@@ -405,11 +405,11 @@
   ## Now we go through sectionsToAdd and generate icons for them
   #set($defaultIcon = $xwiki.getAttachmentURL($nameOfThisDocument, 'DefaultAdminSectionIcon.png'))
   #if($globaladmin)
     #set($queryString = "editor=globaladmin&amp;amp;section=")
   #else
-    #set($queryString = "space=${currentSpace}&amp;amp;section=")
+    #set($queryString = "space=$escapetool.url($currentSpace)&amp;amp;section=")
     #if($request.getParameter('editor'))
       #set($queryString = "editor=$escapetool.url($request.getParameter('editor'))&amp;amp;$queryString")
     #end
   #end
 
diff --git a/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/test/java/org/xwiki/administration/ConfigurableClassPageTest.java b/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/test/java/org/xwiki/administration/ConfigurableClassPageTest.java
index f79fc8637db72..8fb7fa79ddcbb 100644
--- a/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/test/java/org/xwiki/administration/ConfigurableClassPageTest.java
+++ b/xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/test/java/org/xwiki/administration/ConfigurableClassPageTest.java
@@ -17,14 +17,17 @@
  * 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.administration;
 
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
+import org.apache.http.client.utils.URLEncodedUtils;
 import org.jsoup.nodes.Document;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -52,10 +55,11 @@
 import com.xpn.xwiki.doc.XWikiDocument;
 import com.xpn.xwiki.internal.model.reference.DocumentReferenceConverter;
 import com.xpn.xwiki.objects.BaseObject;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
@@ -206,6 +210,41 @@ void escapeNonViewableSections() throws Exception
         Document htmlPage = renderHTMLPage(doc);
         String errorMessage = Objects.requireNonNull(htmlPage.selectFirst("div.errormessage p")).text();
         assertEquals(String.format("xe.admin.configurable.noViewAccessSomeApplications [[%s]]", MY_SECTION_SERIALIZED),
             errorMessage);
     }
+
+    @Test
+    void escapeSectionLink() throws Exception
+    {
+        // Create a new section document.
+        XWikiDocument mySectionDoc = new XWikiDocument(MY_SECTION);
+        BaseObject object = mySectionDoc.newXObject(CONFIGURABLE_CLASS, this.context);
+        object.setStringValue("displayInCategory", "other");
+        object.setStringValue("displayInSection", "other");
+        object.set("scope", "WIKI+ALL_SPACES", this.context);
+        this.xwiki.saveDocument(mySectionDoc, this.context);
+
+        // Make sure the section document is returned by the query and the user has access to edit.
+        when(this.query.execute()).thenReturn(List.of(MY_SECTION_SERIALIZED)).thenReturn(List.of());
+        when(this.oldcore.getMockRightService()
+            .hasAccessLevel(eq("edit"), any(), any(), any())).thenReturn(true);
+
+        // Set a new document with space ">{{/html}}{{noscript /}} as context document to check escaping of the
+        // current space.
+        String spaceName = "\">{{/html}}{{noscript /}}";
+        DocumentReference docRef = new DocumentReference(WIKI_NAME, spaceName, "WebHome");
+        XWikiDocument contextDoc = new XWikiDocument(docRef);
+        this.xwiki.saveDocument(contextDoc, this.context);
+        this.context.setDoc(contextDoc);
+
+        XWikiDocument doc = loadPage(CONFIGURABLE_CLASS);
+        Document htmlPage = renderHTMLPage(doc);
+        String link = Objects.requireNonNull(htmlPage.selectFirst("li.other a")).attr("href");
+        URI uri = new URI(link);
+        // Parse the query parameters and check the space name.
+        URLEncodedUtils.parse(uri, StandardCharsets.UTF_8).stream()
+            .filter(pair -> pair.getName().equals("space"))
+            .findFirst()
+            .ifPresentOrElse(pair -> assertEquals(spaceName, pair.getValue()), () -> fail("No space parameter in URL"));
+    }
 }

Vulnerable code:     #set($queryString = "space=${currentSpace}&amp;amp;section=")
Fixed code:     #set($queryString = "space=$escapetool.url($currentSpace)&amp;amp;section=")
import java.net.URI;
import java.nio.charset.StandardCharsets;
import org.apache.http.client.utils.URLEncodedUtils;
import static org.junit.jupiter.api.Assertions.fail;

    @Test
    void escapeSectionLink() throws Exception
    {
        // Create a new section document.
        XWikiDocument mySectionDoc = new XWikiDocument(MY_SECTION);
        BaseObject object = mySectionDoc.newXObject(CONFIGURABLE_CLASS, this.context);
        object.setStringValue("displayInCategory", "other");
        object.setStringValue("displayInSection", "other");
        object.set("scope", "WIKI+ALL_SPACES", this.context);
        this.xwiki.saveDocument(mySectionDoc, this.context);

        // Make sure the section document is returned by the query and the user has access to edit.
        when(this.query.execute()).thenReturn(List.of(MY_SECTION_SERIALIZED)).thenReturn(List.of());
        when(this.oldcore.getMockRightService()
            .hasAccessLevel(eq("edit"), any(), any(), any())).thenReturn(true);

        // Set a new document with space ">{{/html}}{{noscript /}} as context document to check escaping of the
        // current space.
        String spaceName = "\">{{/html}}{{noscript /}}";
        DocumentReference docRef = new DocumentReference(WIKI_NAME, spaceName, "WebHome");
        XWikiDocument contextDoc = new XWikiDocument(docRef);
        this.xwiki.saveDocument(contextDoc, this.context);
        this.context.setDoc(contextDoc);

        XWikiDocument doc = loadPage(CONFIGURABLE_CLASS);
        Document htmlPage = renderHTMLPage(doc);
        String link = Objects.requireNonNull(htmlPage.selectFirst("li.other a")).attr("href");
        URI uri = new URI(link);
        // Parse the query parameters and check the space name.
        URLEncodedUtils.parse(uri, StandardCharsets.UTF_8).stream()
            .filter(pair -> pair.getName().equals("space"))
            .findFirst()
            .ifPresentOrElse(pair -> assertEquals(spaceName, pair.getValue()), () -> fail("No space parameter in URL"));
    }