CVE-2023-34465 (Java, published 2023-06-23T16:15Z)
Description: value: XWiki Platform is a generic wiki platform. Starting in version 11.8-rc-1 and prior to versions 14.4.8, 14.10.6, and 15.2, `Mail.MailConfig` can be edited by any logged-in user by default. Consequently, they can change the mail obfuscation configuration and view and edit the mail sending configuration, including the smtp domain name and credentials. The problem has been patched in XWiki 14.4.8, 14.10.6, and 15.1. As a workaround, the rights of the `Mail.MailConfig` page can be manually updated so that only a set of trusted users can view, edit and delete it (e.g., the `XWiki.XWikiAdminGroup` group).
Severity: nan (CVSS v3 base score: 8.1)
Weakness: CWE-269 \u2014 Improper Privilege Management
The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.
Repository: https://github.com/xwiki/xwiki-platform
Files changed:
xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-send/xwiki-platform-mail-send-default/pom.xml
xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-send/xwiki-platform-mail-send-default/src/main/java/org/xwiki/mail/internal/MailConfigMandatoryDocumentInitializer.java
xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-send/xwiki-platform-mail-send-default/src/test/java/org/xwiki/mail/internal/MailConfigMandatoryDocumentInitializerTest.java
xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authorization/xwiki-platform-security-authorization-bridge/pom.xml
xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authorization/xwiki-platform-security-authorization-bridge/src/main/java/org/xwiki/security/internal/DocumentInitializerRightsManager.java
xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authorization/xwiki-platform-security-authorization-bridge/src/main/resources/META-INF/components.txt
xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authorization/xwiki-platform-security-authorization-bridge/src/test/java/org/xwiki/security/internal/DocumentInitializerRightsManagerTest.java
Commit message: XWIKI-20519: Improve Mail.MailConfig initialization


Vulnerable code:
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.LocalDocumentReference;
import org.xwiki.test.junit5.mockito.InjectMockComponents;
import org.xwiki.test.junit5.mockito.MockComponent;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;
import javax.inject.Provider;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
    <xwiki.jacoco.instructionRatio>0.70</xwiki.jacoco.instructionRatio>

Fixed code:
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-security-authorization-bridge</artifactId>
      <version>${project.version}</version>
    </dependency>
import org.xwiki.security.internal.DocumentInitializerRightsManager;
    @Inject
    private DocumentInitializerRightsManager documentInitializerRightsManager;

        if (this.documentInitializerRightsManager.restrictToAdmin(document)) {
            needsUpdate = true;
        }

import javax.inject.Provider;

import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.LocalDocumentReference;
import org.xwiki.security.internal.DocumentInitializerRightsManager;
import org.xwiki.test.junit5.mockito.InjectMockComponents;
import org.xwiki.test.junit5.mockito.MockComponent;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
    @MockComponent
    private DocumentInitializerRightsManager documentInitializerRightsManager;

        verify(this.documentInitializerRightsManager).restrictToAdmin(document);
    <xwiki.jacoco.instructionRatio>0.71</xwiki.jacoco.instructionRatio>
/*
 * 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.security.internal;

import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.security.authorization.Right;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;

import static org.xwiki.security.authorization.Right.DELETE;
import static org.xwiki.security.authorization.Right.EDIT;
import static org.xwiki.security.authorization.Right.VIEW;
import static org.xwiki.security.internal.XWikiConstants.ALLOW_FIELD_NAME;
import static org.xwiki.security.internal.XWikiConstants.GROUPS_FIELD_NAME;
import static org.xwiki.security.internal.XWikiConstants.LEVELS_FIELD_NAME;
import static org.xwiki.security.internal.XWikiConstants.LOCAL_CLASS_REFERENCE;

/**
 * Service provided to {@link com.xpn.xwiki.doc.MandatoryDocumentInitializer}s to initialize documents with the correct
 * rights.
 *
 * @version $Id$
 * @since 14.4.8
 * @since 14.10.5
 * @since 15.1RC1
 */
@Component(roles = DocumentInitializerRightsManager.class)
@Singleton
public class DocumentInitializerRightsManager
{
    private static final String XWIKI_ADMIN_GROUP_DOCUMENT_REFERENCE = "XWiki.XWikiAdminGroup";

    @Inject
    private Provider<XWikiContext> xcontextProvider;

    @Inject
    private Logger logger;

    /**
     * Restrict the rights of the provided document so that it can only be viewed, edited and deleted by the
     * {@code XWiki.XWikiAdminGroup} group. Note that this restriction is only applied if no rights are already applied
     * on the current document.
     * <br>
     *
     * @param document the document to updated rights on
     * @return {@code true} if the document has been modified, {@code false} otherwise (including if the document hasn't
     *     been modified because of an error during the modification)
     */
    public boolean restrictToAdmin(XWikiDocument document)
    {
        boolean updated = false;
        // If some rights have already been set on the document, we consider that it has already been protected 
        // manually.
        if (document.getXObjects(LOCAL_CLASS_REFERENCE).isEmpty()) {
            updated = initializeRights(document, XWIKI_ADMIN_GROUP_DOCUMENT_REFERENCE, List.of(VIEW, EDIT, DELETE));
        }

        return updated;
    }

