CVE-2024-31987 (published 2024-04-10T21:15Z)

XWiki Platform is a generic wiki platform. Starting in version 6.4-milestone-1 and prior to versions 4.10.19, 15.5.4, and 15.10-rc-1, any user who can edit any page like their profile can create a custom skin with a template override that is executed with programming right, thus allowing remote code execution. This has been patched in XWiki 14.10.19, 15.5.4 and 15.10RC1. No known workarounds are available except for upgrading.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: pjeanjean
author_date: 2023-10-30 14:15:04+01:00
msg: XWIKI-21478: Improve rights check of template macros
repository: repo_url: https://github.com/xwiki/xwiki-platform
repo_name: xwiki/xwiki-platform
description: The XWiki platform
repo_language: Java
owner: xwiki
file_changes: filename: XWikiVelocityManager.java
change_type: ModificationType.MODIFY
diff: @@ -42,6 +42,9 @@
 import org.xwiki.observation.EventListener;
 import org.xwiki.observation.ObservationManager;
 import org.xwiki.observation.event.Event;
+import org.xwiki.security.authorization.AuthorExecutor;
+import org.xwiki.security.authorization.AuthorizationManager;
+import org.xwiki.security.authorization.Right;
 import org.xwiki.skin.Skin;
 import org.xwiki.skin.SkinManager;
 import org.xwiki.template.Template;
@@ -73,7 +76,7 @@ public class XWikiVelocityManager extends DefaultVelocityManager implements Init
     private static final String VELOCITYENGINE_CACHEKEY_NAME = "velocity.engine.key";
 
     private static final List<Event> EVENTS =
-        Arrays.<Event>asList(new TemplateUpdatedEvent(), new TemplateDeletedEvent());
+        Arrays.asList(new TemplateUpdatedEvent(), new TemplateDeletedEvent());
 
     /**
      * Used to access the current {@link XWikiContext}.
@@ -96,10 +99,16 @@ public class XWikiVelocityManager extends DefaultVelocityManager implements Init
     @Inject
     private Environment environment;
 
+    @Inject
+    private AuthorExecutor authorExecutor;
+
+    @Inject
+    private AuthorizationManager authorizationManager;
+
     @Inject
     private Logger logger;
 
-    private Map<String, VelocityEngine> velocityEngines = new ConcurrentHashMap<>();
+    private final Map<String, VelocityEngine> velocityEngines = new ConcurrentHashMap<>();
 
     @Override
     public void initialize() throws InitializationException
@@ -114,7 +123,7 @@ public void onEvent(Event event, Object source, Object data)
                 if (event instanceof TemplateEvent) {
                     TemplateEvent templateEvent = (TemplateEvent) event;
 
-                    velocityEngines.remove(templateEvent.getId());
+                    XWikiVelocityManager.this.velocityEngines.remove(templateEvent.getId());
                 }
             }
 
@@ -177,8 +186,8 @@ private Template getVelocityEngineMacrosTemplate()
 
     /**
      * @return the Velocity Engine corresponding to the current execution context. More specifically returns the
-     *         Velocity Engine for the current skin since each skin has its own Velocity Engine so that each skin can
-     *         have global velocimacros defined
+     *     Velocity Engine for the current skin since each skin has its own Velocity Engine so that each skin can have
+     *     global velocimacros defined
      * @throws XWikiVelocityException in case of an error while creating a Velocity Engine
      */
     @Override
@@ -248,10 +257,13 @@ private void injectBaseMacros(VelocityEngine velocityEngine, Template skinMacros
             }
         }
 
-        // Inject skin macros
-        if (skinMacrosTemplate != null) {
-            VelocityTemplate skinMacros = compile("", new StringReader(skinMacrosTemplate.getContent().getContent()));
-
+        // Inject skin macros if their author has at least Script rights.
+        if (skinMacrosTemplate != null
+            && this.authorizationManager.hasAccess(Right.SCRIPT, skinMacrosTemplate.getContent().getAuthorReference(),
+            skinMacrosTemplate.getContent().getDocumentReference()))
+        {
+            VelocityTemplate skinMacros =
+                compile("", new StringReader(skinMacrosTemplate.getContent().getContent()));
             velocityEngine.addGlobalMacros(skinMacros.getMacros());
         }
     }

programming_language: Java
filename: XWikiVelocityManagerTest.java
change_type: ModificationType.MODIFY
diff: @@ -22,15 +22,24 @@
 import org.apache.velocity.VelocityContext;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.xwiki.component.manager.ComponentLookupException;
