CVE-2023-37914 (published 2023-08-17T18:15Z)

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. Any user who can view `Invitation.WebHome` 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. This vulnerability has been patched on XWiki 14.4.8, 15.2-rc-1, and 14.10.6. Users are advised to upgrade. Users unable to upgrade may manually apply the patch on `Invitation.InvitationCommon` and `Invitation.InvitationConfig`, but there are otherwise no known workarounds for this vulnerability.

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-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which 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. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Manuel Leduc
author_date: 2023-02-28 17:13:51+01:00
msg: XWIKI-20421: Improved escaping of subjectLineTemplate
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: InvitationCommon.xml
change_type: ModificationType.MODIFY
diff: @@ -825,7 +825,7 @@
       <smtp_server_username/>
     </property>
     <property>
-      <subjectLineTemplate>{{velocity}}$services.localization.render('xe.invitation.emailContent.subjectLine', [$xcontext.getUser().replaceAll("^[^\.]*.", ""), $xwiki.getRequestURL().replaceAll("https?://([^/:]*).*$", "$1"), $!subjectLine]){{/velocity}}</subjectLineTemplate>
+      <subjectLineTemplate>{{velocity wiki='false'}}$services.localization.render('xe.invitation.emailContent.subjectLine', [$xcontext.getUser().replaceAll("^[^\.]*.", ""), $xwiki.getRequestURL().replaceAll("https?://([^/:]*).*$", "$1"), $!subjectLine]){{/velocity}}</subjectLineTemplate>
     </property>
     <property>
       <usersMayPersonalizeMessage>1</usersMayPersonalizeMessage>

programming_language: PHP
filename: InvitationConfig.xml
change_type: ModificationType.MODIFY
diff: @@ -420,7 +420,7 @@
       <smtp_server_username/>
     </property>
     <property>
-      <subjectLineTemplate>{{velocity}}$services.localization.render('xe.invitation.emailContent.subjectLine', [$xcontext.getUser().replaceAll("^[^\.]*.", ""), $xwiki.getRequestURL().replaceAll("https?://([^/:]*).*$", "$1"), $!subjectLine]){{/velocity}}</subjectLineTemplate>
+      <subjectLineTemplate>{{velocity wiki='false'}}$services.localization.render('xe.invitation.emailContent.subjectLine', [$xcontext.getUser().replaceAll("^[^\.]*.", ""), $xwiki.getRequestURL().replaceAll("https?://([^/:]*).*$", "$1"), $!subjectLine]){{/velocity}}</subjectLineTemplate>
     </property>
     <property>
       <usersMayPersonalizeMessage>1</usersMayPersonalizeMessage>

programming_language: Jupyter Notebook
filename: InvitationCommonPageTest.java
change_type: ModificationType.MODIFY
diff: @@ -33,7 +33,6 @@
 import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList;
 import org.xwiki.rendering.internal.macro.message.ErrorMessageMacro;
 import org.xwiki.rendering.internal.macro.message.InfoMessageMacro;
-import org.xwiki.rendering.syntax.Syntax;
 import org.xwiki.script.ScriptContextManager;
 import org.xwiki.security.authorization.AuthorizationManager;
 import org.xwiki.test.annotation.ComponentList;
