CVE-2023-29211 (Java, published 2023-04-16T07:15Z)
Description: value: XWiki Commons are technical libraries common to several other top level XWiki projects. Any user with view rights `WikiManager.DeleteWiki` can execute arbitrary Groovy, Python or Velocity code in XWiki leading to full access to the XWiki installation. The root cause is improper escaping of the `wikiId` url parameter. The problem has been patched on XWiki  13.10.11, 14.4.7, and 14.10.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-94 \u2014 Improper Control of Generation of Code ('Code Injection')
The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
Repository: https://github.com/xwiki/xwiki-platform
Files changed:
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-ui/src/test/java/org/xwiki/attachment/AttachmentSelectorPageTest.java
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-theme/xwiki-platform-flamingo-theme-ui/src/test/java/org/xwiki/flamingo/WebHomeSheetPageTest.java
xwiki-platform-core/xwiki-platform-help/xwiki-platform-help-ui/src/test/java/org/xwiki/help/XWikiSyntaxMacrosListPageTest.java
xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/java/org/xwiki/test/page/LocalizationSetup.java
xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/java/org/xwiki/test/page/TestNoScriptMacro.java
xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-page/src/main/resources/META-INF/components.txt
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/DisplayPageTest.java
xwiki-platform-core/xwiki-platform-wiki/xwiki-platform-wiki-ui/xwiki-platform-wiki-ui-mainwiki/pom.xml
xwiki-platform-core/xwiki-platform-wiki/xwiki-platform-wiki-ui/xwiki-platform-wiki-ui-mainwiki/src/main/resources/WikiManager/DeleteWiki.xml
xwiki-platform-core/xwiki-platform-wiki/xwiki-platform-wiki-ui/xwiki-platform-wiki-ui-mainwiki/src/test/java/org/xwiki/wiki/DeleteWikiPageTest.java
xwiki-platform-core/xwiki-platform-wiki/xwiki-platform-wiki-ui/xwiki-platform-wiki-ui-mainwiki/src/test/java/org/xwiki/wiki/WikiManagerPageTest.java
Commit message: XWIKI-20297: Improved DeleteWiki error message escaping

- Cleanup the unknown wiki error message in DeleteWiki
- Make it easier to use the TestNoScriptMacro by moving it to  xwiki-platform-test-page
- Make it easier to use the translation macro in page tests


Vulnerable code:
/*
 * 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.flamingo;

import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.macro.AbstractMacro;
import org.xwiki.rendering.transformation.MacroTransformationContext;

import static java.util.Collections.emptyList;

/**
 * This script prints an error log when it is interpreted, making the test fail.
 *
 * @version $Id$
 * @since 13.10.10
 * @since 14.4.6
 * @since 14.9RC1
 */
@Component
@Named("noscript")
@Singleton
public class TestNoScriptMacro extends AbstractMacro<Object>
{
    @Inject
    private Logger logger;

    /**
     * Default constructor.
     */
    public TestNoScriptMacro()
    {
        super("NoScript", "No Script!");
    }

    @Override
    public boolean supportsInlineMode()
    {
        return true;
    }

    @Override
    public List<Block> execute(Object parameters, String content, MacroTransformationContext context)
    {
        this.logger.error("SHOULD NOT BE CALLED");
        return emptyList();
    }
}
import java.util.Locale;

import org.xwiki.localization.Translation;
import org.xwiki.localization.TranslationBundle;
import org.xwiki.localization.TranslationBundleContext;
import org.xwiki.rendering.block.WordBlock;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
        TranslationBundleContext translationBundleContext = this.componentManager
            .getInstance(TranslationBundleContext.class);
        TranslationBundle translationBundle = mock(TranslationBundle.class);
        Translation translation = mock(Translation.class);
        when(translation.getLocale()).thenReturn(Locale.ENGLISH);
        when(translation.render(any(), any())).thenAnswer(invocationOnMock -> new WordBlock(
            "platform.flamingo.themes.home.create.csrf " + invocationOnMock.getArgument(1)));
        when(translationBundle.getTranslation(eq("platform.flamingo.themes.home.create.csrf"), any()))
            .thenReturn(translation);
        translationBundleContext.addBundle(translationBundle);

        assertEquals("platform.flamingo.themes.home.create.csrf some content\"/}}{{noscript/}}",
            Set.of("Category1", "Category2"), "My Macro Description", "WIKI");