+import org.mockito.Mock;
 import org.xwiki.internal.script.XWikiScriptContextInitializer;
 import org.xwiki.localization.ContextualLocalizationManager;
 import org.xwiki.logging.internal.DefaultLoggerConfiguration;
 import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.properties.ConverterManager;
 import org.xwiki.script.internal.DefaultScriptContextManager;
+import org.xwiki.security.authorization.AuthorizationManager;
+import org.xwiki.security.authorization.Right;
+import org.xwiki.skin.Skin;
+import org.xwiki.skin.SkinManager;
+import org.xwiki.template.Template;
+import org.xwiki.template.TemplateContent;
+import org.xwiki.template.TemplateManager;
 import org.xwiki.test.annotation.ComponentList;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
 import org.xwiki.test.junit5.mockito.MockComponent;
+import org.xwiki.velocity.VelocityEngine;
 import org.xwiki.velocity.internal.DefaultVelocityConfiguration;
 import org.xwiki.velocity.internal.VelocityExecutionContextInitializer;
 
@@ -44,17 +53,29 @@
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 /**
  * Validate {@link XWikiVelocityManager}.
- * 
+ *
  * @version $Id$
  */
-@ComponentList(value = {DefaultScriptContextManager.class, XWikiScriptContextInitializer.class,
-    DefaultVelocityConfiguration.class, DefaultLoggerConfiguration.class})
+@ComponentList({
+    DefaultScriptContextManager.class,
+    XWikiScriptContextInitializer.class,
+    DefaultVelocityConfiguration.class,
+    DefaultLoggerConfiguration.class
+})
 @OldcoreTest
 public class XWikiVelocityManagerTest
 {
+    private static final DocumentReference TEMPLATE_DOCUMENT = new DocumentReference("xwiki", "XWiki", "TestMacros");
+
+    private static final DocumentReference SCRIPT_USER = new DocumentReference("xwiki", "XWiki", "Script");
+
     @MockComponent
     private ContextualLocalizationManager localizationManager;
 
@@ -64,19 +85,47 @@
     @InjectMockitoOldcore
     private MockitoOldcore oldcore;
 
+    @MockComponent
+    private SkinManager skinManager;
+
+    @Mock
+    private Skin skin;
+
+    @MockComponent
+    private TemplateManager templateManager;
+
+    @Mock
+    private Template template;
+
+    @Mock
+    private TemplateContent templateContent;
+
+    @MockComponent
+    private ConverterManager converterManager;
+
     @BeforeEach
-    public void beforeEach() throws ComponentLookupException
+    void beforeEach() throws Exception
     {
         this.oldcore.getExecutionContext().setProperty(VelocityExecutionContextInitializer.VELOCITY_CONTEXT_ID,
             new VelocityContext());
+
+        when(this.skinManager.getCurrentSkin(true)).thenReturn(this.skin);
+        when(this.templateManager.getTemplate("macros.vm")).thenReturn(this.template);
+        when(this.template.getId()).thenReturn("testMacros");
+        when(this.template.getContent()).thenReturn(this.templateContent);
+        when(this.templateContent.getDocumentReference()).thenReturn(TEMPLATE_DOCUMENT);
+        when(this.templateContent.getContent()).thenReturn("");
+
+        AuthorizationManager authorizationManager = this.oldcore.getMockAuthorizationManager();
+        when(authorizationManager.hasAccess(Right.SCRIPT, SCRIPT_USER, TEMPLATE_DOCUMENT)).thenReturn(true);
     }
 
     // Tests
 
     @Test
-    public void getVelocityContext()
+    void getVelocityContext()
     {
-        VelocityContext context = velocityManager.getVelocityContext();
+        VelocityContext context = this.velocityManager.getVelocityContext();
 
         assertNull(context.get("doc"));
         assertNull(context.get("sdoc"));
@@ -112,4 +161,19 @@ public void getVelocityContext()
         assertNotNull(context.get("sdoc"));
         assertNotSame(sdoc, context.get("sdoc"));
     }
+
+    @Test
+    void checkMacrosInjectionWithoutScriptRights() throws Exception
+    {
+        VelocityEngine engine = this.velocityManager.getVelocityEngine();
+        verify(engine, never()).addGlobalMacros(anyMap());
+    }
+
+    @Test
+    void checkMacrosInjectionWithScriptRights() throws Exception
+    {
+        when(this.templateContent.getAuthorReference()).thenReturn(SCRIPT_USER);
+        VelocityEngine engine = this.velocityManager.getVelocityEngine();
+        verify(engine).addGlobalMacros(anyMap());
+    }
 }

programming_language: Java