CVE-2023-29213 (published 2023-04-17T22:15Z)

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. In affected versions of `org.xwiki.platform:xwiki-platform-logging-ui` it is possible to trick a user with programming rights into visiting a constructed url where e.g., by embedding an image with this URL in a document that is viewed by a user with programming rights which will evaluate an expression in the constructed url and execute it. This issue has been addressed in versions 13.10.11, 14.4.7, and 14.10. Users are advised to upgrade. There are no known workarounds for this vulnerability.\n

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

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: 2022-10-28 15:05:16+02:00
msg: XWIKI-20291: Improved translations of logging administration
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: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -62,4 +62,26 @@
       <scope>test</scope>
     </dependency>
   </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>test-jar</id>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+            <configuration>
+              <includes>
+                <include>**/LoggingScriptServiceComponentList.class</include>
+                <include>**/NullLoggerManager.class</include>
+              </includes>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
 </project>

programming_language: HTML
filename: LoggingScriptServiceComponentList.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,53 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * 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.
+ */
+package org.xwiki.logging;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.xwiki.logging.script.LoggingScriptService;
+import org.xwiki.test.annotation.ComponentList;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Component list for {@link LoggingScriptService}.
+ * 
+ * @version $Id$
+ * @since 13.10.11
+ * @since 14.4.7
+ * @since 14.10RC1
+ */
+@Documented
+@Retention(RUNTIME)
+@Target({ TYPE, METHOD, ANNOTATION_TYPE })
+@ComponentList({
+    LoggingScriptService.class,
+    NullLoggerManager.class
+})
+@Inherited
+public @interface LoggingScriptServiceComponentList
+{
+}

programming_language: Java
filename: NullLoggerManager.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,66 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * 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.
+ */
+package org.xwiki.logging;
+
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.xwiki.observation.EventListener;
+
+/**
+ * Null logger manager, to be overridden if need be.
+ * 
+ * @version $Id$
+ * @since 13.10.11
+ * @since 14.4.7
+ * @since 14.10RC1
+ */
+public class NullLoggerManager implements LoggerManager
+{
+    @Override
+    public void pushLogListener(EventListener listener)
+    {
+        
+    }
+
+    @Override
+    public EventListener popLogListener()
+    {
+        return null;
+    }
+
+    @Override
+    public void setLoggerLevel(String loggerName, LogLevel level)
+    {
+
+    }
+
+    @Override
+    public LogLevel getLoggerLevel(String loggerName)
+    {
+        return null;
+    }
+
+    @Override
+    public Collection<Logger> getLoggers()
+    {
+        return null;
+    }
+}

programming_language: Java
filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -80,5 +80,36 @@
       <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
+    <!-- Test dependencies. -->
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-test-page</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <!-- Provides the component list for RenderingScriptService. -->
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-rendering-xwiki</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <!-- Provides the component list for the LiveData macro. -->
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-livedata-macro</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <!-- Required for LoggingScriptService. -->
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-logging-script</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>

programming_language: HTML
filename: Translations.xml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,79 @@
+<?xml version="1.1" encoding="UTF-8"?>
+
+<!--
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * 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.5" reference="XWiki.Logging.Code.Translations" locale="">
+  <web>XWiki.Logging.Code</web>
+  <name>Translations</name>
+  <language/>
+  <defaultLanguage>en</defaultLanguage>
+  <translation>0</translation>
+  <creator>xwiki:XWiki.Admin</creator>
+  <parent>WebHome</parent>
+  <author>xwiki:XWiki.Admin</author>
+  <contentAuthor>xwiki:XWiki.Admin</contentAuthor>
+  <version>1.1</version>
+  <title>Translations</title>
+  <comment/>
+  <minorEdit>false</minorEdit>
+  <syntaxId>plain/1.0</syntaxId>
+  <hidden>true</hidden>
+  <content>logging.admin.unsetLevel.success=Logger "{0}" level has been unset.
+logging.admin.setLevel.success=Logger "{0}" level has been set to "{1}".
+logging.admin.setLevel.error=Failed to set log level: the logger "{0}" doesn't exist.</content>
+  <object>
+    <name>XWiki.Logging.Code.Translations</name>
+    <number>0</number>
+    <className>XWiki.TranslationDocumentClass</className>
+    <guid>fc29bfac-011a-4573-80ba-3f5713f1e847</guid>
+    <class>
+      <name>XWiki.TranslationDocumentClass</name>
+      <customClass/>
+      <customMapping/>
+      <defaultViewSheet/>
+      <defaultEditSheet/>
+      <defaultWeb/>
+      <nameField/>
+      <validationScript/>
+      <scope>
+        <cache>0</cache>
+        <disabled>0</disabled>
+        <displayType>select</displayType>
+        <freeText>forbidden</freeText>
+        <largeStorage>0</largeStorage>
+        <multiSelect>0</multiSelect>
+        <name>scope</name>
+        <number>1</number>
+        <prettyName>Scope</prettyName>
+        <relationalStorage>0</relationalStorage>
+        <separator> </separator>
+        <separators>|, </separators>
+        <size>1</size>
+        <unmodifiable>0</unmodifiable>
+        <values>GLOBAL|WIKI|USER|ON_DEMAND</values>
+        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
+      </scope>
+    </class>
+    <property>
+      <scope>WIKI</scope>
+    </property>
+  </object>
+</xwikidoc>

