CVE-2023-38509: Transmission of Private Resources into a New Sphere ('Resource Leak') (CWE-402)
value: XWiki Platform is a generic wiki platform. In org.xwiki.platform:xwiki-platform-livetable-ui starting with version 3.5-milestone-1 and prior to versions 14.10.9 and 15.3-rc-1, the mail obfuscation configuration was not fully taken into account and is was still possible by obfuscated emails. This has been patched in XWiki 14.10.9 and XWiki 15.3-rc-1. A workaround is to modify the page `XWiki.LiveTableResultsMacros` following the patch.
CWE description: The product makes resources available to untrusted parties when those resources are only intended to be accessed by the product.
CVSS v3 base score: 4.3
Published: 2023-11-07T04:17Z
Repository: https://github.com/xwiki/xwiki-platform
Language: XML
Files changed: xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml, xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java
Commit date: 2023-04-12 14:17:25 +0200
Commit message: XWIKI-20601: Improved User Directory sorting

Diff: diff --git a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml
index 8e858ac6e8d29..2de46599d14c8 100644
--- a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml
+++ b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml
@@ -18,11 +18,11 @@
  * License along with this software; if not, write to the Free
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 -->
 
-<xwikidoc version="1.4" reference="XWiki.LiveTableResultsMacros" locale="">
+<xwikidoc version="1.5" reference="XWiki.LiveTableResultsMacros" locale="">
   <web>XWiki</web>
   <name>LiveTableResultsMacros</name>
   <language/>
   <defaultLanguage/>
   <translation>0</translation>
@@ -136,10 +136,12 @@
     #end
   #end
   #set($order = "$!request.sort")
   #if ($order == 'doc.location')
     #set ($order = 'doc.fullName')
+  #elseif ($order == 'email' &amp;&amp; $services.mail.general.shouldObfuscate())
+    #set ($order = '')
   #end
   #set ($orderSql = '')
   #if($order != '')
     #set($orderDirection = "$!{request.get('dir').toLowerCase()}")
     #if("$!orderDirection" != '' &amp;&amp; "$!orderDirection" != 'asc')
diff --git a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java
index b9f8c8e906174..56495d2359277 100644
--- a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java
+++ b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java
@@ -641,10 +641,92 @@ void obfuscateEmails(boolean obfuscate, String expectedHql, Map<String, Object>
 
         verify(this.queryService).hql(expectedHql);
         verify(this.query).bindValues(expectedBindValues);
     }
 
