VYPR
High severityNVD Advisory· Published Jun 23, 2023· Updated Nov 27, 2024

XWiki Platform may show email addresses in clear in REST results

CVE-2023-35151

Description

XWiki Platform is a generic wiki platform. Starting in version 7.3-milestone-1 and prior to versions 14.4.8, 14.10.6, and 15.1, ny user can call a REST endpoint and obtain the obfuscated passwords, even when the mail obfuscation is activated. The issue has been patched in XWiki 14.4.8, 14.10.6, and 15.1. There is no known workaround.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.xwiki.platform:xwiki-platform-rest-serverMaven
>= 7.3-milestone-1, < 14.4.814.4.8
org.xwiki.platform:xwiki-platform-rest-serverMaven
>= 14.5, < 14.10.614.10.6
org.xwiki.platform:xwiki-platform-rest-serverMaven
>= 15.0-rc-1, < 15.115.1

Affected products

1

Patches

1
824cd742ecf5

XWIKI-16138: Improved REST properties cleanup

https://github.com/xwiki/xwiki-platformManuel LeducFeb 21, 2023via ghsa
3 files changed · +169 10
  • xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/pom.xml+5 0 modified
    @@ -180,6 +180,11 @@
           <artifactId>xwiki-platform-icon-api</artifactId>
           <version>${project.version}</version>
         </dependency>
    +    <dependency>
    +      <groupId>org.xwiki.platform</groupId>
    +      <artifactId>xwiki-platform-mail-api</artifactId>
    +      <version>${project.version}</version>
    +    </dependency>
     
         <!-- Test dependencies -->
         <dependency>
    
  • xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/ModelFactory.java+52 4 modified
    @@ -28,13 +28,16 @@
     import java.util.HashSet;
     import java.util.List;
     import java.util.Locale;
    +import java.util.Objects;
     import java.util.Set;
     import java.util.Vector;
     import java.util.stream.Collectors;
     
     import javax.inject.Inject;
     import javax.inject.Provider;
     import javax.inject.Singleton;
    +import javax.mail.internet.AddressException;
    +import javax.mail.internet.InternetAddress;
     import javax.xml.parsers.ParserConfigurationException;
     
     import org.apache.commons.lang3.StringUtils;
    @@ -46,6 +49,8 @@
     import org.xwiki.logging.LogLevel;
     import org.xwiki.logging.event.LogEvent;
     import org.xwiki.logging.tail.LogTail;
    +import org.xwiki.mail.EmailAddressObfuscator;
    +import org.xwiki.mail.GeneralMailConfiguration;
     import org.xwiki.model.EntityType;
     import org.xwiki.model.reference.DocumentReference;
     import org.xwiki.model.reference.EntityReference;
    @@ -129,6 +134,8 @@
     import com.xpn.xwiki.objects.classes.ComputedFieldClass;
     import com.xpn.xwiki.objects.classes.ListClass;
     
    +import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;
    +
     /**
      * Various common tools for resources.
      * 
    @@ -167,6 +174,12 @@ public class ModelFactory
         @Inject
         private UserReferenceSerializer<String> userReferenceSerializer;
     
    +    @Inject
    +    private GeneralMailConfiguration generalMailConfiguration;
    +
    +    @Inject
    +    private EmailAddressObfuscator emailAddressObfuscator;
    +
         public ModelFactory()
         {
             this.objectFactory = new ObjectFactory();
    @@ -262,7 +275,16 @@ private void fillObjectSummary(ObjectSummary objectSummary, Document doc, BaseOb
             String[] propertyNames = xwikiObject.getPropertyNames();
             if (propertyNames.length > 0) {
                 try {
    -                objectSummary.setHeadline(serializePropertyValue(xwikiObject.get(propertyNames[0])));
    +                String firstPropertyName = propertyNames[0];
    +                BaseClass baseClass = xwikiObject.getXClass(this.xcontextProvider.get());
    +                PropertyInterface field = baseClass.getField(firstPropertyName);
    +                // The property might not exist in the class. But if it does, it will be a PropertyClass. 
    +                if (field != null) {
    +                    String classType = ((com.xpn.xwiki.objects.classes.PropertyClass) field).getClassType();
    +                    objectSummary.setHeadline(cleanupBeforeMakingPublic(classType, xwikiObject.get(firstPropertyName)));
    +                } else {
    +                    objectSummary.setHeadline(serializePropertyValue(xwikiObject.get(firstPropertyName)));
    +                }
                 } catch (XWikiException e) {
                     // Should never happen
                 }
    @@ -567,7 +589,7 @@ private Locale getDefaultLocale(Document document)
                     return xcontext.getWiki().getDocument(document.getDocumentReference(), xcontext).getRealLocale();
                 } catch (XWikiException e) {
                     this.logger.warn("Failed to get the default locale from [{}]. Root cause is [{}].",
    -                    document.getDocumentReference(), ExceptionUtils.getRootCauseMessage(e));
    +                    document.getDocumentReference(), getRootCauseMessage(e));
                     // Fall-back on the default locale specified on the translation page, which may not be accurate.
                     return document.getDefaultLocale();
                 }
    @@ -992,7 +1014,7 @@ public Hierarchy toRestHierarchy(EntityReference targetEntityReference, Boolean
                     } catch (Exception e) {
                         this.logger.warn(
                             "Failed to get the pretty name of entity [{}]. Continue using the entity name. Root cause is [{}].",
    -                        entityReference, ExceptionUtils.getRootCauseMessage(e));
    +                        entityReference, getRootCauseMessage(e));
                     }
                 }
                 hierarchy.withItems(hierarchyItem);
    @@ -1051,7 +1073,7 @@ private String serializePropertyValue(PropertyInterface property,
                     context.setDoc(document);
                 }
             } else {
    -            return serializePropertyValue(property);
    +            return cleanupBeforeMakingPublic(propertyClass.getClassType(), property);
             }
         }
     
    @@ -1246,4 +1268,30 @@ private boolean hasAccess(Property restProperty)
     
             return true;
         }
    +
    +    private String cleanupBeforeMakingPublic(String type, PropertyInterface baseProperty)
    +    {
    +        String cleanedUpStringValue;
    +        if (Objects.equals(type, PASSWORD_TYPE)) {
    +            cleanedUpStringValue = null;
    +        } else {
    +            cleanedUpStringValue = serializePropertyValue(baseProperty);
    +            // We obfuscate the email only if the obfuscation has been activated, and if the current user does not have 
    +            // the right to edit the document containing the base property.
    +            // A user allowed to edit a document has to view the unescaped email to be able to edit it correctly. 
    +            if (Objects.equals(type, "Email") && this.generalMailConfiguration.shouldObfuscate()
    +                && !this.authorizationManagerProvider.get().hasAccess(Right.EDIT, baseProperty.getReference()))
    +            {
    +                try {
    +                    cleanedUpStringValue =
    +                        this.emailAddressObfuscator.obfuscate(InternetAddress.parse(cleanedUpStringValue)[0]);
    +                } catch (AddressException e) {
    +                    this.logger.warn("Failed to parse [{}] to an email address. Cause: [{}]", cleanedUpStringValue,
    +                        getRootCauseMessage(e));
    +                    cleanedUpStringValue = "";
    +                }
    +            }
    +        }
    +        return cleanedUpStringValue;
    +    }
     }
    
  • xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/ModelFactoryTest.java+112 6 modified
    @@ -29,16 +29,24 @@
     import java.util.Map;
     import java.util.Set;
     import java.util.Vector;
    +import java.util.stream.Stream;
    +
    +import javax.mail.internet.InternetAddress;
     
     import org.junit.jupiter.api.BeforeEach;
     import org.junit.jupiter.api.Test;
     import org.junit.jupiter.api.extension.RegisterExtension;
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.Arguments;
    +import org.junit.jupiter.params.provider.MethodSource;
     import org.mockito.Mock;
    -import org.xwiki.component.manager.ComponentManager;
    +import org.xwiki.mail.EmailAddressObfuscator;
    +import org.xwiki.mail.GeneralMailConfiguration;
     import org.xwiki.model.reference.AttachmentReference;
     import org.xwiki.model.reference.DocumentReference;
     import org.xwiki.model.reference.EntityReference;
     import org.xwiki.model.reference.EntityReferenceSerializer;
    +import org.xwiki.model.reference.ObjectReference;
     import org.xwiki.model.reference.SpaceReference;
     import org.xwiki.model.reference.SpaceReferenceResolver;
     import org.xwiki.model.reference.WikiReference;
    @@ -54,7 +62,6 @@
     import org.xwiki.security.authorization.Right;
     import org.xwiki.test.LogLevel;
     import org.xwiki.test.junit5.LogCaptureExtension;
    -import org.xwiki.test.junit5.mockito.InjectComponentManager;
     import org.xwiki.test.junit5.mockito.InjectMockComponents;
     import org.xwiki.test.junit5.mockito.MockComponent;
     import org.xwiki.wiki.descriptor.WikiDescriptor;
    @@ -65,17 +72,22 @@
     import com.xpn.xwiki.XWikiException;
     import com.xpn.xwiki.api.Document;
     import com.xpn.xwiki.doc.XWikiDocument;
    +import com.xpn.xwiki.objects.BaseCollection;
     import com.xpn.xwiki.objects.BaseObject;
     import com.xpn.xwiki.objects.StringProperty;
     import com.xpn.xwiki.objects.classes.BaseClass;
    +import com.xpn.xwiki.objects.classes.EmailClass;
     import com.xpn.xwiki.objects.classes.PasswordClass;
     import com.xpn.xwiki.objects.classes.StringClass;
     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.assertTrue;
    +import static org.mockito.ArgumentMatchers.any;
     import static org.mockito.Mockito.mock;
     import static org.mockito.Mockito.when;
     
    @@ -93,9 +105,6 @@ class ModelFactoryTest
         @RegisterExtension
         private LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.WARN);
     
    -    @InjectComponentManager
    -    private ComponentManager componentManager;
    -
         @MockComponent
         private ContextualAuthorizationManager authorizationManager;
     
    @@ -108,6 +117,12 @@ class ModelFactoryTest
         @MockComponent
         private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
     
    +    @MockComponent
    +    private GeneralMailConfiguration generalMailConfiguration;
    +
    +    @MockComponent
    +    private EmailAddressObfuscator emailAddressObfuscator;
    +
         @InjectMockComponents
         private ModelFactory modelFactory;
     
    @@ -206,10 +221,101 @@ void toRestObjectCheckWhichObjectValuesAreAvailableForAdmins() throws Exception
     
             Map<String, String> expectedValues = new HashMap<>();
             expectedValues.put(TEST_STRING_FIELD, TEST_STRING_VALUE);
    -        expectedValues.put(TEST_PASSWORD_FIELD, TEST_PASSWORD_VALUE);
    +        expectedValues.put(TEST_PASSWORD_FIELD, null);
             assertExpectedPropertyValues(result.getProperties(), expectedValues);
         }
     
    +    public static Stream<Arguments> toRestObjectWithObfuscatedMailSource()
    +    {
    +        return Stream.of(
    +            // no mail obfuscation
    +            Arguments.of(false, true, "user@domain.tld", "user@domain.tld", null),
    +            // mail obfuscation activated but the current user has edit rights
    +            Arguments.of(true, true, "user@domain.tld", "user@domain.tld", null),
    +            // mail obfuscation activated and the current user does not have edit rights 
    +            Arguments.of(true, false, "user@domain.tld", "u...@domain.tld", null),
    +            // mail obfuscation activated, the current user does not have edit rights and the mail is badly formatted
    +            Arguments.of(true, false, "wrong@", "",
    +                "Failed to parse [wrong@] to an email address. Cause: [AddressException: Missing domain]")
    +        );
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("toRestObjectWithObfuscatedMailSource")
    +    void toRestObjectWithObfuscatedMail(boolean shouldObfuscate, boolean hasEditRight, String inputMail,
    +        String expectedEmail, String expectedWarning) throws Exception
    +    {
    +        ObjectReference objectReference = mock(ObjectReference.class);
    +        when(this.generalMailConfiguration.shouldObfuscate()).thenReturn(shouldObfuscate);
    +        when(this.authorizationManager.hasAccess(Right.EDIT, objectReference)).thenReturn(hasEditRight);
    +        when(this.emailAddressObfuscator.obfuscate(any(InternetAddress.class))).thenReturn(expectedEmail);
    +
    +        BaseObject xwikiObject = mock(BaseObject.class);
    +        BaseClass xwikiClass = mock(BaseClass.class);
    +
    +        when(xwikiObject.getPropertyNames()).thenReturn(new String[] {});
    +        when(xwikiObject.getXClass(this.xcontext)).thenReturn(xwikiClass);
    +        when(xwikiObject.getClassName()).thenReturn("Some.XClass");
    +        when(xwikiObject.getNumber()).thenReturn(0);
    +
    +        EmailClass emailField = new EmailClass();
    +        emailField.setName("emailValue");
    +        StringProperty emailElement = new StringProperty();
    +        emailElement.setName("emailValue");
    +        emailElement.setClassType("Password");
    +        emailElement.setValue(inputMail);
    +        BaseCollection baseCollection = mock(BaseCollection.class);
    +        when(baseCollection.getReference()).thenReturn(objectReference);
    +        emailElement.setObject(baseCollection);
    +        when(xwikiObject.get("emailValue")).thenReturn(emailElement);
    +
    +        when(xwikiClass.getProperties()).thenReturn(new java.lang.Object[] { emailField });
    +
    +        Object result = this.modelFactory.toRestObject(this.baseURI, this.testDocument, xwikiObject, false, false);
    +
    +        assertExpectedPropertyValues(result.getProperties(), Map.of("emailValue", expectedEmail));
    +
    +        if (expectedWarning != null) {
    +            assertEquals(expectedWarning, this.logCapture.getMessage(0));
    +            assertEquals(Level.WARN, this.logCapture.getLogEvent(0).getLevel());
    +        }
    +    }
    +
    +    @Test
    +    void toRestObjectWithObfuscatedMailInHeader() throws Exception
    +    {
    +        ObjectReference objectReference = mock(ObjectReference.class);
    +        when(this.generalMailConfiguration.shouldObfuscate()).thenReturn(true);
    +        when(this.authorizationManager.hasAccess(Right.EDIT, objectReference)).thenReturn(false);
    +        when(this.emailAddressObfuscator.obfuscate(any(InternetAddress.class))).thenReturn("u...@domain.tld");
    +
    +        BaseObject xwikiObject = mock(BaseObject.class);
    +        BaseClass xwikiClass = mock(BaseClass.class);
    +
    +        when(xwikiObject.getPropertyNames()).thenReturn(new String[] {});
    +        when(xwikiObject.getXClass(this.xcontext)).thenReturn(xwikiClass);
    +        when(xwikiObject.getClassName()).thenReturn("Some.XClass");
    +        when(xwikiObject.getNumber()).thenReturn(0);
    +
    +        EmailClass emailField = new EmailClass();
    +        emailField.setName("emailValue");
    +        StringProperty emailElement = new StringProperty();
    +        emailElement.setName("emailValue");
    +        emailElement.setClassType("Password");
    +        emailElement.setValue("user@domain.tld");
    +        BaseCollection baseCollection = mock(BaseCollection.class);
    +        when(baseCollection.getReference()).thenReturn(objectReference);
    +        emailElement.setObject(baseCollection);
    +        when(xwikiObject.get("emailValue")).thenReturn(emailElement);
    +
    +        when(xwikiClass.getProperties()).thenReturn(new java.lang.Object[] { emailField });
    +        when(xwikiObject.getPropertyNames()).thenReturn(new String[] { "emailValue" });
    +
    +        Object result = this.modelFactory.toRestObject(this.baseURI, this.testDocument, xwikiObject, false, false);
    +
    +        assertExpectedPropertyValues(result.getProperties(), Map.of("emailValue", "u...@domain.tld"));
    +    }
    +
         private void assertExpectedPropertyValues(List<Property> properties, Map<String, String> expectedValues)
         {
             Set<String> propertiesFound = new HashSet<>();
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.