CVE-2023-29523 (published 2023-04-19T00:15Z)

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. Any user who can edit their own user profile can execute arbitrary script macros including Groovy and Python macros that allow remote code execution including unrestricted read and write access to all wiki contents. The same vulnerability can also be exploited in other contexts where the `display` method on a document is used to display a field with wiki syntax, for example in applications created using `App Within Minutes`. This has been patched in XWiki 13.10.11, 14.4.8, 14.10.2 and 15.0RC1. There is no workaround apart from upgrading.

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

Weakness information:
cwe_id: CWE-74
cwe_name: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
extended_description: Software or other automated logic has certain assumptions about what constitutes data and control respectively. It is the lack of verification of these assumptions for user-controlled input that leads to injection problems. Injection problems encompass a wide variety of issues -- all mitigated in very different ways and usually attempted in order to alter the control flow of the process. For this reason, the most effective way to discuss these weaknesses is to note the distinct features that classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed.
         

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Michael Hamann
author_date: 2022-12-01 18:06:56+01:00
msg: XWIKI-20327: Escape closing HTML macro in XWikiDocument#display
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: XWikiDocument.java
change_type: ModificationType.MODIFY
diff: @@ -220,6 +220,8 @@ public class XWikiDocument implements DocumentModelBridge, Cloneable
 
     private static final String TM_FAILEDDOCUMENTPARSE = "core.document.error.failedParse";
 
+    private static final String CLOSE_HTML_MACRO = "{{/html}}";
+
     /**
      * An attachment waiting to be deleted at next document save.
      *
@@ -3885,10 +3887,19 @@ public String display(String fieldname, String type, String pref, BaseObject obj
             // We test if we're inside the rendering engine since it's also possible that this display() method is
             // called directly from a template and in this case we only want HTML as a result and not wiki syntax.
             // TODO: find a more generic way to handle html macro because this works only for XWiki 1.0 and XWiki 2.0
-            // Add the {{html}}{{/html}} only when result really contains html since it's not needed for pure text
-            if (isInRenderingEngine && !is10Syntax(wrappingSyntaxId) && HTMLUtils.containsElementText(result)) {
+            // Add the {{html}}{{/html}} only when result really contains html or { which could be part of an XWiki
+            // macro syntax since it's not needed for pure text
+            if (isInRenderingEngine && !is10Syntax(wrappingSyntaxId)
+                && (HTMLUtils.containsElementText(result) || result.indexOf("{") != -1))
+            {
                 result.insert(0, "{{html clean=\"false\" wiki=\"false\"}}");
-                result.append("{{/html}}");
+                // Escape closing HTML macro syntax.
+                int startIndex = 0;
+                // Start searching at the last match to avoid scanning the whole string again.
+                while ((startIndex = result.indexOf(CLOSE_HTML_MACRO, startIndex)) != -1) {
+                    result.replace(startIndex, startIndex + 2, "&#123;&#123;");
+                }
+                result.append(CLOSE_HTML_MACRO);
             }
 
             return result.toString();

programming_language: Java
filename: XWikiDocumentTest.java
change_type: ModificationType.MODIFY
diff: @@ -59,6 +59,7 @@
 import com.xpn.xwiki.api.DocumentSection;
 import com.xpn.xwiki.objects.BaseObject;
 import com.xpn.xwiki.objects.classes.BaseClass;
+import com.xpn.xwiki.objects.classes.PropertyClass;
 import com.xpn.xwiki.objects.classes.TextAreaClass;
 import com.xpn.xwiki.store.XWikiStoreInterface;
 import com.xpn.xwiki.store.XWikiVersioningStoreInterface;
@@ -76,10 +77,12 @@
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -647,6 +650,30 @@ public void displayTemplate20()
         assertEquals("<p>area</p>", this.document.display("area", "view", this.oldcore.getXWikiContext()));
     }
 
+    @Test
+    void displayEscapesClosingHTMLMacro()
+    {
+        this.oldcore.getXWikiContext().put("isInRenderingEngine", true);
+        when(this.xWiki.getCurrentContentSyntaxId(any())).thenReturn("xwiki/2.1");
+        this.document.setSyntax(Syntax.XWIKI_2_0);
+
+        BaseObject object = mock(BaseObject.class);
+        when(object.getOwnerDocument()).thenReturn(this.document);
+
+        BaseClass xClass = mock(BaseClass.class);
+        when(object.getXClass(any())).thenReturn(xClass);
+        PropertyClass propertyInterface = mock(PropertyClass.class);
+        when(xClass.get("mock")).thenReturn(propertyInterface);
+        doAnswer(call -> {
+            call.getArgument(0, StringBuffer.class).append("{{/html}}content{{/html}}");
+            return null;
+        }).when(propertyInterface).displayView(any(StringBuffer.class), eq("mock"), any(String.class), eq(object),
+            anyBoolean(), any(XWikiContext.class));
+
+        assertEquals("{{html clean=\"false\" wiki=\"false\"}}&#123;&#123;/html}}content&#123;&#123;/html}}{{/html}}",
+            this.document.display("mock", "view", object, this.oldcore.getXWikiContext()));
+    }
+
     @Test
     public void convertSyntax() throws XWikiException
     {

programming_language: Java