CVE-2024-31465: Insufficient Information (NVD-CWE-noinfo)
value: XWiki Platform is a generic wiki platform. Starting in version 5.0-rc-1 and prior to versions 14.10.20, 15.5.4, and 15.9-rc-1, any user with edit right on any page can execute any code on the server by adding an object of type `XWiki.SearchSuggestSourceClass` to their user profile or any other page. This compromises the confidentiality, integrity and availability of the whole XWiki installation. This vulnerability has been patched in XWiki 14.10.20, 15.5.4 and 15.10 RC1. As a workaround, manually apply the patch to the document `XWiki.SearchSuggestSourceSheet`.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-10T20:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: XML
Files changed: xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestSourceSheet.xml, xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/SearchSuggestSourceSheetPageTest.java
Commit date: 2023-11-16 16:32:25 +0100
Commit message: XWIKI-21474: Improve escaping in XWiki.SearchSuggestSourceSheet

Diff: diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestSourceSheet.xml b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestSourceSheet.xml
index c3f3d2195e928..19e46bdfbc969 100644
--- a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestSourceSheet.xml
+++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestSourceSheet.xml
@@ -68,11 +68,11 @@
 
   #if (!$isCheckbox)
     : #if ($editing)
         $doc.display($property.name, 'edit')
       #else
-        {{{$!object.getProperty($property.name).value}}}
+        $services.rendering.escape($!object.getProperty($property.name).value, 'xwiki/2.1')
       #end
   #end
 #end
 )))
 {{/velocity}}</content>
diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/SearchSuggestSourceSheetPageTest.java b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/SearchSuggestSourceSheetPageTest.java
new file mode 100644
index 0000000000000..1b4741c77fd35
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/SearchSuggestSourceSheetPageTest.java
@@ -0,0 +1,90 @@
+package org.xwiki.search.ui;
+
+import org.jsoup.nodes.Document;
+import org.jsoup.select.Elements;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.rendering.RenderingScriptServiceComponentList;
+import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList;
+import org.xwiki.test.annotation.ComponentList;
+import org.xwiki.test.page.HTML50ComponentList;
+import org.xwiki.test.page.PageTest;
+import org.xwiki.test.page.TestNoScriptMacro;
+import org.xwiki.test.page.XWikiSyntax21ComponentList;
+import org.xwiki.uiextension.script.UIExtensionScriptServiceComponentList;
+
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Page test for {@code XWiki.SearchSuggestSourceSheet}.
+ *
+ * @version $Id$
+ */
+@ComponentList({
+    TestNoScriptMacro.class
+})
+@UIExtensionScriptServiceComponentList
+@RenderingScriptServiceComponentList
+@DefaultRenderingConfigurationComponentList
+@HTML50ComponentList
+@XWikiSyntax21ComponentList
+class SearchSuggestSourceSheetPageTest extends PageTest
+{
+    private static final String WIKI_NAME = "xwiki";
+
+    private static final String XWIKI_SPACE = "XWiki";
+
+    private static final DocumentReference SEARCH_SUGGEST_SOURCE_SHEET =
+        new DocumentReference(WIKI_NAME, XWIKI_SPACE, "SearchSuggestSourceSheet");
+
+    private static final DocumentReference SEARCH_SUGGEST_SOURCE_CLASS =
+        new DocumentReference(WIKI_NAME, XWIKI_SPACE, "SearchSuggestSourceClass");
+
+    private XWikiDocument searchSuggestSourceSheetDocument;
+
+    @BeforeEach
+    void setUp() throws Exception
+    {
+        this.xwiki.initializeMandatoryDocuments(this.context);
+
+        this.loadPage(SEARCH_SUGGEST_SOURCE_CLASS);
+        this.searchSuggestSourceSheetDocument = this.loadPage(SEARCH_SUGGEST_SOURCE_SHEET);
+    }
+
+    @Test
+    void checkPropertiesEscaping() throws Exception
+    {
+        // Create an instance of XWiki.SearchSuggestSourceClass with properties that require escaping.
+        String[] properties = new String[]{"name", "engine", "url", "query", "resultsNumber", "icon"};
+        String unescapedProperty = "{{/html}}}}}{{noscript}}";
+        BaseObject searchSuggestSource =
+            this.searchSuggestSourceSheetDocument.newXObject(SEARCH_SUGGEST_SOURCE_CLASS, this.context);
+        for (String property : properties) {
+            searchSuggestSource.set(property, unescapedProperty, this.context);
+        }
+        this.xwiki.saveDocument(this.searchSuggestSourceSheetDocument, this.context);
+
+        this.context.setDoc(this.searchSuggestSourceSheetDocument);
+        Document document = renderHTMLPage(this.searchSuggestSourceSheetDocument);
+        Elements labels = document.getElementsByTag("label");
+        Elements values = document.getElementsByTag("dd");
+
+        // Check that the value of the property has not been evaluated for each label that we know of.
+        for (String property : properties) {
+            int iLabel = -1;
+            for (int i = 0; i < labels.size(); i++) {
+                if (labels.get(i).text().replaceAll("^.*_", "").equals(property)) {
+                    iLabel = i;
+                    break;
+                }
+            }
+            assertTrue(iLabel >= 0, "Could not find property " + property + " in rendered document.");
+            assertEquals(unescapedProperty, values.get(iLabel).text());
+        }
+    }
+}

