CVE-2023-29213: Cross-Site Request Forgery (CSRF) (CWE-352)
value: 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.

CWE 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.
CVSS v3 base score: 8.8
Published: 2023-04-17T22:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: XML
Files changed: xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/pom.xml, xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/LoggingScriptServiceComponentList.java, xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/NullLoggerManager.java, xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/pom.xml, xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/Logging/Code/Translations.xml, xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/LoggingAdmin.xml, xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/test/java/org/xwiki/logging/LoggingAdminPageTest.java, xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/java/org/xwiki/test/page/LocalizationSetup.java
Commit date: 2022-10-28 15:35:07 +0200
Commit message: XWIKI-20291: Improved translations of logging administration

Diff: diff --git a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/pom.xml b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/pom.xml
index 5e1acc480dd36..450d98ddbe967 100644
--- a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/pom.xml
@@ -60,6 +60,28 @@
       <artifactId>xwiki-commons-tool-test-component</artifactId>
       <version>${commons.version}</version>
       <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>
diff --git a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/LoggingScriptServiceComponentList.java b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/LoggingScriptServiceComponentList.java
new file mode 100644
index 0000000000000..bb431c20aff78
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/LoggingScriptServiceComponentList.java
@@ -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
+{
+}
diff --git a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/NullLoggerManager.java b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/NullLoggerManager.java
new file mode 100644
index 0000000000000..9bacd550aa36d
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-script/src/test/java/org/xwiki/logging/NullLoggerManager.java
@@ -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;
+    }
+}
diff --git a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/pom.xml b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/pom.xml
index 4f60e9433a4ee..2607c86bfbc1e 100644
--- a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/pom.xml
@@ -78,7 +78,38 @@
       <groupId>org.xwiki.platform</groupId>
       <artifactId>xwiki-platform-livedata-livetable</artifactId>
       <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>
diff --git a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/Logging/Code/Translations.xml b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/Logging/Code/Translations.xml
new file mode 100644
index 0000000000000..95816a2ddf216
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/Logging/Code/Translations.xml
@@ -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>
diff --git a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/LoggingAdmin.xml b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/LoggingAdmin.xml
index a5fc7570349dd..cf040cee8f2de 100644
--- a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/LoggingAdmin.xml
+++ b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/main/resources/XWiki/LoggingAdmin.xml
@@ -42,24 +42,32 @@
 {{velocity}}
 #set($logging = $services.logging)
 ##
 ## 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
 ##
 #set ($properties = ['logger', 'level'])
@@ -75,10 +83,11 @@
     'displayer': 'html',
     'sortable': false,
     'filterable': false
   }))
 #end
+
 {{liveData
   id="logging"
   properties="$stringtool.join($properties, ',')"
   source="liveTable"
   sourceParameters="resultPage=XWiki.LoggingAdminTableJson&amp;translationPrefix=logging.admin.livetable."
diff --git a/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/test/java/org/xwiki/logging/LoggingAdminPageTest.java b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/test/java/org/xwiki/logging/LoggingAdminPageTest.java
new file mode 100644
index 0000000000000..c3bdd1940c88a
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-logging/xwiki-platform-logging-ui/src/test/java/org/xwiki/logging/LoggingAdminPageTest.java
@@ -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);
+    }
+}
diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/java/org/xwiki/test/page/LocalizationSetup.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/java/org/xwiki/test/page/LocalizationSetup.java
index d3cf1ee6929e2..07bef0050866d 100644
--- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/java/org/xwiki/test/page/LocalizationSetup.java
+++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/java/org/xwiki/test/page/LocalizationSetup.java
@@ -125,17 +125,13 @@ 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[] {};
         }
         return parameters;
     }

Vulnerable code: #if ($request.loggeraction_set &amp;&amp; $request.logger_name &amp;&amp; $xwiki.hasAccessLevel('programming'))
  #if ($!logger_list.containsKey($request.logger_name))
      {{success}}Logger ${request.logger_name} level has been unset.{{/success}}
      #set($void = $logging.setLevel($request.logger_name, $null))
      {{success}}Logger ${request.logger_name} level has been set to ${request.logger_level}.{{/success}}
      #set($void = $logging.setLevel($request.logger_name, $request.logger_level))
    {{error}}Failed to set log level: the logger "$request.logger_name" doesn't exist.{{/error}}

        if (invocationOnMockRender.getArguments().length > i) {
            Object argument = invocationOnMockRender.getArgument(i);
            if (argument instanceof String) {
                parameters = new Object[] { argument };
            } else {
                parameters = (Object[]) argument;
            }
Fixed code:   <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>
/*
 * 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
{
}
/*
 * 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;
    }
}
    <!-- 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>
<?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>
#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'))
  #if ($!logger_list.containsKey($loggerName))
      {{success}}
        {{translation key='logging.admin.unsetLevel.success' parameters="~"${loggerNameEscaped}~""/}}
      {{/success}}
      #set($void = $logging.setLevel($loggerName, $null))
      {{success}}
        {{translation key='logging.admin.setLevel.success'
          parameters="~"${loggerNameEscaped}~",~"${loggerLevelEscaped}~""/}}
      {{/success}}
      #set($void = $logging.setLevel($loggerName, $request.logger_level))
    {{error}}{{translation key='logging.admin.setLevel.error' parameters="~"${loggerNameEscaped}~""/}}{{/error}}

/*
 * 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);
    }
}
        Object[] arguments = invocationOnMockRender.getArguments();
        if (arguments.length > i) {
            parameters = Arrays.copyOfRange(arguments, i, arguments.length);