@@ -51,6 +50,9 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
+import static org.xwiki.rendering.syntax.Syntax.PLAIN_1_0;
+import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_0;
+import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_1;
 
 /**
  * Test of {@code Invitation.InvitationCommon}.
@@ -73,6 +75,9 @@
 })
 class InvitationCommonPageTest extends PageTest
 {
+    public static final DocumentReference INVITATION_COMMON_REFERENCE =
+        new DocumentReference("xwiki", "Invitation", "InvitationCommon");
+
     private ScriptContext scriptContext;
 
     @BeforeEach
@@ -91,8 +96,7 @@ void testEq0() throws Exception
         this.context.setDoc(this.xwiki.getDocument(
             new DocumentReference("xwiki", "]]  {{noscript/}}", "InvitationCommon"), this.context));
 
-        DocumentReference invitationCommonReference = new DocumentReference("xwiki", "Invitation", "InvitationCommon");
-        Document document = Jsoup.parse(loadPage(invitationCommonReference).getRenderedContent(this.context));
+        Document document = Jsoup.parse(loadPage(INVITATION_COMMON_REFERENCE).getRenderedContent(this.context));
         assertEquals("xe.invitation.internalDocument []] {{noscript/}}.WebHome]",
             document.selectFirst(".infomessage").text());
     }
@@ -101,7 +105,6 @@ void testEq0() throws Exception
     void testEq1ConfigClassIsNew() throws Exception
     {
         String spaceName = "<script>console.log</script>]]{{noscript/}}";
-        DocumentReference invitationCommonReference = new DocumentReference("xwiki", "Invitation", "InvitationCommon");
 
         XWikiDocument doc =
             this.xwiki.getDocument(new DocumentReference("xwiki", spaceName, "InvitationCommon"), this.context);
@@ -110,7 +113,7 @@ void testEq1ConfigClassIsNew() throws Exception
 
         this.request.put("test", "1");
 
-        Document document = Jsoup.parse(loadPage(invitationCommonReference).getRenderedContent(this.context));
+        Document document = Jsoup.parse(loadPage(INVITATION_COMMON_REFERENCE).getRenderedContent(this.context));
 
         assertEquals("testLoadInvitationConfig", document.selectFirst(".infomessage").text());
         Element errorMessage = document.selectFirst(".errormessage");
@@ -148,7 +151,7 @@ void testEq1ConfigClassExistsInvalidFromAddress() throws Exception
 
         this.request.put("test", "1");
 
-        XWikiDocument invitationCommonDoc = loadPage(new DocumentReference("xwiki", "Invitation", "InvitationCommon"));
+        XWikiDocument invitationCommonDoc = loadPage(INVITATION_COMMON_REFERENCE);
         Document document = Jsoup.parse(invitationCommonDoc.getRenderedContent(this.context));
 
         assertEquals("testLoadInvitationConfig", document.selectFirst(".infomessage").text());
@@ -187,8 +190,7 @@ void testEq1ConfigClassExistsConfigMapTooSmall() throws Exception
 
         this.request.put("test", "1");
 
-        DocumentReference reference = new DocumentReference("xwiki", "Invitation", "InvitationCommon");
-        XWikiDocument invitationCommonDoc = loadPage(reference);
+        XWikiDocument invitationCommonDoc = loadPage(INVITATION_COMMON_REFERENCE);
         Document document = Jsoup.parse(invitationCommonDoc.getRenderedContent(this.context));
 
         assertEquals("testLoadInvitationConfig", document.selectFirst(".infomessage").text());
@@ -229,8 +231,7 @@ void testEq1ConfigClassExistsNewInvitationConfig() throws Exception
 
         this.request.put("test", "1");
 
-        DocumentReference reference = new DocumentReference("xwiki", "Invitation", "InvitationCommon");
-        XWikiDocument invitationCommonDoc = loadPage(reference);
+        XWikiDocument invitationCommonDoc = loadPage(INVITATION_COMMON_REFERENCE);
         Document document = Jsoup.parse(invitationCommonDoc.getRenderedContent(this.context));
 
         assertEquals("testLoadInvitationConfig", document.selectFirst(".infomessage").text());
@@ -241,7 +242,7 @@ void testEq1ConfigClassExistsNewInvitationConfig() throws Exception
     @Test
     void displayMessageVelocityMacro() throws Exception
     {
-        loadPage(new DocumentReference("xwiki", "Invitation", "InvitationCommon"));
+        loadPage(INVITATION_COMMON_REFERENCE);
         DocumentReference invitationMailClassDocumentReference =
             new DocumentReference("xwiki", "Invitation", "InvitationMailClass");
         loadPage(invitationMailClassDocumentReference);
@@ -249,7 +250,7 @@ void displayMessageVelocityMacro() throws Exception
         XWikiDocument page = this.xwiki.getDocument(new DocumentReference("xwiki", "Space", "Page"), this.context);
         BaseObject invitationMailXObject = page.newXObject(invitationMailClassDocumentReference, this.context);
         invitationMailXObject.set("messageBody", "<strong>message body</strong>", this.context);
-        page.setSyntax(Syntax.XWIKI_2_1);
+        page.setSyntax(XWIKI_2_1);
         page.setContent("{{include reference=\"Invitation.InvitationCommon\"/}}\n"
             + "\n"
             + "{{velocity}}\n"
@@ -262,4 +263,22 @@ void displayMessageVelocityMacro() throws Exception
         Document document = renderHTMLPage(page);
         assertEquals("<strong>message body</strong>", document.selectFirst("#preview-messagebody-field").html());
     }
+
+    /**
+     * Check if the value of subjectLine correctly escape its parameters.
+     */
+    @Test
+    void subjectLineTemplate() throws Exception
+    {
+        com.xpn.xwiki.api.Document invitationCommonDocument =
+            new com.xpn.xwiki.api.Document(loadPage(INVITATION_COMMON_REFERENCE), this.context);
+        String value = String.valueOf(
+            invitationCommonDocument.getObject("xwiki:Invitation.WebHome").getProperty("subjectLineTemplate")
+                .getValue());
+        this.oldcore.getScriptContext().setAttribute("subjectLine", "{{noscript/}}", GLOBAL_SCOPE);
+        com.xpn.xwiki.api.Document testDocument = new com.xpn.xwiki.api.Document(
+            this.xwiki.getDocument(new DocumentReference("xwiki", "Space", "Test"), this.context), this.context);
+        String renderedContent = testDocument.getRenderedContent(value, XWIKI_2_0.toIdString(), PLAIN_1_0.toIdString());
+        assertEquals("xe.invitation.emailContent.subjectLine [XWikiGuest, null, {{noscript/}}]", renderedContent);
+    }
 }