+    public static Stream<Arguments> provideObfuscateEmailsSort()
+    {
+        return Stream.of(
+            Arguments.of(
+                true,
+                ", BaseObject as obj   "
+                    + "where obj.name=doc.fullName "
+                    + "and obj.className = :className "
+                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  ",
+                Map.of(
+                    "className", "Space.MyClass",
+                    "classTemplate1", "Space.MyClassTemplate",
+                    "classTemplate2", "Space.MyTemplate"
+                ),
+                "t...@mail.com",
+                "t...@mail.com"
+            ),
+            Arguments.of(
+                false,
+                ", BaseObject as obj , StringProperty prop_email  "
+                    + "where obj.name=doc.fullName "
+                    + "and obj.className = :className "
+                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  "
+                    + "and obj.id=prop_email.id.id "
+                    + "and prop_email.name = :prop_email_name   "
+                    + "order by lower(prop_email.value) asc, prop_email.value asc",
+                Map.of(
+                    "className", "Space.MyClass",
+                    "classTemplate1", "Space.MyClassTemplate",
+                    "classTemplate2", "Space.MyTemplate",
+                    "prop_email_name", "email"
+                ),
+                "<a href=\"mailto:test@mail.com\">test@mail.com</a>",
+                "test@mail.com"
+            )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideObfuscateEmailsSort")
+    void obfuscateEmailsSort(boolean obfuscate, String expectedHql, Map<String, Object> expectedBindValues,
+        String expectedMail, String expectedMailValue) throws Exception
+    {
+        // TODO: We mock the mail configuration as it relies on document that are loaded through xar files from external
+        //  modules, which is currently not possible (would it be loaded using a mandatory document initializer, it 
+        //  would work though).
+        when(this.generalMailConfiguration.shouldObfuscate()).thenReturn(obfuscate);
+
+        DocumentReference myClassReference = new DocumentReference("xwiki", "Space", "MyClass");
+        XWikiDocument xClassDocument = new XWikiDocument(myClassReference);
+        xClassDocument.getXClass().addEmailField("mail", "Email", 100);
+        xClassDocument.getXClass().addTextField("name", "Name", 100);
+        this.xwiki.saveDocument(xClassDocument, this.context);
+
+        XWikiDocument xObjectDocument = new XWikiDocument(new DocumentReference("xwiki", "Space", "MyObject"));
+        xObjectDocument.setSyntax(XWIKI_2_1);
+        BaseObject baseObject = xObjectDocument.newXObject(myClassReference, this.context);
+        baseObject.set("mail", "test@mail.com", this.context);
+        baseObject.set("name", "testName", this.context);
+        this.xwiki.saveDocument(xObjectDocument, this.context);
+
+        setColumns("mail,name");
+        setClassName("Space.MyClass");
+        setSort("email", true);
+
+        when(this.queryService.hql(anyString())).thenReturn(this.query);
+        when(this.query.setLimit(anyInt())).thenReturn(this.query);
+        when(this.query.setOffset(anyInt())).thenReturn(this.query);
+        when(this.query.bindValues(any(Map.class))).thenReturn(this.query);
+        when(this.query.count()).thenReturn(1L);
+        when(this.query.execute()).thenReturn(singletonList("Space.MyObject"));
+
+        renderPage();
+
+        List<Map<String, Object>> rows = getRows();
+        assertEquals(expectedMail, StringUtils.trim((String) rows.get(0).get("mail")));
+        assertEquals(expectedMailValue, rows.get(0).get("mail_value"));
+
+        verify(this.queryService).hql(expectedHql);
+        verify(this.query).bindValues(expectedBindValues);
+    }
+
     //
     // Helper methods
     //
 
     @SuppressWarnings("unchecked")

Vulnerable code: <xwikidoc version="1.4" reference="XWiki.LiveTableResultsMacros" locale="">
Fixed code: <xwikidoc version="1.5" reference="XWiki.LiveTableResultsMacros" locale="">
  #elseif ($order == 'email' &amp;&amp; $services.mail.general.shouldObfuscate())
    #set ($order = '')
    public static Stream<Arguments> provideObfuscateEmailsSort()
    {
        return Stream.of(
            Arguments.of(
                true,
                ", BaseObject as obj   "
                    + "where obj.name=doc.fullName "
                    + "and obj.className = :className "
                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  ",
                Map.of(
                    "className", "Space.MyClass",
                    "classTemplate1", "Space.MyClassTemplate",
                    "classTemplate2", "Space.MyTemplate"
                ),
                "t...@mail.com",
                "t...@mail.com"
            ),
            Arguments.of(
                false,
                ", BaseObject as obj , StringProperty prop_email  "
                    + "where obj.name=doc.fullName "
                    + "and obj.className = :className "
                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  "
                    + "and obj.id=prop_email.id.id "
                    + "and prop_email.name = :prop_email_name   "
                    + "order by lower(prop_email.value) asc, prop_email.value asc",
                Map.of(
                    "className", "Space.MyClass",
                    "classTemplate1", "Space.MyClassTemplate",
                    "classTemplate2", "Space.MyTemplate",
                    "prop_email_name", "email"
                ),
                "<a href=\"mailto:test@mail.com\">test@mail.com</a>",
                "test@mail.com"
            )
        );
    }

    @ParameterizedTest
    @MethodSource("provideObfuscateEmailsSort")
    void obfuscateEmailsSort(boolean obfuscate, String expectedHql, Map<String, Object> expectedBindValues,
        String expectedMail, String expectedMailValue) throws Exception
    {
        // TODO: We mock the mail configuration as it relies on document that are loaded through xar files from external
        //  modules, which is currently not possible (would it be loaded using a mandatory document initializer, it 
        //  would work though).
        when(this.generalMailConfiguration.shouldObfuscate()).thenReturn(obfuscate);

        DocumentReference myClassReference = new DocumentReference("xwiki", "Space", "MyClass");
        XWikiDocument xClassDocument = new XWikiDocument(myClassReference);
        xClassDocument.getXClass().addEmailField("mail", "Email", 100);
        xClassDocument.getXClass().addTextField("name", "Name", 100);
        this.xwiki.saveDocument(xClassDocument, this.context);

        XWikiDocument xObjectDocument = new XWikiDocument(new DocumentReference("xwiki", "Space", "MyObject"));
        xObjectDocument.setSyntax(XWIKI_2_1);
        BaseObject baseObject = xObjectDocument.newXObject(myClassReference, this.context);
        baseObject.set("mail", "test@mail.com", this.context);
        baseObject.set("name", "testName", this.context);
        this.xwiki.saveDocument(xObjectDocument, this.context);

        setColumns("mail,name");
        setClassName("Space.MyClass");
        setSort("email", true);

        when(this.queryService.hql(anyString())).thenReturn(this.query);
        when(this.query.setLimit(anyInt())).thenReturn(this.query);
        when(this.query.setOffset(anyInt())).thenReturn(this.query);
        when(this.query.bindValues(any(Map.class))).thenReturn(this.query);
        when(this.query.count()).thenReturn(1L);
        when(this.query.execute()).thenReturn(singletonList("Space.MyObject"));

        renderPage();

        List<Map<String, Object>> rows = getRows();
        assertEquals(expectedMail, StringUtils.trim((String) rows.get(0).get("mail")));
        assertEquals(expectedMailValue, rows.get(0).get("mail_value"));

        verify(this.queryService).hql(expectedHql);
        verify(this.query).bindValues(expectedBindValues);
    }