VYPR
Moderate severityNVD Advisory· Published Aug 23, 2023· Updated Oct 2, 2024

SXSS in the user profile via the timezone displayer

CVE-2023-40176

Description

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. Any registered user can exploit a stored XSS through their user profile by setting the payload as the value of the time zone user preference. Even though the time zone is selected from a drop down (no free text value) it can still be set from JavaScript (using the browser developer tools) or by calling the save URL on the user profile with the right query string. Once the time zone is set it is displayed without escaping which means the payload gets executed for any user that visits the malicious user profile, allowing the attacker to steal information and even gain more access rights (escalation to programming rights). This issue is present since version 4.1M2 when the time zone user preference was introduced. The issue has been fixed in XWiki 14.10.5 and 15.1RC1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.xwiki.platform:xwiki-platform-web-templatesMaven
>= 4.1-milestone-2, < 14.10.514.10.5

Affected products

1

Patches

1
d11ca5d781f8

XWIKI-20276: Improve escaping in the timezone displayer

https://github.com/xwiki/xwiki-platformMarius Dumitru FloreaFeb 8, 2023via ghsa
17 files changed · +373 100
  • xwiki-platform-core/xwiki-platform-administration/xwiki-platform-administration-ui/src/main/resources/XWiki/TemplateProviderMacros.xml+1 3 modified
    @@ -47,9 +47,7 @@
       #else
         #set ($output = $doc.display($property))
       #end
    -  #set ($output = $stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'))
    -  #set ($output = $stringtool.removeEnd($output, '{{/html}}'))
    -  $output
    +  #unwrapXPropertyDisplay($output)
     #end
     
     #macro (displayPathsPropertyValue $property)
    
  • xwiki-platform-core/xwiki-platform-annotation/xwiki-platform-annotation-ui/src/main/resources/AnnotationCode/Macros.xml+1 3 modified
    @@ -364,9 +364,7 @@
         ## the annotated document (i.e. isolated), not the current document.
         #set ($output = $annotatedDocument.display($annotationProperty.name, 'view', $annotationObject))
         ## Remove the HTML macro wrapping because the output is later injected in another HTML macro with wiki=false.
    -    #set ($output = $stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'))
    -    #set ($output = $stringtool.removeEnd($output, '{{/html}}'))
    -    $output
    +    #unwrapXPropertyDisplay($output)
       #end
     #end
     {{/velocity}}</content>
    
  • xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/VelocityMacros.xml+1 4 modified
    @@ -241,10 +241,7 @@
     #end
     
     #macro (displayCKEditorConfigPropertyValue $configDoc $propName $action)
    -  #set ($output = $configDoc.display($propName, $action))
    -  #set ($output = $stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'))
    -  #set ($output = $stringtool.removeEnd($output, '{{/html}}'))
    -  $output
    +  #unwrapXPropertyDisplay($configDoc.display($propName, $action))
     #end
     
     #macro (displayCKEditorConfig $configDoc $action)
    
  • xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-ui/src/main/resources/XWiki/PDFExport/ConfigurationSheet.xml+1 4 modified
    @@ -78,10 +78,7 @@
     #end
     
     #macro (displayPDFExportConfigPropertyValue $configProperty $displayMode)
    -  #set ($output = $doc.display($configProperty.name, $displayMode))
    -  #set ($output = $stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'))
    -  #set ($output = $stringtool.removeEnd($output, '{{/html}}'))
    -  $!output
    +  #unwrapXPropertyDisplay($doc.display($configProperty.name, $displayMode))
     #end
     
     #macro (viewPDFExportConfig)
    
  • xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-ui/src/main/resources/XWiki/PDFExport/Sheet.xml+1 4 modified
    @@ -105,10 +105,7 @@
     #end
     
     #macro (renderPDFElement $pdfTemplateObj $element)
    -  #set ($output = $tdoc.display($element, $pdfTemplateObj))
    -  #set ($output = $stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'))
    -  #set ($output = $stringtool.removeEnd($output, '{{/html}}'))
    -  $!output
    +  #unwrapXPropertyDisplay($tdoc.display($element, $pdfTemplateObj))
     #end
     
     #macro (renderPDFContent)
    
  • xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml+2 2 modified
    @@ -502,7 +502,7 @@
         #else
           #set($fieldValue = "$!fieldProperty.getValue()")
         #end
    -    #set($fieldDisplayValue = "$!itemDoc.display($colname, 'view')")
    +    #set($fieldDisplayValue = "#unwrapXPropertyDisplay($itemDoc.display($colname, 'view'))")
         #if($fieldDisplayValue == '')
           #set($fieldDisplayValue = $services.localization.render("${request.transprefix}emptyvalue"))
         #end
    @@ -514,7 +514,7 @@
             #set($fieldUrl = '')
           #end
         #end
    -    #set($discard = $row.put($colname, $fieldDisplayValue.replaceFirst($regextool.quote('{{html clean="false" wiki="false"}}'), '').replaceAll("$regextool.quote('{{/html}}')$", '')))
    +    #set($discard = $row.put($colname, $fieldDisplayValue))
         #set($discard = $row.put("${colname}_value", $fieldValue))
         #set($discard = $row.put("${colname}_url", $fieldUrl))
         ## Reset to the default class
    
  • xwiki-platform-core/xwiki-platform-panels/xwiki-platform-panels-ui/src/main/resources/Panels/PanelWizard.xml+1 4 modified
    @@ -79,10 +79,7 @@
     
     {{velocity output="false"}}
     #macro (displayConfigPropertyValue $propName $action)
    -  #set ($output = $doc.display($propName, $action))
    -  #set ($output = $stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'))
    -  #set ($output = $stringtool.removeEnd($output, '{{/html}}'))
    -  $output
    +  #unwrapXPropertyDisplay($doc.display($propName, $action))
     #end
     
     #macro (displayPageLayoutSection)
    
  • xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestConfigSheet.xml+1 4 modified
    @@ -40,10 +40,7 @@
     
     {{velocity output="false"}}
     #macro (displayObjectPropertyValue $propertyName $mode)
    -  #set ($output = $doc.display($propertyName, $mode))
    -  #set ($output = $stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'))
    -  #set ($output = $stringtool.removeEnd($output, '{{/html}}'))
    -  $output
    +  #unwrapXPropertyDisplay($doc.display($propertyName, $mode))
     #end
     
     #macro (displaySearchSuggestSource $source)
    
  • xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/pom.xml+21 0 modified
    @@ -111,4 +111,25 @@
           <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>**/UIExtensionScriptServiceComponentList.class</include>
    +              </includes>
    +            </configuration>
    +          </execution>
    +        </executions>
    +      </plugin>
    +    </plugins>
    +  </build>
     </project>
    
  • xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/script/UIExtensionScriptServiceComponentList.java+81 0 added
    @@ -0,0 +1,81 @@
    +/*
    + * 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.uiextension.script;
    +
    +import java.lang.annotation.Documented;
    +import java.lang.annotation.Inherited;
    +import java.lang.annotation.Retention;
    +import java.lang.annotation.Target;
    +
    +import org.xwiki.component.wiki.internal.DefaultWikiComponentManager;
    +import org.xwiki.component.wiki.internal.DefaultWikiComponentManagerContext;
    +import org.xwiki.component.wiki.internal.WikiComponentManagerEventListenerHelper;
    +import org.xwiki.component.wiki.internal.bridge.DefaultContentParser;
    +import org.xwiki.component.wiki.internal.bridge.DefaultWikiObjectComponentManagerEventListener;
    +import org.xwiki.component.wiki.internal.bridge.WikiObjectComponentManagerEventListenerProxy;
    +import org.xwiki.rendering.async.internal.block.DefaultBlockAsyncRenderer;
    +import org.xwiki.test.annotation.ComponentList;
    +import org.xwiki.uiextension.internal.DefaultUIExtensionManager;
    +import org.xwiki.uiextension.internal.WikiUIExtensionComponentBuilder;
    +import org.xwiki.uiextension.internal.filter.ExcludeFilter;
    +import org.xwiki.uiextension.internal.filter.SelectFilter;
    +import org.xwiki.uiextension.internal.filter.SortByCustomOrderFilter;
    +import org.xwiki.uiextension.internal.filter.SortByIdFilter;
    +import org.xwiki.uiextension.internal.filter.SortByParameterFilter;
    +
    +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;
    +
    +/**
    + * Pack of default component implementations that are needed for {@link UIExtensionScriptService}.
    + *
    + * @version $Id$
    + * @since 14.10.5
    + * @since 15.1RC1
    + */
    +@Documented
    +@Retention(RUNTIME)
    +@Target({TYPE, METHOD, ANNOTATION_TYPE})
    +@ComponentList({
    +    UIExtensionScriptService.class,
    +    DefaultUIExtensionManager.class,
    +    WikiUIExtensionComponentBuilder.class,
    +    // Filters
    +    ExcludeFilter.class,
    +    SelectFilter.class,
    +    SortByCustomOrderFilter.class,
    +    SortByIdFilter.class,
    +    SortByParameterFilter.class,
    +    // Needed for registering the UI extensions defined in wiki pages.
    +    DefaultWikiObjectComponentManagerEventListener.class,
    +    WikiObjectComponentManagerEventListenerProxy.class,
    +    WikiComponentManagerEventListenerHelper.class,
    +    DefaultWikiComponentManager.class,
    +    DefaultWikiComponentManagerContext.class,
    +    // Needed for rendering the UI extensions.
    +    DefaultContentParser.class,
    +    DefaultBlockAsyncRenderer.class
    +})
    +@Inherited
    +public @interface UIExtensionScriptServiceComponentList
    +{
    +}
    
  • xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-directory/xwiki-platform-user-directory-ui/src/main/resources/XWiki/UserDirectoryMacros.xml+1 2 modified
    @@ -276,8 +276,7 @@
     #macro (displayProperty $propertyName $object $action)
       #set ($propertyClass = $object.xWikiClass.get($propertyName))
       #set ($isCheckbox = $propertyClass.getValue('displayFormType') == 'checkbox')
    -  #set ($fieldDisplay = $stringtool.removeEnd($stringtool.removeStart($object.display($propertyName, $action),
    -    '{{html clean="false" wiki="false"}}'), '{{/html}}'))
    +  #set ($fieldDisplay = "#unwrapXPropertyDisplay($object.display($propertyName, $action))")
       &lt;dt&gt;
         &lt;label #if ($action == 'edit' &amp;&amp; !$isCheckbox)
             for="$escapetool.xml("${object.xWikiClass.name}_${object.number}_$propertyName")"#end&gt;
    
  • xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-profile/xwiki-platform-user-profile-ui/pom.xml+37 0 modified
    @@ -118,5 +118,42 @@
           <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>
    +    <dependency>
    +      <groupId>org.xwiki.platform</groupId>
    +      <artifactId>xwiki-platform-web-templates</artifactId>
    +      <version>${project.version}</version>
    +      <scope>test</scope>
    +    </dependency>
    +    <!-- Provides the component list for UIExtensionScriptService. -->
    +    <dependency>
    +      <groupId>org.xwiki.platform</groupId>
    +      <artifactId>xwiki-platform-uiextension-api</artifactId>
    +      <version>${project.version}</version>
    +      <classifier>tests</classifier>
    +      <type>test-jar</type>
    +      <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>
    +    <dependency>
    +      <groupId>org.xwiki.platform</groupId>
    +      <artifactId>xwiki-platform-rendering-configuration-default</artifactId>
    +      <version>${project.version}</version>
    +      <type>test-jar</type>
    +      <scope>test</scope>
    +    </dependency>
       </dependencies>
     </project>
    
  • xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-profile/xwiki-platform-user-profile-ui/src/main/resources/XWiki/XWikiUserPreferencesSheet.xml+57 57 modified
    @@ -20,7 +20,7 @@
      * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
     -->
     
    -<xwikidoc version="1.3" reference="XWiki.XWikiUserPreferencesSheet" locale="">
    +<xwikidoc version="1.5" reference="XWiki.XWikiUserPreferencesSheet" locale="">
       <web>XWiki</web>
       <name>XWikiUserPreferencesSheet</name>
       <language/>
    @@ -73,11 +73,11 @@
             <number>4</number>
             <prettyName>Context elements</prettyName>
             <relationalStorage>0</relationalStorage>
    -        <separator> </separator>
    +        <separator>, </separator>
             <separators>|, </separators>
             <size>5</size>
             <unmodifiable>0</unmodifiable>
    -        <values>doc.reference=Document|icon.theme=Icon theme|locale=Language|rendering.defaultsyntax=Default syntax|rendering.restricted=Restricted|rendering.targetsyntax=Target syntax|request.base=Request base URL|request.parameters=Request parameters|request.url=Request URL|request.wiki=Request wiki|user=User|wiki=Wiki</values>
    +        <values>action=Action|doc.reference=Document|icon.theme=Icon theme|locale=Language|rendering.defaultsyntax=Default syntax|rendering.restricted=Restricted|rendering.targetsyntax=Target syntax|request.base=Request base URL|request.cookies|request.headers|request.parameters=Request parameters|request.remoteAddr|request.url=Request URL|request.wiki=Request wiki|user=User|wiki=Wiki</values>
             <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
           </async_context>
           <async_enabled>
    @@ -97,6 +97,7 @@
             <name>content</name>
             <number>1</number>
             <prettyName>Executed Content</prettyName>
    +        <restricted>0</restricted>
             <rows>25</rows>
             <size>120</size>
             <unmodifiable>0</unmodifiable>
    @@ -127,6 +128,7 @@
             <name>parameters</name>
             <number>7</number>
             <prettyName>Extension Parameters</prettyName>
    +        <restricted>0</restricted>
             <rows>10</rows>
             <size>40</size>
             <unmodifiable>0</unmodifiable>
    @@ -173,72 +175,68 @@
     ###############################
     ##        CONTROLLER
     ###############################
    -#if(!$obj)
    -  {{info}}$services.localization.render('xe.admin.users.applyonusers'){{/info}}##
    +#if (!$obj)
    +  {{info}}{{translation key="xe.admin.users.applyonusers" /}}{{/info}}
     #else
       #displayFields()
     #end
     ###############################
     ##  DISPLAY FIELD OPTION LABEL
     ###############################
    -#macro(displayFieldOptionLabel $option)
    -  #if ($services.localization.render($option.label) != $option.label)
    -    $services.localization.render($option.label)
    +#macro (displayFieldOptionLabel $option)
    +  #if ($services.localization.get($option.label))
    +    $escapetool.xml($services.localization.render($option.label))
       #else
    -    $option.value
    +    $escapetool.xml($option.value)
       #end
     #end
     ###############################
     ##   DISPLAY SELECT FIELD
     ###############################
    -#macro(displaySelectField $fieldName $default)
    +#macro (displaySelectField $fieldName $defaultValue)
       #set ($value = $doc.getValue($fieldName, $obj))
       #set ($prop = $xclass.get($fieldName))
       #set ($options = [])
       #if ($prop.classType == 'Boolean')
         #set ($discard = $options.add({'value': 1, 'label': 'yes'}))
         #set ($discard = $options.add({'value': 0, 'label': 'no'}))
       #elseif ($prop.classType == 'Timezone')
    -    #foreach($tz in $xwiki.jodatime.serverTimezone.availableIDs)
    -      #set ($discard = $options.add({'value': $tz, 'label': $tz}))
    -    #end
    -    ## If the default value is empty (nothing has been set by the administrator), the fallback is the timezone of the 
    -    ## server. But we won't display the "System Default" option, which is technical (it supposes the user knows there 
    -    ## is a server with its own timezone, etc...)
    -    #if ("$!default" == '')
    -      #set ($default = $xwiki.jodatime.serverTimezone)
    +    #foreach ($timeZoneId in $xwiki.jodatime.serverTimezone.availableIDs)
    +      #set ($discard = $options.add({'value': $timeZoneId, 'label': $timeZoneId}))
         #end
       #else
    -    #foreach($v in $prop.listValues)
    +    #foreach ($v in $prop.listValues)
           #set ($discard = $options.add({'value': $v, 'label': "XWiki.XWikiUsers_${fieldName}_${v}"}))
         #end
       #end
    +  #set ($defaultValueHint = "($escapetool.xml($services.localization.render('userprofile.default')))")
       #if ($isEdit)
    -    &lt;select name="XWiki.XWikiUsers_0_${fieldName}" id="XWiki.XWikiUsers_0_${fieldName}" size="1"&gt;
    -      #if ($default)
    +    #set ($escapedId = $escapetool.xml("XWiki.XWikiUsers_0_${fieldName}"))
    +    &lt;select name="$escapedId" id="$escapedId" size="1"&gt;
    +      #if ($defaultValue)
             &lt;option value=""&gt;---&lt;/option&gt;
           #end
           #foreach ($option in $options)
             ## Some properties actually have "---" as a possible value, so we don't display it because we have
             ## already displayed it above.
             #if ($option.value != '---')
    -          &lt;option value="$option.value" #if($value == $option.value)selected="selected"#end&gt;
    +          &lt;option value="$escapetool.xml($option.value)"#if ($value == $option.value) selected#end&gt;
                 #displayFieldOptionLabel($option)
    -            #if ($default == $option.value)
    -              ($services.localization.render('userprofile.default'))
    +            #if ($defaultValue == $option.value)
    +              $defaultValueHint
                 #end
               &lt;/option&gt;
             #end
           #end
         &lt;/select&gt;
       #else
         #if ("$!value" != '')
    -      $doc.display($fieldName)
    +      #unwrapXPropertyDisplay($doc.display($fieldName))
         #else
           #foreach ($option in $options)
    -        #if ("$!default" == "$!option.value")
    +        #if ("$!defaultValue" == "$!option.value")
               #displayFieldOptionLabel($option)
    -          &lt;small&gt;($services.localization.render('userprofile.default'))&lt;/small&gt;
    +          &lt;small&gt;$defaultValueHint&lt;/small&gt;
             #end
           #end
         #end
    @@ -247,57 +245,56 @@
     ###############################
     ##      DISPLAY FIELD
     ###############################
    -#macro(displayField $fieldName $label $default)
    +#macro (displayField $fieldName $label $defaultValue)
       #set ($hintKey = "XWiki.XWikiUsers_${fieldName}.hint")
       &lt;dt&gt;
    -    &lt;label&gt;$services.localization.render($label)&lt;/label&gt;
    +    &lt;label&gt;$!escapetool.xml($services.localization.render($label))&lt;/label&gt;
         #if ($services.localization.get($hintKey))
    -      &lt;span class="xHint"&gt;$services.localization.render($hintKey)&lt;/span&gt;
    +      &lt;span class="xHint"&gt;$!escapetool.xml($services.localization.render($hintKey))&lt;/span&gt;
         #end
       &lt;/dt&gt;
    -  &lt;dd&gt;
    +  &lt;dd data-user-property="$!escapetool.xml($fieldName)"&gt;
         #if ($fieldName == 'timezone' &amp;&amp; !$xwiki.jodatime)
    -      #if ($isEdit)
    -        &lt;input id="XWiki.XWikiUsers_0_${fieldName}" name="XWiki.XWikiUsers_0_${fieldName}" type="text" value="$!escapetool.xml($obj.getValue($fieldName))"/&gt;
    -      #else
    -        #if ("$!obj.getValue('timezone')" == '' &amp;&amp; "$!xwiki.getXWikiPreference('timezone')" != '')
    -          $xwiki.getXWikiPreference('timezone') &lt;small&gt;($services.localization.render('userprofile.default'))&lt;/small&gt;
    -        #else
    -          $doc.display('timezone')
    -        #end
    -      #end
    +      #unwrapXPropertyDisplay($doc.display('timezone'))
         #else
    -      #displaySelectField($fieldName, $default)
    +      #displaySelectField($fieldName, $defaultValue)
         #end
       &lt;/dd&gt;
     #end
     ###############################
     ##      DISPLAY FIELDS
     ###############################
    -#macro(displayFields)
    -{{html}}
    -  &lt;div class="#if($xcontext.action == 'view')half #{else}full #{end}column xform"&gt;
    +#macro (displayFields)
    +{{html clean="false"}}
    +  &lt;div class="#if ($xcontext.action == 'view')half #{else}full #{end}column xform"&gt;
         &lt;div class="userPreferences"&gt;
    -      #if($xcontext.action == 'view' &amp;&amp; $hasEdit)
    +      #if ($xcontext.action == 'view' &amp;&amp; $hasEdit)
             &lt;div class="editProfileCategory"&gt;
    -          &lt;a title="$escapetool.xml($services.localization.render('platform.core.profile.category.preferences.edit'))"
    +          &lt;a title="$!escapetool.xml($services.localization.render('platform.core.profile.category.preferences.edit'))"
                   href="$doc.getURL('edit', 'category=preferences')" class="btn btn-xs"&gt;
                 &lt;span class="action-icon"&gt;$services.icon.renderHTML('pencil')&lt;/span&gt;
    -            &lt;span class='sr-only'&gt;$escapetool.xml($services.localization.render('platform.core.profile.category.preferences.edit'))&lt;/span&gt;
    +            &lt;span class='sr-only'&gt;$!escapetool.xml($services.localization.render(
    +              'platform.core.profile.category.preferences.edit'))&lt;/span&gt;
               &lt;/a&gt;
             &lt;/div&gt;
           #end
    -      &lt;h1&gt;$services.localization.render('platform.core.profile.section.displayPreferences')&lt;/h1&gt;
    +      &lt;h1&gt;$escapetool.xml($services.localization.render('platform.core.profile.section.displayPreferences'))&lt;/h1&gt;
           &lt;dl&gt;
             #displayField('displayHiddenDocuments', 'platform.core.profile.displayHiddenDocuments', '0')
             #displayField('accessibility'         , 'platform.core.profile.enableAccessibility'   , '0')
           &lt;/dl&gt;
    -      &lt;h1&gt;$services.localization.render('platform.core.profile.section.localizationPreferences')&lt;/h1&gt;
    +      &lt;h1&gt;$escapetool.xml($services.localization.render('platform.core.profile.section.localizationPreferences'))&lt;/h1&gt;
           &lt;dl&gt;
             #set ($defaultValue = $xwiki.getXWikiPreference('timezone'))
    +        ## If the default value is empty (nothing has been set by the administrator), the fallback is the timezone of
    +        ## the server. But we won't display the "System Default" option, which is technical (it supposes the user knows
    +        ## there is a server with its own timezone, etc.).
    +        #if ("$!defaultValue" == '')
    +          #set ($defaultValue = $xwiki.jodatime.serverTimezone)
    +        #end
             #displayField('timezone'              , 'platform.core.profile.timezone'              , $defaultValue)
           &lt;/dl&gt;
    -      &lt;h1&gt;$services.localization.render('platform.core.profile.section.editorPreferences')&lt;/h1&gt;
    +      &lt;h1&gt;$escapetool.xml($services.localization.render('platform.core.profile.section.editorPreferences'))&lt;/h1&gt;
           &lt;dl&gt;
             #set ($defaultValue = $xwiki.getXWikiPreference('editor'))
             #if ("$!defaultValue" == '')
    @@ -306,7 +303,7 @@
             #displayField('editor'                , 'platform.core.profile.editor'                , $defaultValue)
             #displayField('usertype'              , 'platform.core.profile.userType'              , 'Simple')
           &lt;/dl&gt;
    -      &lt;h1&gt;$services.localization.render('platform.core.profile.section.extensionPreferences')&lt;/h1&gt;
    +      &lt;h1&gt;$escapetool.xml($services.localization.render('platform.core.profile.section.extensionPreferences'))&lt;/h1&gt;
           &lt;dl&gt;
             #set ($defaultValue = $xwiki.getXWikiPreference('extensionConflictSetup'))
             #if ("$!defaultValue" == '')
    @@ -316,17 +313,20 @@
           &lt;/dl&gt;
         &lt;/div&gt;
       &lt;/div&gt;
    -  #set($isMyProfile = ($xwiki.getDocument($xcontext.user).prefixedFullName == $doc.prefixedFullName))
    -  #if(($isMyProfile || $hasAdmin) &amp;&amp; ($xcontext.action == 'view') &amp;&amp; !$doc.getObject('XWiki.LDAPProfileClass'))
    +  #set ($isMyProfile = $xwiki.getDocument($xcontext.user).prefixedFullName == $doc.prefixedFullName)
    +  #if (($isMyProfile || $hasAdmin) &amp;&amp; $xcontext.action == 'view' &amp;&amp; !$doc.getObject('XWiki.LDAPProfileClass'))
         &lt;div class="half column"&gt;
           &lt;div class="passwordManagement"&gt;
    -        &lt;h1&gt;$services.localization.render('platform.core.profile.section.security')&lt;/h1&gt;
    -        &lt;span class="buttonwrapper"&gt;&lt;a id="changePassword" href="$doc.getURL('view', 'xpage=passwd')"&gt;$services.localization.render('platform.core.profile.changePassword')&lt;/a&gt;&lt;/span&gt;
    +        &lt;h1&gt;$escapetool.xml($services.localization.render('platform.core.profile.section.security'))&lt;/h1&gt;
    +        &lt;span class="buttonwrapper"&gt;
    +          &lt;a id="changePassword" href="$doc.getURL('view', 'xpage=passwd')"&gt;$escapetool.xml(
    +            $services.localization.render('platform.core.profile.changePassword'))&lt;/a&gt;
    +        &lt;/span&gt;
           &lt;/div&gt;
         &lt;/div&gt;
       #end
       &lt;div class="clearfloats"&gt;&amp;nbsp;&lt;/div&gt;
    -{{/html}}##
    +{{/html}}
     #end
     {{/velocity}}</content>
         </property>
    @@ -340,7 +340,7 @@
           <parameters>id=preferences
     icon=wrench
     # isActive: The user is seeing her own profile or the user is an admin.
    -isActive=#if(($services.model.resolveDocument($xcontext.user) == $doc.documentReference) || $hasAdmin)true#{else}false#end
    +isActive=#if ($xcontext.userReference == $doc.documentReference || $hasAdmin)true#{else}false#end
     priority=20</parameters>
         </property>
         <property>
    
  • xwiki-platform-core/xwiki-platform-user/xwiki-platform-user-profile/xwiki-platform-user-profile-ui/src/test/java/org/xwiki/user/profile/UserProfilePageTest.java+134 0 added
    @@ -0,0 +1,134 @@
    +/*
    + * 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.user.profile;
    +
    +import java.util.HashMap;
    +import java.util.Map;
    +
    +import org.jsoup.Jsoup;
    +import org.jsoup.nodes.Document;
    +import org.jsoup.nodes.Element;
    +import org.junit.jupiter.api.BeforeEach;
    +import org.junit.jupiter.api.Test;
    +import org.xwiki.bridge.event.DocumentCreatedEvent;
    +import org.xwiki.component.manager.ComponentManager;
    +import org.xwiki.component.wiki.internal.bridge.DefaultWikiObjectComponentManagerEventListener;
    +import org.xwiki.model.reference.DocumentReference;
    +import org.xwiki.observation.EventListener;
    +import org.xwiki.rendering.RenderingScriptServiceComponentList;
    +import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList;
    +import org.xwiki.security.authorization.AuthorizationManager;
    +import org.xwiki.test.annotation.ComponentList;
    +import org.xwiki.test.page.HTML50ComponentList;
    +import org.xwiki.test.page.PageTest;
    +import org.xwiki.test.page.XWikiSyntax20ComponentList;
    +import org.xwiki.uiextension.script.UIExtensionScriptServiceComponentList;
    +
    +import com.xpn.xwiki.doc.XWikiDocument;
    +import com.xpn.xwiki.internal.mandatory.XWikiUsersDocumentInitializer;
    +
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +import static org.mockito.ArgumentMatchers.any;
    +import static org.mockito.ArgumentMatchers.eq;
    +import static org.mockito.Mockito.when;
    +
    +/**
    + * Page test of {@code XWiki.XWikiUserSheet} that is used to display the user profile.
    + * 
    + * @version $Id$
    + * @since 14.10.5
    + * @since 15.1RC1
    + */
    +@HTML50ComponentList
    +@XWikiSyntax20ComponentList
    +@RenderingScriptServiceComponentList
    +@DefaultRenderingConfigurationComponentList
    +@UIExtensionScriptServiceComponentList
    +@ComponentList({XWikiUsersDocumentInitializer.class})
    +class UserProfilePageTest extends PageTest
    +{
    +    private static final DocumentReference USER_SHEET = new DocumentReference("xwiki", "XWiki", "XWikiUserSheet");
    +
    +    private static final DocumentReference USER_PREFERENCES_SHEET =
    +        new DocumentReference("xwiki", "XWiki", "XWikiUserPreferencesSheet");
    +
    +    private static final DocumentReference ADMIN_REFERENCE = new DocumentReference("xwiki", "XWiki", "Admin");
    +
    +    private static final DocumentReference ALICE_REFERENCE = new DocumentReference("xwiki", "XWiki", "alice");
    +
    +    @BeforeEach
    +    void setUp() throws Exception
    +    {
    +        // Initialize the XWiki.XWikiUsers class (required in order to be able to display user properties).
    +        this.xwiki.initializeMandatoryDocuments(this.context);
    +
    +        AuthorizationManager authorizationManager =
    +            this.componentManager.registerMockComponent(AuthorizationManager.class);
    +        when(authorizationManager.hasAccess(any(), eq(ADMIN_REFERENCE), any())).thenReturn(true);
    +
    +        // Register the user profile UI extensions at wiki level (requires administration rights).
    +        this.componentManager.registerComponent(ComponentManager.class, "wiki", this.componentManager);
    +        this.context.setUserReference(ADMIN_REFERENCE);
    +        registerUIX(USER_PREFERENCES_SHEET);
    +    }
    +
    +    @Test
    +    void timeZoneDisplay() throws Exception
    +    {
    +        Map<String, Object> userProperties = new HashMap<>();
    +        userProperties.put("timezone", "Europe/<em>Bucharest</em>");
    +        createUser(userProperties);
    +
    +        Document dom = renderSheet(USER_SHEET);
    +        Element timeZone = dom.selectFirst(".userPreferences dd[data-user-property='timezone']");
    +        assertEquals(userProperties.get("timezone"), timeZone.text());
    +    }
    +
    +    private void registerUIX(DocumentReference documentReference) throws Exception
    +    {
    +        XWikiDocument document = loadPage(documentReference);
    +
    +        // The event listeners are not registered by default. We trigger it manually so that the UIX is registered and
    +        // can be found and rendered.
    +        this.componentManager
    +            .<EventListener>getInstance(EventListener.class,
    +                DefaultWikiObjectComponentManagerEventListener.EVENT_LISTENER_NAME)
    +            .onEvent(new DocumentCreatedEvent(), document, null);
    +    }
    +
    +    private XWikiDocument createUser(Map<String, Object> userProperties) throws Exception
    +    {
    +        this.xwiki.createUser(ALICE_REFERENCE.getName(), userProperties, this.context);
    +        XWikiDocument userDocument = this.xwiki.getDocument(ALICE_REFERENCE, this.context);
    +
    +        // Setup the script context as if we are logged in with the created user on its user profile.
    +        this.context.setDoc(userDocument);
    +        this.context.setUserReference(ALICE_REFERENCE);
    +
    +        return userDocument;
    +    }
    +
    +    private Document renderSheet(DocumentReference sheetReference) throws Exception
    +    {
    +        XWikiDocument sheet = loadPage(sheetReference);
    +
    +        return Jsoup.parse(sheet.getRenderedContent(this.context));
    +    }
    +}
    
  • xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/displayer_timezone.vm+17 11 modified
    @@ -17,22 +17,28 @@
     ## Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
     ## 02110-1301 USA, or see the FSF site: http://www.fsf.org.
     ## ---------------------------------------------------------------------------
    -#if ($type == 'edit' || $type == 'search')
    -  #if($xwiki.jodatime)
    -    <select id='$prefix$name' name='$prefix$name'>
    -      <option value="" #if($value == $tz)selected="selected"#end>$services.localization.render('XWiki.XWikiPreferences_timezone_default')</option>
    -      #foreach($tz in $xwiki.jodatime.getServerTimezone().getAvailableIDs())
    -        <option value="$tz" #if($value == $tz)selected="selected"#end>$tz</option>
    +#if ($type == 'edit' || $type == 'search' || $type == 'hidden')
    +  #set ($escapedId = $escapetool.xml("${prefix}${name}"))
    +  #if ($xwiki.jodatime && $type != 'hidden')
    +    <select id="$!escapedId" name="$!escapedId">
    +      <option value="">$!escapetool.xml($services.localization.render(
    +        'XWiki.XWikiPreferences_timezone_default'))</option>
    +      #foreach ($timeZoneId in $xwiki.jodatime.getServerTimezone().getAvailableIDs())
    +        <option value="$!escapetool.xml($timeZoneId)"#if ($value == $timeZoneId) selected#end
    +          >$!escapetool.xml($timeZoneId)</option>
           #end
         </select>
       #else
    -    <input id='$prefix$name' name='$prefix$name' type="text" value="$!value"/>
    +    #set ($inputType = "#if ($type == 'hidden')hidden#{else}text#end")
    +    <input id="$!escapedId" name="$!escapedId" type="$!escapetool.xml($inputType)" value="$!escapetool.xml($value)" />
       #end
     #elseif ($type == 'view' || $type == 'rendered')
    -$!value
    -#elseif ($type == 'hidden')
    -  #set ($id = $escapetool.xml("${prefix}${name}"))
    -  <input type="hidden" id="$!id" name="$!id" value="$!escapetool.xml($value)" />
    +## $defaultValue may be set before calling the displayer.
    +#if ("$!value" == '' && "$!defaultValue" != '')
    +$!escapetool.xml($defaultValue) <small>($escapetool.xml($services.localization.render('userprofile.default')))</small>
    +#else
    +$!escapetool.xml($value)
    +#end
     #else
       ## In order for the custom displayer to be taken into account, the result of its evaluation with an unknown display
       ## mode must not be empty. Let's output something.
    
  • xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/macros.vm+14 0 modified
    @@ -3017,4 +3017,18 @@ Recursive title display detected!##
     #else
     #setVariable("$resultValue" '')
     #end
    +#end
    +
    +#**
    + * The output of the api.Document#display(...) methods is wrapped in an HTML (wiki syntax) rendering macro if it contains
    + * HTML special characters or if it tries to close the outer HTML macro. This can be annoying when the output is going to
    + * be insered in another HTML macro with wiki syntax disabled, or if the output is meant to be included in a JSON and
    + * thus used on the client-side where we can't render the wiki syntax. For these use cases we need to get rid of the HTML
    + * macro wrapping.
    + *
    + * @since 14.10.5
    + * @since 15.1RC1
    + *#
    +#macro (unwrapXPropertyDisplay $displayOutput)
    +$!stringtool.removeEnd($stringtool.removeStart($displayOutput, '{{html clean="false" wiki="false"}}'), '{{/html}}')##
     #end
    \ No newline at end of file
    
  • xwiki-platform-core/xwiki-platform-wiki/xwiki-platform-wiki-ui/xwiki-platform-wiki-ui-mainwiki/src/main/resources/WikiManager/WikisLiveTableResultsMacros.xml+2 2 modified
    @@ -415,7 +415,7 @@
           #set($propType = "$!propClass.get($colname).type")
         #end
         #set($fieldValue = "$!itemDoc.getValue($colname)")
    -    #set($fieldDisplayValue = "$!itemDoc.display($colname, 'view')")
    +    #set($fieldDisplayValue = "#unwrapXPropertyDisplay($itemDoc.display($colname, 'view'))")
         #if($fieldDisplayValue == '')
           #set($fieldDisplayValue = $services.localization.render("${request.transprefix}emptyvalue"))
         #end
    @@ -427,7 +427,7 @@
             #set($fieldUrl = '')
           #end
         #end
    -    #set($discard = $row.put($colname, $fieldDisplayValue.replaceFirst($regextool.quote('{{html clean="false" wiki="false"}}'), '').replaceAll("$regextool.quote('{{/html}}')$", '')))
    +    #set($discard = $row.put($colname, $fieldDisplayValue))
         #set($discard = $row.put("${colname}_value", $fieldValue))
         ######## WIKI UI MAINWIKI CUSTOMIZATION #########
         #if($colname == 'wikiprettyname')
    

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.