CVE-2022-41935: Other (NVD-CWE-Other)
value: XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. Users without the right to view documents can deduce their existence by repeated Livetable queries. The issue has been patched in XWiki 14.6RC1, 13.10.8, and 14.4.3, the response is not properly cleaned up of obfuscated entries. As a workaround, The patch for the document `XWiki.LiveTableResultsMacros` can be manually applied or a XAR archive of a patched version can be imported, on versions 12.10.11, 13.9-rc-1, and 13.4.4. There are no known workarounds for this issue.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 4.3
Published: 2022-11-23T20:15Z
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: 2022-07-12 14:08:54 +0200
Commit message: XWIKI-19999: Livetable sources filtering improvement

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 543cf4589f40..ced0d6642b1e 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
@@ -334,11 +334,16 @@
     #set ($query = $query.addFilter($queryFilter))
   #end
   #set ($query = $query.setLimit($limit).setOffset($offset).bindValues($sqlParams))
   #set($items = $query.execute())
   #set($discard = $map.put('totalrows', $query.count()))
-  #set($discard = $map.put('returnedrows', $mathtool.min($items.size(), $limit)))
+  #if ($limit > 0)
+    #set($discard = $map.put('returnedrows', $mathtool.min($items.size(), $limit)))
+  #else
+    ## When the limit is 0, it's equivalent to no limit at all and the actual number of returned results can be used.
+    #set($discard = $map.put('returnedrows', $items.size()))
+  #end
   #set($discard = $map.put('offset', $mathtool.add($offset, 1)))
   #set($rows = [])
   #foreach($item in $items)
     #gridresult_buildRowJSON($item $rows)
   #end
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 3ba2168d6a87..cf2ae0bd5f4d 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
@@ -45,10 +45,11 @@
 import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.doc.XWikiDocument;
 import com.xpn.xwiki.objects.classes.StaticListClass;
 import com.xpn.xwiki.plugin.tag.TagPluginApi;
 
+import static java.util.Collections.emptyList;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -468,10 +469,39 @@ void removeObfuscatedResultsWhenTotalrowsLowerThanLimit() throws Exception
         Map<String, Object> viewable = rows.get(0);
         assertTrue((boolean) viewable.get("doc_viewable"));
         assertEquals("XWiki.Viewable", viewable.get("doc_fullName"));
     }
 
+    @Test
+    void removeObfuscatedResultsWhenLimitIs0() throws Exception
+    {
+        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(Arrays.asList("XWiki.NotViewable"));
+
+        when(this.oldcore.getMockContextualAuthorizationManager()
+            .hasAccess(same(Right.VIEW), eq(new DocumentReference("xwiki", "XWiki", "NotViewable")))).thenReturn(false);
+
+        this.request.put("limit", "0");
+        this.request.put("classname", "");
+        this.request.put("collist", "doc.title,doc.location,doc.content");
+        this.request.put("doc.title", "Sandbo");
+        this.request.put("doc.location", "Sandbox.TestPage3");
+        this.request.put("doc.content", "dummy");
+        this.request.put("limit", "0");
+
+        renderPage();
+
+        assertEquals(0, getTotalRowCount());
+        assertEquals(0, getRowCount());
+        assertEquals(1, getOffset());
+        assertEquals(emptyList(), getRows());
+    }
+
     //
     // Helper methods
     //
 
     @SuppressWarnings("unchecked")

Vulnerable code:   #set($discard = $map.put('returnedrows', $mathtool.min($items.size(), $limit)))
Fixed code:   #if ($limit > 0)
    #set($discard = $map.put('returnedrows', $mathtool.min($items.size(), $limit)))
  #else
    ## When the limit is 0, it's equivalent to no limit at all and the actual number of returned results can be used.
    #set($discard = $map.put('returnedrows', $items.size()))
  #end
import static java.util.Collections.emptyList;
    @Test
    void removeObfuscatedResultsWhenLimitIs0() throws Exception
    {
        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(Arrays.asList("XWiki.NotViewable"));

        when(this.oldcore.getMockContextualAuthorizationManager()
            .hasAccess(same(Right.VIEW), eq(new DocumentReference("xwiki", "XWiki", "NotViewable")))).thenReturn(false);

        this.request.put("limit", "0");
        this.request.put("classname", "");
        this.request.put("collist", "doc.title,doc.location,doc.content");
        this.request.put("doc.title", "Sandbo");
        this.request.put("doc.location", "Sandbox.TestPage3");
        this.request.put("doc.content", "dummy");
        this.request.put("limit", "0");

        renderPage();

        assertEquals(0, getTotalRowCount());
        assertEquals(0, getRowCount());
        assertEquals(1, getOffset());
        assertEquals(emptyList(), getRows());
    }