programming_language: Java
filename: InvitationConfigPageTest.java
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,10 @@
 
 import com.xpn.xwiki.doc.XWikiDocument;
 
+import static javax.script.ScriptContext.GLOBAL_SCOPE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.xwiki.rendering.syntax.Syntax.PLAIN_1_0;
+import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_0;
 
 /**
  * Test of {@code Invitation.InvitationConfig}.
@@ -59,13 +62,13 @@
 })
 class InvitationConfigPageTest extends PageTest
 {
-    private static final DocumentReference INVITATION_CONFIG_DOCUMENT_REFERENCE =
+    private static final DocumentReference INVITATION_CONFIG_REFERENCE =
         new DocumentReference("xwiki", "Invitation", "InvitationConfig");
 
     @Test
     void escapeInfoMessageInternalDocumentParameter() throws Exception
     {
-        XWikiDocument invitationGuestActionsDocument = loadPage(INVITATION_CONFIG_DOCUMENT_REFERENCE);
+        XWikiDocument invitationGuestActionsDocument = loadPage(INVITATION_CONFIG_REFERENCE);
 
         // Set up the current doc in the context so that $doc is bound in scripts
         this.context.setDoc(
@@ -75,4 +78,19 @@ void escapeInfoMessageInternalDocumentParameter() throws Exception
         Element infomessage = document.selectFirst(".infomessage");
         assertEquals("xe.invitation.internalDocument [Invitation.WebHome]", infomessage.text());
     }
+
+    @Test
+    void subjectLineTemplate() throws Exception
+    {
+        com.xpn.xwiki.api.Document invitationConfigDocument =
+            new com.xpn.xwiki.api.Document(loadPage(INVITATION_CONFIG_REFERENCE), this.context);
+        String value = String.valueOf(
+            invitationConfigDocument.getObject("xwiki:Invitation.WebHome").getProperty("subjectLineTemplate")
+                .getValue());
+        this.oldcore.getScriptContext().setAttribute("subjectLine", "{{noscript/}}", GLOBAL_SCOPE);
+        com.xpn.xwiki.api.Document testDocument = new com.xpn.xwiki.api.Document(
+            this.xwiki.getDocument(new DocumentReference("xwiki", "Space", "Test"), this.context), this.context);
+        String renderedContent = testDocument.getRenderedContent(value, XWIKI_2_0.toIdString(), PLAIN_1_0.toIdString());
+        assertEquals("xe.invitation.emailContent.subjectLine [XWikiGuest, null, {{noscript/}}]", renderedContent);
+    }
 }

programming_language: Java