import org.mockito.stubbing.Answer;
     * 
        
        when(lss.render(anyString())).thenAnswer(
            (Answer<String>) invocationOnMock -> {
                return invocationOnMock.getArgument(0);
        when(lss.render(anyString(), anyCollection())).thenAnswer((Answer<String>) invocationOnMock -> {
            Object key = invocationOnMock.getArgument(0);
            return parameters.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(", ", key + " [", "]"));
package org.xwiki.attachment;
 * This script prints an error log when it is interpreted, making the test fail.
 * @since 14.5
 * @since 14.4.2
 * @since 13.10.7
    private static final String DEFAULT_LABEL = "defaultValue";
        + "<option selected='selected' value='' label='defaultValue'>defaultValue</option>"
        + "<option value='value1' label='value1'>value1</option></select>"
      {{error}}{{translation key="platform.wiki.error.wikidoesnotexist" parameters="$wikiId"/}}{{/error}}
class WikiManagerTest extends PageTest

Fixed code:
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.TestNoScriptMacro;
        assertEquals("platform.flamingo.themes.home.create.csrf [some content\"/}}{{noscript/}}]",
            Set.of("Category1", "Category2"), "My Macro Description", "XWiki.WikiMacroClass_visibility_WIKI");
import java.util.Arrays;
import java.util.Locale;
import org.mockito.invocation.InvocationOnMock;
import org.xwiki.localization.Translation;
import org.xwiki.localization.TranslationBundle;
import org.xwiki.localization.TranslationBundleContext;
import org.xwiki.rendering.block.WordBlock;
import static org.mockito.ArgumentMatchers.any;

        when(lss.render(anyString())).thenAnswer(invocationOnMock -> {
                return renderString(invocationOnMock.getArgument(0), new Object[] {});
        when(lss.render(anyString(), anyCollection())).thenAnswer(invocationOnMock -> {
            return renderString(invocationOnMock.getArgument(0), parameters.toArray());

        TranslationBundleContext translationBundleContext = tcm.getInstance(TranslationBundleContext.class);
        TranslationBundle translationBundle = mock(TranslationBundle.class);
        when(translationBundle.getTranslation(any(), any()))
            .thenAnswer(invocationOnMockTranslation -> {
                Translation translation = mock(Translation.class);
                when(translation.getLocale()).thenReturn(Locale.ENGLISH);
                String translationKey = invocationOnMockTranslation.getArgument(0);
                when(translation.getKey()).thenReturn(translationKey);
                when(translation.render(any())).thenAnswer(invocationOnMockRender -> {
                    Object[] parameters = getVarArgs(invocationOnMockRender, 0);
                    return renderBlock(translationKey, parameters);
                });
                when(translation.render(any(), any())).thenAnswer(invocationOnMockRender -> {
                    Object[] parameters = getVarArgs(invocationOnMockRender, 1);
                    return renderBlock(translationKey, parameters);
                });
                return translation;
            });
        translationBundleContext.addBundle(translationBundle);
    }

    private static WordBlock renderBlock(String translationKey, Object[] parameters)
    {
        return new WordBlock(renderString(translationKey, parameters));
    }

    private static String renderString(String translationKey, Object[] parameters)
    {
        String word;
        if (parameters.length == 0) {
            word = translationKey;
        } else {
            String parametersString = Arrays.stream(parameters)
                .map(String::valueOf)
                .collect(Collectors.joining(", ", "[", "]"));
            word = String.format("%s %s", translationKey, parametersString);
        }
        return word;
    }

    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;
            }
        } else {
            parameters = new Object[] {};
        }
        return parameters;
package org.xwiki.test.page;
 * This macro prints an error log when it is executed, making the page test fail.
 * @since 13.10.11
 * @since 14.4.7
 * @since 14.10RC1
org.xwiki.test.page.TestNoScriptMacro
    private static final String DEFAULT_LABEL = "space.page_testField_";
        + "<option selected='selected' value='' label='space.page_testField_'>space.page_testField_</option>"
        + "<option value='value1' label='space.page_testField_value1'>space.page_testField_value1</option></select>"
    <!-- Runtime dependencies. -->
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rendering-xwiki</artifactId>
      <version>${project.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rendering-xwiki</artifactId>
      <version>${project.version}</version>
      <type>test-jar</type>
      <scope>test</scope>
    </dependency>
      #set ($escapedWikiId = $services.rendering.escape($escapetool.java($wikiId), 'xwiki/2.1'))
      {{error}}{{translation key="platform.wiki.error.wikidoesnotexist" parameters="~"${escapedWikiId}~""/}}{{/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.wiki;

import org.jsoup.nodes.Document;
import org.junit.jupiter.api.Test;
import org.xwiki.localization.macro.internal.TranslationMacro;
import org.xwiki.model.reference.DocumentReference;
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.test.annotation.ComponentList;
import org.xwiki.test.page.HTML50ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.XWikiSyntax21ComponentList;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test of the {@code WikiManager.DeleteWiki} page.
 *
 * @version $Id$
 * @since 13.10.11
 * @since 14.4.7
 * @since 14.10RC1
 */
@HTML50ComponentList
@XWikiSyntax21ComponentList
@RenderingScriptServiceComponentList
@ComponentList({
    ErrorMessageMacro.class,
    TranslationMacro.class,
    TestNoScriptMacro.class,
    DefaultExtendedRenderingConfiguration.class,
    RenderingConfigClassDocumentConfigurationSource.class
})
class DeleteWikiPageTest extends PageTest
{
    @Test
    void unknownWikiId() throws Exception
    {
        this.request.put("wikiId", "\" /}}{{noscript/}}");
        Document document = renderHTMLPage(new DocumentReference("xwiki", "WikiManager", "DeleteWiki"));
        assertEquals("platform.wiki.error.wikidoesnotexist [\" /}}{{noscript/}}]",
            document.select(".box.errormessage").text());
    }
}
class WikiManagerPageTest extends PageTest