CVE-2023-45137: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. `org.xwiki.platform:xwiki-platform-web` starting in version 3.1-milestone-2 and prior to version 13.4-rc-1, as well as `org.xwiki.platform:xwiki-platform-web-templates` prior to versions 14.10.12 and 15.5-rc-1, are vulnerable to cross-site scripting. When trying to create a document that already exists, XWiki displays an error message in the form for creating it. Due to missing escaping, this error message is vulnerable to raw HTML injection and thus XSS. The injected code is the document reference of the existing document so this requires that the attacker first creates a non-empty document whose name contains the attack code. This has been patched in `org.xwiki.platform:xwiki-platform-web` version 13.4-rc-1 and `org.xwiki.platform:xwiki-platform-web-templates` versions 14.10.12 and 15.5-rc-1 by adding the appropriate escaping. The vulnerable template file `createinline.vm` is part of XWiki's WAR and can be patched by manually applying the changes from the fix.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2023-10-25T21:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Other
Files changed: xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/createinline.vm, xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/CreateInlinePageTest.java
Commit date: 2023-06-05 12:21:28 +0200
Commit message: XWIKI-20961: Improve escaping for document exists error

* Add escaping for the document reference and add a test.

Diff: diff --git a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/createinline.vm b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/createinline.vm
index 728a7535f8b51..60ed5dd40f293 100644
--- a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/createinline.vm
+++ b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/createinline.vm
@@ -77,11 +77,11 @@
 ## check if the exception is about the document which already exists
 #if("$!exception" != '' && $exception.code == 11002)
   <div class='box errormessage'>
       ## Use the 'existingDocumentReference' context binding set by the create action for this case.
       $services.localization.render('core.create.page.error.docalreadyexists',
-        ["${existingDocumentReference}",
+        [$escapetool.xml("${existingDocumentReference}"),
          $xwiki.getURL($existingDocumentReference, 'view', ''),
          $xwiki.getURL($existingDocumentReference, 'edit', '')
         ]
       )
   </div>
diff --git a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/CreateInlinePageTest.java b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/CreateInlinePageTest.java
index c5e7c0f7d51f1..565a315c46c14 100644
--- a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/CreateInlinePageTest.java
+++ b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/CreateInlinePageTest.java
@@ -49,10 +49,16 @@ class CreateInlinePageTest extends PageTest
     /**
      * The name of the template to test.
      */
     private static final String CREATE_INLINE_VM = "createinline.vm";
 
+    private static final String DOCUMENT_REFERENCE = "xwiki:space.</div>page";
+
+    private static final String CREATE_EXCEPTION_VELOCITY_KEY = "createException";
+
+    private static final String ERROR_MESSAGE_CLASS = "errormessage";
+
     private VelocityManager velocityManager;
 
     @Inject
     private TemplateManager templateManager;
 
@@ -69,22 +75,47 @@ void setup() throws Exception
      */
     @Test
     void testNameValidationError() throws Exception
     {
         // Set "createException" to an XWikiException to simulate a validation error.
-        String documentReference = "xwiki:space.</div>page";
-        Object[] args = { documentReference };
+        Object[] args = { DOCUMENT_REFERENCE };
         XWikiException invalidNameException = new XWikiException(XWikiException.MODULE_XWIKI_STORE,
             XWikiException.ERROR_XWIKI_APP_DOCUMENT_NAME_INVALID,
             "Cannot create document {0} because its name does not respect the name strategy of the wiki.", null,
             args);
-        this.velocityManager.getVelocityContext().put("createException", invalidNameException);
-        this.velocityManager.getVelocityContext().put("invalidNameReference", documentReference);
+        this.velocityManager.getVelocityContext().put(CREATE_EXCEPTION_VELOCITY_KEY, invalidNameException);
+        this.velocityManager.getVelocityContext().put("invalidNameReference", DOCUMENT_REFERENCE);
+
+        // Render the template.
+        Document document = Jsoup.parse(this.templateManager.render(CREATE_INLINE_VM));
+        Element errormessage = document.getElementsByClass(ERROR_MESSAGE_CLASS).first();
+        assertNotNull(errormessage);
+        String expectedMessage = String.format("entitynamevalidation.create.invalidname [%s]", DOCUMENT_REFERENCE);
+        assertEquals(expectedMessage, errormessage.text());
+    }
+
+    /**
+     * Test that when there is an exception about the document already existing, the name is correctly escaped.
+     */
+    @Test
+    void testDocumentAlreadyExistsError() throws Exception
+    {
+        // Set "createException" to an XWikiException to simulate a document exists already error.
+        String urlToDocument = "space/%3C%2Fdiv%3Epage";
+        Object[] args = { DOCUMENT_REFERENCE };
+        XWikiException documentAlreadyExistsException = new XWikiException(XWikiException.MODULE_XWIKI_STORE,
+            XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY,
+            "Cannot create document {0} because it already has content", null, args);
+        this.velocityManager.getVelocityContext().put(CREATE_EXCEPTION_VELOCITY_KEY, documentAlreadyExistsException);
+        this.velocityManager.getVelocityContext().put("existingDocumentReference", DOCUMENT_REFERENCE);
 
         // Render the template.
         Document document = Jsoup.parse(this.templateManager.render(CREATE_INLINE_VM));
-        Element errormessage = document.getElementsByClass("errormessage").first();
+        Element errormessage = document.getElementsByClass(ERROR_MESSAGE_CLASS).first();
         assertNotNull(errormessage);
-        String expectedMessage = String.format("entitynamevalidation.create.invalidname [%s]", documentReference);
+        String viewURL = String.format("/xwiki/bin/view/%s", urlToDocument);
+        String editURL = String.format("/xwiki/bin/edit/%s", urlToDocument);
+        String expectedMessage = String.format("core.create.page.error.docalreadyexists [%s, %s, %s]",
+            DOCUMENT_REFERENCE, viewURL, editURL);
         assertEquals(expectedMessage, errormessage.text());
     }
 }