programming_language: TypeScript
filename: LoggingAdmin.xml
change_type: ModificationType.MODIFY
diff: @@ -44,20 +44,28 @@
 ##
 ## Setting the logger level if button has been pressed ##
 ##
-#if ($request.loggeraction_set &amp;&amp; $request.logger_name &amp;&amp; $xwiki.hasAccessLevel('programming'))
+#set ($loggerName = $request.logger_name)
+#set ($loggerNameEscaped = $services.rendering.escape($escapetool.java($loggerName), 'xwiki/2.1'))
+#set ($loggerLevel = $request.logger_level)
+#set ($loggerLevelEscaped = $services.rendering.escape($escapetool.java($loggerLevel), 'xwiki/2.1'))
+#if ($request.loggeraction_set &amp;&amp; $loggerName &amp;&amp; $xwiki.hasAccessLevel('programming'))
   #set($logger_list = $logging.getLevels())
-  #if ($!logger_list.containsKey($request.logger_name))
+  #if ($!logger_list.containsKey($loggerName))
     #if ($request.logger_level == '')
-      {{success}}Logger ${request.logger_name} level has been unset.{{/success}}
-      #set($void = $logging.setLevel($request.logger_name, $null))
+      {{success}}
+        {{translation key='logging.admin.unsetLevel.success' parameters="~"${loggerNameEscaped}~""/}}
+      {{/success}}
+      #set($void = $logging.setLevel($loggerName, $null))
     #else
-      {{success}}Logger ${request.logger_name} level has been set to ${request.logger_level}.{{/success}}
-      #set($void = $logging.setLevel($request.logger_name, $request.logger_level))
+      {{success}}
+        {{translation key='logging.admin.setLevel.success'
+          parameters="~"${loggerNameEscaped}~",~"${loggerLevelEscaped}~""/}}
+      {{/success}}
+      #set($void = $logging.setLevel($loggerName, $request.logger_level))
     #end
   #else
-    {{error}}Failed to set log level: the logger "$request.logger_name" doesn't exist.{{/error}}
+    {{error}}{{translation key='logging.admin.setLevel.error' parameters="~"${loggerNameEscaped}~""/}}{{/error}}
   #end
-
 #end
 ##
 ## Live Data
@@ -77,6 +85,7 @@
     'filterable': false
   }))
 #end