    private boolean initializeRights(XWikiDocument document, String xwikiAdminGroupDocumentReference,
        List<Right> rights)
    {
        boolean updated = false;

        try {
            XWikiContext xwikiContext = this.xcontextProvider.get();
            BaseObject object = document.newXObject(LOCAL_CLASS_REFERENCE, xwikiContext);
            XWikiContext xWikiContext = this.xcontextProvider.get();
            object.set(GROUPS_FIELD_NAME, xwikiAdminGroupDocumentReference, xWikiContext);
            object.set(LEVELS_FIELD_NAME, rights.stream().map(Right::getName).collect(Collectors.toList()),
                xWikiContext);
            object.set(ALLOW_FIELD_NAME, 1, xWikiContext);
            updated = true;
        } catch (XWikiException e) {
            this.logger.error(String.format("Error adding a [%s] object to the document [%s]", LOCAL_CLASS_REFERENCE,
                document.getDocumentReference()));
        }

        return updated;
    }
}
org.xwiki.security.internal.DocumentInitializerRightsManager
/*
 * 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.security.internal;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.test.LogLevel;
import org.xwiki.test.annotation.AllComponents;
import org.xwiki.test.junit5.LogCaptureExtension;
import org.xwiki.test.junit5.mockito.InjectMockComponents;
import org.xwiki.test.mockito.MockitoComponentManager;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.MandatoryDocumentInitializer;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.internal.mandatory.XWikiRightsDocumentInitializer;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.test.MockitoOldcore;
import com.xpn.xwiki.test.junit5.mockito.InjectMockitoOldcore;
import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;

import ch.qos.logback.classic.Level;

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.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.xwiki.security.internal.XWikiConstants.ALLOW_FIELD_NAME;
import static org.xwiki.security.internal.XWikiConstants.GROUPS_FIELD_NAME;
import static org.xwiki.security.internal.XWikiConstants.LEVELS_FIELD_NAME;
import static org.xwiki.security.internal.XWikiConstants.LOCAL_CLASS_REFERENCE;

/**
 * Test of {@link DocumentInitializerRightsManager}.
 *
 * @version $Id$
 */
@OldcoreTest
@AllComponents
class DocumentInitializerRightsManagerTest
{
    @InjectMockitoOldcore
    private MockitoOldcore oldcore;

    @InjectMockComponents
    private DocumentInitializerRightsManager rightsManager;

    @RegisterExtension
    LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.WARN);

    private XWikiDocument document;

    private XWikiContext xWikiContext;

    @BeforeEach
    void setUp(MockitoComponentManager componentManager) throws Exception
    {
        this.xWikiContext = this.oldcore.getXWikiContext();
        String wikiId = this.xWikiContext.getWikiId();
        this.document = this.oldcore.getSpyXWiki()
            .getDocument(new DocumentReference(wikiId, "Space", "Page"), this.xWikiContext);
        XWikiDocument xWikiRigths = this.oldcore.getSpyXWiki()
            .getDocument(new DocumentReference(wikiId, "XWiki", "XWikiRights"), this.xWikiContext);
        componentManager.<MandatoryDocumentInitializer>getInstance(MandatoryDocumentInitializer.class,
            XWikiRightsDocumentInitializer.CLASS_REFERENCE_STRING).updateDocument(xWikiRigths);
        this.oldcore.getSpyXWiki().saveDocument(xWikiRigths, this.xWikiContext);
    }

    @Test
    void restrictToAdminSkipWhenAlreadyHasRights() throws Exception
    {
        this.document.newXObject(LOCAL_CLASS_REFERENCE, this.xWikiContext);
        assertFalse(this.rightsManager.restrictToAdmin(this.document));
    }

    @Test
    void restrictToAdmin()
    {
        assertTrue(this.rightsManager.restrictToAdmin(this.document));
        assertEquals(1, this.document.getXObjects(LOCAL_CLASS_REFERENCE).size());
        BaseObject xObject = this.document.getXObject(LOCAL_CLASS_REFERENCE);
        assertEquals("XWiki.XWikiAdminGroup", xObject.getStringValue(GROUPS_FIELD_NAME));
        assertEquals("view, edit, delete", xObject.getStringValue(LEVELS_FIELD_NAME));
        assertEquals("1", xObject.getStringValue(ALLOW_FIELD_NAME));
    }

    @Test
    void restrictToAdminWithException() throws XWikiException
    {
        this.document = spy(this.document);
        doThrow(XWikiException.class).when(this.document)
            .newXObject(any(EntityReference.class), any(XWikiContext.class));
        assertFalse(this.rightsManager.restrictToAdmin(this.document));
        assertEquals(0, this.document.getXObjects(LOCAL_CLASS_REFERENCE).size());
        assertEquals("Error adding a [XWiki.XWikiRights] object to the document [xwiki:Space.Page]",
            this.logCapture.getMessage(0));
        assertEquals(Level.ERROR, this.logCapture.getLogEvent(0).getLevel());
    }
}