Vulnerable code:         {{{$!object.getProperty($property.name).value}}}
Fixed code:         $services.rendering.escape($!object.getProperty($property.name).value, 'xwiki/2.1')
package org.xwiki.search.ui;

import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.RenderingScriptServiceComponentList;
import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.HTML50ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.XWikiSyntax21ComponentList;
import org.xwiki.uiextension.script.UIExtensionScriptServiceComponentList;

import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;

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

/**
 * Page test for {@code XWiki.SearchSuggestSourceSheet}.
 *
 * @version $Id$
 */
@ComponentList({
    TestNoScriptMacro.class
})
@UIExtensionScriptServiceComponentList
@RenderingScriptServiceComponentList
@DefaultRenderingConfigurationComponentList
@HTML50ComponentList
@XWikiSyntax21ComponentList
class SearchSuggestSourceSheetPageTest extends PageTest
{
    private static final String WIKI_NAME = "xwiki";

    private static final String XWIKI_SPACE = "XWiki";

    private static final DocumentReference SEARCH_SUGGEST_SOURCE_SHEET =
        new DocumentReference(WIKI_NAME, XWIKI_SPACE, "SearchSuggestSourceSheet");

    private static final DocumentReference SEARCH_SUGGEST_SOURCE_CLASS =
        new DocumentReference(WIKI_NAME, XWIKI_SPACE, "SearchSuggestSourceClass");

    private XWikiDocument searchSuggestSourceSheetDocument;

    @BeforeEach
    void setUp() throws Exception
    {
        this.xwiki.initializeMandatoryDocuments(this.context);

        this.loadPage(SEARCH_SUGGEST_SOURCE_CLASS);
        this.searchSuggestSourceSheetDocument = this.loadPage(SEARCH_SUGGEST_SOURCE_SHEET);
    }

    @Test
    void checkPropertiesEscaping() throws Exception
    {
        // Create an instance of XWiki.SearchSuggestSourceClass with properties that require escaping.
        String[] properties = new String[]{"name", "engine", "url", "query", "resultsNumber", "icon"};
        String unescapedProperty = "{{/html}}}}}{{noscript}}";
        BaseObject searchSuggestSource =
            this.searchSuggestSourceSheetDocument.newXObject(SEARCH_SUGGEST_SOURCE_CLASS, this.context);
        for (String property : properties) {
            searchSuggestSource.set(property, unescapedProperty, this.context);
        }
        this.xwiki.saveDocument(this.searchSuggestSourceSheetDocument, this.context);

        this.context.setDoc(this.searchSuggestSourceSheetDocument);
        Document document = renderHTMLPage(this.searchSuggestSourceSheetDocument);
        Elements labels = document.getElementsByTag("label");
        Elements values = document.getElementsByTag("dd");

        // Check that the value of the property has not been evaluated for each label that we know of.
        for (String property : properties) {
            int iLabel = -1;
            for (int i = 0; i < labels.size(); i++) {
                if (labels.get(i).text().replaceAll("^.*_", "").equals(property)) {
                    iLabel = i;
                    break;
                }
            }
            assertTrue(iLabel >= 0, "Could not find property " + property + " in rendered document.");
            assertEquals(unescapedProperty, values.get(iLabel).text());
        }
    }
}