Vulnerable code:         ["${existingDocumentReference}",
        String documentReference = "xwiki:space.</div>page";
        Object[] args = { documentReference };
        this.velocityManager.getVelocityContext().put("createException", invalidNameException);
        this.velocityManager.getVelocityContext().put("invalidNameReference", documentReference);
        Element errormessage = document.getElementsByClass("errormessage").first();
        String expectedMessage = String.format("entitynamevalidation.create.invalidname [%s]", documentReference);
Fixed code:         [$escapetool.xml("${existingDocumentReference}"),
    private static final String DOCUMENT_REFERENCE = "xwiki:space.</div>page";

    private static final String CREATE_EXCEPTION_VELOCITY_KEY = "createException";

    private static final String ERROR_MESSAGE_CLASS = "errormessage";

        Object[] args = { DOCUMENT_REFERENCE };
        this.velocityManager.getVelocityContext().put(CREATE_EXCEPTION_VELOCITY_KEY, invalidNameException);
        this.velocityManager.getVelocityContext().put("invalidNameReference", DOCUMENT_REFERENCE);

        // Render the template.
        Document document = Jsoup.parse(this.templateManager.render(CREATE_INLINE_VM));
        Element errormessage = document.getElementsByClass(ERROR_MESSAGE_CLASS).first();
        assertNotNull(errormessage);
        String expectedMessage = String.format("entitynamevalidation.create.invalidname [%s]", DOCUMENT_REFERENCE);
        assertEquals(expectedMessage, errormessage.text());
    }

    /**
     * Test that when there is an exception about the document already existing, the name is correctly escaped.
     */
    @Test
    void testDocumentAlreadyExistsError() throws Exception
    {
        // Set "createException" to an XWikiException to simulate a document exists already error.
        String urlToDocument = "space/%3C%2Fdiv%3Epage";
        Object[] args = { DOCUMENT_REFERENCE };
        XWikiException documentAlreadyExistsException = new XWikiException(XWikiException.MODULE_XWIKI_STORE,
            XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY,
            "Cannot create document {0} because it already has content", null, args);
        this.velocityManager.getVelocityContext().put(CREATE_EXCEPTION_VELOCITY_KEY, documentAlreadyExistsException);
        this.velocityManager.getVelocityContext().put("existingDocumentReference", DOCUMENT_REFERENCE);
        Element errormessage = document.getElementsByClass(ERROR_MESSAGE_CLASS).first();
        String viewURL = String.format("/xwiki/bin/view/%s", urlToDocument);
        String editURL = String.format("/xwiki/bin/edit/%s", urlToDocument);
        String expectedMessage = String.format("core.create.page.error.docalreadyexists [%s, %s, %s]",
            DOCUMENT_REFERENCE, viewURL, editURL);