High severityNVD Advisory· Published May 28, 2021· Updated Aug 3, 2024
Script injection without script or programming rights through Gadget titles
CVE-2021-32621
Description
XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. In versions prior to 12.6.7 and 12.10.3, a user without Script or Programming right is able to execute script requiring privileges by editing gadget titles in the dashboard. The issue has been patched in XWiki 12.6.7, 12.10.3 and 13.0RC1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.commons:xwiki-commons-coreMaven | < 12.6.7 | 12.6.7 |
org.xwiki.commons:xwiki-commons-coreMaven | >= 12.10.0, < 12.10.3 | 12.10.3 |
Affected products
1- Range: < 12.6.7
Patches
1bb7068bd911fXWIKI-17794: Properly interpret velocity in gadget titles
2 files changed · +79 −21
xwiki-platform-core/xwiki-platform-dashboard/xwiki-platform-dashboard-macro/src/main/java/org/xwiki/rendering/internal/macro/dashboard/DefaultGadgetSource.java+28 −10 modified@@ -44,17 +44,16 @@ import org.xwiki.rendering.block.WordBlock; import org.xwiki.rendering.block.XDOM; import org.xwiki.rendering.executor.ContentExecutor; -import org.xwiki.rendering.executor.ContentExecutorException; import org.xwiki.rendering.listener.reference.ResourceReference; import org.xwiki.rendering.listener.reference.ResourceType; import org.xwiki.rendering.macro.dashboard.Gadget; import org.xwiki.rendering.macro.dashboard.GadgetSource; -import org.xwiki.rendering.parser.MissingParserException; -import org.xwiki.rendering.parser.ParseException; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.rendering.transformation.MacroTransformationContext; import org.xwiki.rendering.util.ParserUtils; import org.xwiki.security.authorization.AuthorExecutor; +import org.xwiki.security.authorization.AuthorizationManager; +import org.xwiki.security.authorization.Right; import org.xwiki.velocity.VelocityEngine; import org.xwiki.velocity.VelocityManager; @@ -120,6 +119,9 @@ public class DefaultGadgetSource implements GadgetSource @Inject private JobProgressManager progress; + @Inject + private AuthorizationManager authorizationManager; + /** * Prepare the parser to parse the title and content of the gadget into blocks. */ @@ -194,19 +196,23 @@ private List<Gadget> prepareGadgets(List<BaseObject> objects, Syntax sourceSynta String position = xObject.getStringValue("position"); String id = xObject.getNumber() + ""; - // render title with velocity - StringWriter writer = new StringWriter(); - // FIXME: the engine has an issue with $ and # as last character. To test and fix if it happens - velocityEngine.evaluate(velocityContext, writer, key, title); - String gadgetTitle = writer.toString(); + String gadgetTitle; + + XWikiDocument ownerDocument = xObject.getOwnerDocument(); + if (this.authorizationManager.hasAccess(Right.SCRIPT, ownerDocument.getAuthorReference(), ownerDocument.getDocumentReference())) { + gadgetTitle = + this.evaluateVelocityTitle(velocityContext, velocityEngine, key, title, ownerDocument); + } else { + gadgetTitle = title; + } // parse both the title and content in the syntax of the transformation context List<Block> titleBlocks = renderGadgetProperty(gadgetTitle, sourceSyntax, xObject.getDocumentReference(), - xObject.getOwnerDocument(), context); + ownerDocument, context); List<Block> contentBlocks = renderGadgetProperty(content, sourceSyntax, xObject.getDocumentReference(), - xObject.getOwnerDocument(), context); + ownerDocument, context); // create a gadget will all these and add the gadget to the container of gadgets Gadget gadget = new Gadget(id, titleBlocks, contentBlocks, position); @@ -222,6 +228,18 @@ private List<Gadget> prepareGadgets(List<BaseObject> objects, Syntax sourceSynta return gadgets; } + private String evaluateVelocityTitle(VelocityContext velocityContext, VelocityEngine velocityEngine, String key, + String title, XWikiDocument ownerDocument) throws Exception + { + return this.authorExecutor.call(() -> { + // render title with velocity + StringWriter writer = new StringWriter(); + // FIXME: the engine has an issue with $ and # as last character. To test and fix if it happens + velocityEngine.evaluate(velocityContext, writer, key, title); + return writer.toString(); + }, ownerDocument.getAuthorReference(), ownerDocument.getDocumentReference()); + } + private List<Block> renderGadgetProperty(String content, Syntax sourceSyntax, EntityReference sourceReference, XWikiDocument ownerDocument, MacroTransformationContext context) throws Exception
xwiki-platform-core/xwiki-platform-dashboard/xwiki-platform-dashboard-macro/src/test/java/org/xwiki/rendering/internal/macro/dashboard/DefaultGadgetSourceTest.java+51 −11 modified@@ -41,6 +41,8 @@ import org.xwiki.rendering.transformation.MacroTransformationContext; import org.xwiki.rendering.transformation.TransformationContext; import org.xwiki.security.authorization.AuthorExecutor; +import org.xwiki.security.authorization.AuthorizationManager; +import org.xwiki.security.authorization.Right; import org.xwiki.test.junit5.mockito.ComponentTest; import org.xwiki.test.junit5.mockito.InjectMockComponents; import org.xwiki.test.junit5.mockito.MockComponent; @@ -57,6 +59,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ComponentTest @@ -72,6 +75,9 @@ class DefaultGadgetSourceTest @MockComponent private AuthorExecutor authorExecutor; + @MockComponent + private AuthorizationManager authorizationManager; + @Mock private DocumentReference documentReference; @@ -99,6 +105,11 @@ class DefaultGadgetSourceTest @Mock private MacroTransformationContext macroTransformationContext; + @Mock + private VelocityEngine velocityEngine; + + private ContentExecutor<MacroTransformationContext> contentExecutor; + @BeforeEach void setup(MockitoComponentManager componentManager) throws Exception { @@ -123,23 +134,14 @@ void setup(MockitoComponentManager componentManager) throws Exception when(transformationContext.getId()).thenReturn(transformationId); VelocityManager velocityManager = componentManager.getInstance(VelocityManager.class); - VelocityEngine velocityEngine = mock(VelocityEngine.class); when(velocityManager.getVelocityEngine()).thenReturn(velocityEngine); - when(velocityEngine.evaluate(any(), any(), any(), any(String.class))).then((Answer<Void>) invocation -> { - Object[] args = invocation.getArguments(); - StringWriter stringWriter = (StringWriter) args[1]; - String title = (String) args[3]; - stringWriter.append(title); - return null; - }); - AuthorExecutor authorExecutor = componentManager.getInstance(AuthorExecutor.class); when(authorExecutor.call(any(), eq(ownerAuthorReference), eq(ownerSourceReference))).then(invocationOnMock -> { Callable callable = (Callable) invocationOnMock.getArguments()[0]; return callable.call(); }); - ContentExecutor<MacroTransformationContext> contentExecutor = + this.contentExecutor = componentManager.getInstance(ContentExecutor.TYPE_MACRO_TRANSFORMATION); when(contentExecutor.execute(any(), any(), any(), any())).then((Answer<XDOM>) invocationOnMock -> { String content = invocationOnMock.getArgument(0); @@ -162,12 +164,50 @@ void getGadgets() throws Exception when(gadgetObject1.getLargeStringValue("content")).thenReturn("Some content"); when(gadgetObject1.getStringValue("position")).thenReturn("0"); when(gadgetObject1.getNumber()).thenReturn(42); + when(this.authorizationManager.hasAccess(Right.SCRIPT, ownerAuthorReference, ownerSourceReference)).thenReturn(true); + when(this.velocityEngine.evaluate(any(), any(), any(), eq("Gadget 1"))).then((Answer<Void>) invocation -> { + Object[] args = invocation.getArguments(); + StringWriter stringWriter = (StringWriter) args[1]; + String title = "Evaluated velocity version of gadget 1"; + stringWriter.append(title); + return null; + }); List<Gadget> gadgets = this.defaultGadgetSource.getGadgets(testSource, macroTransformationContext); assertEquals(1, gadgets.size()); Gadget gadget = gadgets.get(0); - assertEquals("Gadget 1", gadget.getTitle().get(0).toString()); + assertEquals("Evaluated velocity version of gadget 1", gadget.getTitle().get(0).toString()); assertEquals("Some content", gadget.getContent().get(0).toString()); assertEquals("42", gadget.getId()); + verify(this.contentExecutor) + .execute(eq("Evaluated velocity version of gadget 1"), any(), any(), any()); + verify(this.contentExecutor) + .execute(eq("Some content"), any(), any(), any()); + } + + @Test + void getGadgetWithoutScriptRight() throws Exception + { + assertEquals(new ArrayList<>(), this.defaultGadgetSource.getGadgets(testSource, macroTransformationContext)); + + BaseObject gadgetObject1 = mock(BaseObject.class); + when(xWikiDocument.getXObjects(gadgetClassReference)).thenReturn(Collections.singletonList(gadgetObject1)); + when(gadgetObject1.getOwnerDocument()).thenReturn(ownerDocument); + when(gadgetObject1.getStringValue("title")).thenReturn("Gadget 2"); + when(gadgetObject1.getLargeStringValue("content")).thenReturn("Some other content"); + when(gadgetObject1.getStringValue("position")).thenReturn("2"); + when(gadgetObject1.getNumber()).thenReturn(12); + when(this.authorizationManager.hasAccess(Right.SCRIPT, ownerAuthorReference, ownerSourceReference)).thenReturn(false); + + List<Gadget> gadgets = this.defaultGadgetSource.getGadgets(testSource, macroTransformationContext); + assertEquals(1, gadgets.size()); + Gadget gadget = gadgets.get(0); + assertEquals("Gadget 2", gadget.getTitle().get(0).toString()); + assertEquals("Some other content", gadget.getContent().get(0).toString()); + assertEquals("12", gadget.getId()); + verify(this.contentExecutor) + .execute(eq("Gadget 2"), any(), any(), any()); + verify(this.contentExecutor) + .execute(eq("Some other content"), any(), any(), any()); } }
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
6- github.com/advisories/GHSA-h353-hc43-95vcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-32621ghsaADVISORY
- github.com/xwiki/xwiki-platform/commit/bb7068bd911f91e5511f3cfb03276c7ac81100bcghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-h353-hc43-95vcghsax_refsource_CONFIRMWEB
- jay-from-future.github.io/cve/2021/06/17/xwiki-rce-cve.htmlghsax_refsource_MISCWEB
- jira.xwiki.org/browse/XWIKI-17794ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.