+
 {{liveData
   id="logging"
   properties="$stringtool.join($properties, ',')"

programming_language: TypeScript
filename: LoggingAdminPageTest.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,138 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * 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.
+ */
+package org.xwiki.logging;
+
+import java.util.HashMap;
+
+import org.jsoup.nodes.Document;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.xwiki.icon.IconManager;
+import org.xwiki.livedata.internal.macro.LiveDataMacroComponentList;
+import org.xwiki.localization.macro.internal.TranslationMacro;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.properties.internal.converter.EnumConverter;
+import org.xwiki.rendering.RenderingScriptServiceComponentList;
+import org.xwiki.rendering.internal.configuration.DefaultExtendedRenderingConfiguration;
+import org.xwiki.rendering.internal.configuration.RenderingConfigClassDocumentConfigurationSource;
+import org.xwiki.rendering.internal.macro.message.ErrorMessageMacro;
+import org.xwiki.rendering.internal.macro.message.SuccessMessageMacro;
+import org.xwiki.security.authorization.ContextualAuthorizationManager;
+import org.xwiki.test.annotation.ComponentList;
+import org.xwiki.test.page.HTML50ComponentList;
+import org.xwiki.test.page.PageTest;
+import org.xwiki.test.page.XWikiSyntax21ComponentList;
+
+import com.xpn.xwiki.plugin.skinx.SkinExtensionPluginApi;
+
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test of {@code XWiki.LoggingAdmin}.
+ *
+ * @version $Id$
+ * @since 13.10.11
+ * @since 14.4.7
+ * @since 14.10RC1
+ */
+@XWikiSyntax21ComponentList
+@HTML50ComponentList
+@RenderingScriptServiceComponentList
+@LiveDataMacroComponentList
+@LoggingScriptServiceComponentList
+@ComponentList({
+    TranslationMacro.class,
+    DefaultExtendedRenderingConfiguration.class,
+    RenderingConfigClassDocumentConfigurationSource.class,
+    ErrorMessageMacro.class,
+    SuccessMessageMacro.class,
+    EnumConverter.class
+})
+class LoggingAdminPageTest extends PageTest
+{
+    private LoggerManager loggerManager;
+
+    @BeforeEach
+    void setUp() throws Exception
+    {
+        // Spy the jsfx plugin used during the macro rendering to return a mock of its API when required. 
+        when(this.oldcore.getSpyXWiki().getPluginApi("jsfx", this.context))
+            .thenReturn(mock(SkinExtensionPluginApi.class));
+        // Return minimal icons metadata since this is not what we want to test here.
+        IconManager iconManager = this.componentManager.registerMockComponent(IconManager.class);
+        doReturn(new HashMap<>()).when(iconManager).getMetaData(anyString());
+        // Setting all rights as allowed by default.
+        when(this.xwiki.getRightService().hasAccessLevel(any(), any(), anyString(), eq(this.context)))
+            .thenReturn(true);
+        when(this.componentManager.<ContextualAuthorizationManager>getInstance(ContextualAuthorizationManager.class)
+            .hasAccess(any())).thenReturn(true);
+        // Register a mocked LoggerManager as we want to control the logger level. 
+        this.loggerManager = this.componentManager.registerMockComponent(LoggerManager.class);
+        Logger loggerA = mock(Logger.class);
+        when(loggerA.getName()).thenReturn("loggerA");
+        when(this.loggerManager.getLoggers()).thenReturn(asList(loggerA));
+    }
+
+    @Test
+    void unknownLogger() throws Exception
+    {
+        this.request.put("loggeraction_set", "1");
+        this.request.put("logger_name", "{{cache}}{{groovy}}new File(\"/tmp/exploit.txt\").withWriter { out -> "
+            + "out.println(\"created from notification filter preferences!\"); "
+            + "}{{/groovy}}{{/cache}}<strong>bold</strong>\"'");
+        this.request.put("logger_level", "TRACE");
+        Document document = renderHTMLPage(new DocumentReference("xwiki", "XWiki", "LoggingAdmin"));
+        assertEquals("logging.admin.setLevel.error [{{cache}}{{groovy}}new File(\"/tmp/exploit.txt\")"
+            + ".withWriter { out -> out.println(\"created from notification filter preferences!\"); }{{/groovy}}"
+            + "{{/cache}}<strong>bold</strong>\"']", document.select(".box.errormessage").text());
+        verify(this.loggerManager, never()).setLoggerLevel(any(), any());
+    }
+
+    @Test
+    void unsetLogger() throws Exception
+    {
+        this.request.put("loggeraction_set", "1");
+        this.request.put("logger_name", "loggerA");
+        this.request.put("logger_level", "");
+        Document document = renderHTMLPage(new DocumentReference("xwiki", "XWiki", "LoggingAdmin"));
+        assertEquals("logging.admin.unsetLevel.success [loggerA]", document.select(".box.successmessage").text());
+        verify(this.loggerManager).setLoggerLevel("loggerA", null);
+    }
+
+    @Test
+    void setLogger() throws Exception
+    {
+        this.request.put("loggeraction_set", "1");
+        this.request.put("logger_name", "loggerA");
+        this.request.put("logger_level", "TRACE");
+        Document document = renderHTMLPage(new DocumentReference("xwiki", "XWiki", "LoggingAdmin"));
+        assertEquals("logging.admin.setLevel.success [loggerA, TRACE]", document.select(".box.successmessage").text());
+        verify(this.loggerManager).setLoggerLevel("loggerA", LogLevel.TRACE);
+    }
+}

programming_language: Java
filename: LocalizationSetup.java
change_type: ModificationType.MODIFY
diff: @@ -127,13 +127,9 @@ private static String renderString(String translationKey, Object[] parameters)
     private static Object[] getVarArgs(InvocationOnMock invocationOnMockRender, int i)
     {
         Object[] parameters;
-        if (invocationOnMockRender.getArguments().length > i) {
-            Object argument = invocationOnMockRender.getArgument(i);
-            if (argument instanceof String) {
-                parameters = new Object[] { argument };
-            } else {
-                parameters = (Object[]) argument;
-            }
+        Object[] arguments = invocationOnMockRender.getArguments();
+        if (arguments.length > i) {
+            parameters = Arrays.copyOfRange(arguments, i, arguments.length);
         } else {
             parameters = new Object[] {};
         }

programming_language: Java