CVE-2023-35157: 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. It's possible to perform an XSS by forging a request to a delete attachment action with a specific attachment name. Now this XSS can be exploited only if the attacker knows the CSRF token of the user, or if the user ignores the warning about the missing CSRF token. The vulnerability has been patched in XWiki 15.1-rc-1 and XWiki 14.10.6.
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: 4.8
Published: 2023-06-23T19:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Java
Files changed: xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AttachmentIT.java, xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java, xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java, xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/BasePage.java
Commit date: 2023-02-16 15:59:11 +0100
Commit message: XWIKI-20339: Attachments name are not properly displayed with special characters

  * Ensure to properly escape translations
  * Provide a new integration test for it

Diff: diff --git a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AttachmentIT.java b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AttachmentIT.java
index 4b9f269cc8cd0..3aec3d3994e8c 100644
--- a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AttachmentIT.java
+++ b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/AttachmentIT.java
@@ -28,16 +28,18 @@
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 import org.openqa.selenium.By;
 import org.xwiki.flamingo.skin.test.po.AttachmentsPane;
 import org.xwiki.flamingo.skin.test.po.AttachmentsViewPage;
+import org.xwiki.model.reference.AttachmentReference;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.rest.model.jaxb.Page;
 import org.xwiki.test.docker.junit5.TestConfiguration;
 import org.xwiki.test.docker.junit5.TestReference;
 import org.xwiki.test.docker.junit5.UITest;
 import org.xwiki.test.ui.TestUtils;
+import org.xwiki.test.ui.po.BasePage;
 import org.xwiki.test.ui.po.ChangesPane;
 import org.xwiki.test.ui.po.ComparePage;
 import org.xwiki.test.ui.po.DeletePageOutcomePage;
 import org.xwiki.test.ui.po.HistoryPane;
 import org.xwiki.test.ui.po.ViewPage;
@@ -423,10 +425,29 @@ void addSeveralAttachmentsAtOnce(TestUtils setup, TestReference testReference, T
         attachmentsPane.waitForUploadToFinish(SMALL_SIZE_ATTACHMENT);
 
         assertEquals(4, attachmentsPane.getNumberOfAttachments());
     }
 
+    /**
+     * Check the display of delete attachment message when it contains special characters.
+     */
+    @Test
+    @Order(8)
+    void deleteAttachmentWithSpecialChar(TestUtils setup, TestReference testReference)
+    {
+        setup.loginAsSuperAdmin();
+        setup.createPage(testReference, "Empty content");
+        String attachmentName = "<img src=x>";
+        AttachmentReference attachmentReference = new AttachmentReference(attachmentName, testReference);
+        setup.gotoPage(attachmentReference, "delattachment");
+        BasePage basePage = new BasePage();
+        assertEquals(String.format("Error\n"
+                + "Failed to delete attachment %s\n"
+                + "This attachment does not exist.", attachmentName),
+                basePage.getXWikiMessageContent());
+    }
+
     private String getAttachmentsMacroContent(DocumentReference docRef)
     {
         StringBuilder sb = new StringBuilder();
 
         sb.append("{{velocity}}\n");
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
index 50fd11677ef2a..13c3a0fc91bc5 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
@@ -88,10 +88,11 @@
 import org.xwiki.stability.Unstable;
 import org.xwiki.template.TemplateManager;
 import org.xwiki.user.UserReference;
 import org.xwiki.user.UserReferenceResolver;
 import org.xwiki.velocity.VelocityManager;
+import org.xwiki.xml.XMLUtils;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.xpn.xwiki.XWiki;
 import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.XWikiException;
@@ -232,11 +233,11 @@ protected ContextualAuthorizationManager getContextualAuthorizationManager()
         return this.autorization;
     }
 
     protected String localizePlainOrKey(String key, Object... parameters)
     {
-        return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
+        return XMLUtils.escape(StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key));
     }
 
     protected JobProgressManager getProgress()
     {
         if (this.progress == null) {
diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java
index 7be5f39dc66d8..0525e8b62cb39 100644
--- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java
+++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java
@@ -1191,11 +1191,11 @@ private List<String> extractListFromReference(EntityReference reference)
         EntityReference spaceReference = reference.extractReference(EntityType.SPACE);
         EntityReference wikiReference = reference.extractReference(EntityType.WIKI);
         for (EntityReference singleReference : spaceReference.removeParent(wikiReference).getReversedReferenceChain()) {
             path.add(singleReference.getName());
         }
-        if (reference.getType() == EntityType.DOCUMENT) {
+        if (reference.getType() == EntityType.DOCUMENT || reference.getType() == EntityType.ATTACHMENT) {
             path.add(reference.getName());
         }
         return path;
     }
 
diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/BasePage.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/BasePage.java
index 1277051726368..9374618d82e81 100644
--- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/BasePage.java
+++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/BasePage.java
@@ -666,10 +666,22 @@ public boolean isForbidden()
             }
         }
         return false;
     }
 
+    /**
+     * @return the full text of an {@code xwikimessage} box displayed in the full content (e.g. in case of error).
+     * @since 15.1RC1
+     * @since 14.10.6
+     */
+    public String getXWikiMessageContent()
+    {
+        return getDriver()
+            .findElementWithoutWaiting(By.xpath("//div[@id = 'mainContentArea']/div[contains(@class, 'xwikimessage')]"))
+            .getText();
+    }
+
     /**
      * Use the following keyboard shortcut and wait for a new page to load. This should be only used for shortcuts that
      * indeed loads a new page.
      * 
      * @param shortcut the keyboard key combination to perform.

Vulnerable code:         return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
        if (reference.getType() == EntityType.DOCUMENT) {
Fixed code: import org.xwiki.model.reference.AttachmentReference;
import org.xwiki.test.ui.po.BasePage;
    /**
     * Check the display of delete attachment message when it contains special characters.
     */
    @Test
    @Order(8)
    void deleteAttachmentWithSpecialChar(TestUtils setup, TestReference testReference)
    {
        setup.loginAsSuperAdmin();
        setup.createPage(testReference, "Empty content");
        String attachmentName = "<img src=x>";
        AttachmentReference attachmentReference = new AttachmentReference(attachmentName, testReference);
        setup.gotoPage(attachmentReference, "delattachment");
        BasePage basePage = new BasePage();
        assertEquals(String.format("Error\n"
                + "Failed to delete attachment %s\n"
                + "This attachment does not exist.", attachmentName),
                basePage.getXWikiMessageContent());
    }

import org.xwiki.xml.XMLUtils;
        return XMLUtils.escape(StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key));
        if (reference.getType() == EntityType.DOCUMENT || reference.getType() == EntityType.ATTACHMENT) {
    /**
     * @return the full text of an {@code xwikimessage} box displayed in the full content (e.g. in case of error).
     * @since 15.1RC1
     * @since 14.10.6
     */
    public String getXWikiMessageContent()
    {
        return getDriver()
            .findElementWithoutWaiting(By.xpath("//div[@id = 'mainContentArea']/div[contains(@class, 'xwikimessage')]"))
            .getText();
    }