VYPR
Critical severityNVD Advisory· Published Jun 29, 2023· Updated Nov 26, 2024

Upgrading doesn't prevent exploiting vulnerable XWiki documents

CVE-2023-36468

Description

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. When an XWiki installation is upgraded and that upgrade contains a fix for a bug in a document, just a new version of that document is added. In some cases, it's still possible to exploit the vulnerability that was fixed in the new version. The severity of this depends on the fixed vulnerability, for the purpose of this advisory take CVE-2022-36100/GHSA-2g5c-228j-p52x as example - it is easily exploitable with just view rights and critical. When XWiki is upgraded from a version before the fix for it (e.g., 14.3) to a version including the fix (e.g., 14.4), the vulnerability can still be reproduced by adding rev=1.1 to the URL used in the reproduction steps so remote code execution is possible even after upgrading. Therefore, this affects the confidentiality, integrity and availability of the whole XWiki installation. This vulnerability also affects manually added script macros that contained security vulnerabilities that were later fixed by changing the script macro without deleting the versions with the security vulnerability from the history. This vulnerability doesn't affect freshly installed versions of XWiki. Further, this vulnerability doesn't affect content that is only loaded from the current version of a document like the code of wiki macros or UI extensions. This vulnerability has been patched in XWiki 14.10.7 and 15.2RC1 by forcing old revisions to be executed in a restricted mode that disables all script macros. As a workaround, admins can manually delete old revisions of affected documents. A script could be used to identify all installed documents and delete the history for them. However, also manually added and later corrected code may be affected by this vulnerability so it is easy to miss documents.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.xwiki.platform:xwiki-platform-oldcoreMaven
>= 2.0, < 14.10.714.10.7
org.xwiki.platform:xwiki-platform-oldcoreMaven
>= 15.0-rc-1, < 15.2-rc-115.2-rc-1

Affected products

1

Patches

1
15a6f845d820

XWIKI-20594: Mark old revisions and deleted documents as restricted

https://github.com/xwiki/xwiki-platformMichael HamannMar 10, 2023via ghsa
21 files changed · +333 12
  • xwiki-platform-core/xwiki-platform-bridge/src/main/java/org/xwiki/bridge/DocumentModelBridge.java+11 0 modified
    @@ -179,4 +179,15 @@ default Date getDate()
         {
             return null;
         }
    +
    +    /**
    +     * @return {@code true} if the document is restricted, i.e., transformations should be executed in restricted mode
    +     * @since 15.2RC1
    +     * @since 14.10.7
    +     */
    +    @Unstable
    +    default boolean isRestricted()
    +    {
    +        return false;
    +    }
     }
    
  • xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/AbstractDocumentTitleDisplayer.java+5 3 modified
    @@ -173,9 +173,11 @@ private XDOM displayTitle(DocumentModelBridge document, DocumentDisplayerParamet
             if (!StringUtils.isEmpty(rawTitle)) {
                 try {
                     String title = rawTitle;
    -                // Evaluate the title only if the document has script rights, otherwise use the raw title.
    -                if (authorizationManager.hasAccess(Right.SCRIPT, document.getContentAuthorReference(),
    -                    document.getDocumentReference())) {
    +                // Evaluate the title only if the document is not restricted and its content's author has script
    +                // right, otherwise use the raw title.
    +                if (!document.isRestricted() && this.authorizationManager.hasAccess(Right.SCRIPT,
    +                    document.getContentAuthorReference(), document.getDocumentReference()))
    +                {
                         title = evaluateTitle(rawTitle, document, parameters);
                     }
                     return parseTitle(title);
    
  • xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/DocumentContentAsyncRenderer.java+5 0 modified
    @@ -77,6 +77,11 @@ public Set<String> initialize(DocumentModelBridge document, DocumentDisplayerPar
         {
             this.parameters = parameters;
     
    +        // Make sure the restricted property of the document is properly taken into account.
    +        if (document.isRestricted()) {
    +            parameters.setTransformationContextRestricted(true);
    +        }
    +
             this.asyncProperties = this.asyncParser.getAsyncProperties(document);
     
             String transformationId = this.defaultEntityReferenceSerializer
    
  • xwiki-platform-core/xwiki-platform-display/xwiki-platform-display-api/src/main/java/org/xwiki/display/internal/DocumentTitleDisplayer.java+1 1 modified
    @@ -77,7 +77,7 @@ protected XDOM extractTitleFromContent(DocumentModelBridge document, DocumentDis
                     try {
                         TransformationContext txContext =
                             new TransformationContext(headingXDOM, document.getSyntax(),
    -                                                  parameters.isTransformationContextRestricted());
    +                            parameters.isTransformationContextRestricted() || document.isRestricted());
                         txContext.setTargetSyntax(parameters.getTargetSyntax());
                         transformationManager.performTransformations(headingXDOM, txContext);
     
    
  • xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/contentheader.vm+7 0 modified
    @@ -51,4 +51,11 @@
         </div>
       #end
     </div>
    +## Display a warning regarding restricted mode for advanced users or when there is an actual error to not to confuse
    +## simple users. Also, only display the warning for the content viewer as other viewers shouldn't display
    +## rendered content of old revisions and this would thus only confuse users.
    +#if ($viewer == 'content' && $doc.isRestricted()
    +  && ($isAdvancedUser || $stringtool.contains($renderedContent, 'xwikirenderingerror')))
    +  #warning($escapetool.xml($services.localization.render('core.document.restrictedInfo')))
    +#end
     <hr/>
    
  • xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/RecycleBinIT.java+32 0 modified
    @@ -39,6 +39,8 @@
     import org.xwiki.test.ui.po.HistoryPane;
     import org.xwiki.test.ui.po.ViewPage;
     
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.startsWith;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.junit.jupiter.api.Assertions.assertFalse;
     import static org.junit.jupiter.api.Assertions.assertTrue;
    @@ -284,4 +286,34 @@ void viewDeletedPage(TestUtils testUtils, TestReference parentReference)
             assertTrue(deletedPagesEntries.get(0).canBeRestored());
             assertTrue(deletedTerminalPagesEntries.get(0).canBeRestored());
         }
    +
    +    @Test
    +    @Order(3)
    +    void deletedDocumentIsRestricted(TestUtils testUtils, TestReference testReference)
    +    {
    +        testUtils.loginAsSuperAdmin();
    +
    +        ViewPage viewPage = testUtils.createPage(testReference, "{{velocity}}Velocity content{{/velocity}}", "Title");
    +        // Make sure the Velocity macro is executed normally.
    +        assertEquals("Velocity content", viewPage.getContent());
    +
    +        viewPage.deletePage().confirmDeletePage().waitUntilFinished();
    +
    +        testUtils.gotoPage(testReference);
    +        DeletePageOutcomePage recycleBinPage = new DeletePageOutcomePage();
    +
    +        List<DeletedPageEntry> deletedPageEntries = recycleBinPage.getDeletedPagesEntries();
    +        assertFalse(deletedPageEntries.isEmpty());
    +        viewPage = deletedPageEntries.get(0).clickView();
    +
    +        assertEquals("Title", viewPage.getDocumentTitle());
    +        // In the preview the Velocity macro should be forbidden.
    +        assertThat(viewPage.getContent(), startsWith("Failed to execute the [velocity] macro."));
    +
    +        testUtils.gotoPage(testReference);
    +        recycleBinPage = new DeletePageOutcomePage();
    +        viewPage = recycleBinPage.getDeletedPagesEntries().get(0).clickRestore();
    +        // Assert that in the restored document, scripts are allowed again.
    +        assertEquals("Velocity content", viewPage.getContent());
    +    }
     }
    
  • xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/VersionIT.java+48 0 modified
    @@ -33,6 +33,8 @@
     import org.xwiki.test.ui.po.ViewPage;
     import org.xwiki.test.ui.po.editor.WikiEditPage;
     
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.startsWith;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.junit.jupiter.api.Assertions.assertTrue;
     
    @@ -238,4 +240,50 @@ void rollbackAttachmentWithSameNameAndVersion(TestUtils utils, TestReference tes
             attachmentsPane.getAttachmentLink("file.txt").click();
             assertEquals("1", utils.getDriver().findElement(By.tagName("html")).getText());
         }
    +
    +    @Test
    +    @Order(5)
    +    void oldRevisionsAreRestricted(TestUtils utils, TestReference testReference) throws Exception
    +    {
    +        utils.loginAsSuperAdmin();
    +
    +        utils.rest().delete(testReference);
    +
    +        // Create first version of the page
    +        ViewPage vp = utils.createPage(testReference, "{{velocity}}" + CONTENT1 + "{{/velocity}}", TITLE);
    +        assertEquals(CONTENT1, vp.getContent());
    +
    +        // Adds second version
    +        WikiEditPage wikiEditPage = vp.editWiki();
    +        wikiEditPage.setContent("{{velocity}}" + CONTENT2 + "{{velocity}}");
    +        vp = wikiEditPage.clickSaveAndView();
    +
    +        assertEquals(CONTENT2, vp.getContent());
    +
    +        // TODO: Remove when XWIKI-6688 (Possible race condition when clicking on a tab at the bottom of a page in
    +        // view mode) is fixed.
    +        vp.waitForDocExtraPaneActive("comments");
    +
    +        HistoryPane historyTab = vp.openHistoryDocExtraPane();
    +        vp = historyTab.viewVersion("1.1");
    +
    +        // In the preview the Velocity macro should be forbidden.
    +        assertThat(vp.getContent(), startsWith("Failed to execute the [velocity] macro."));
    +
    +        // TODO: Remove when XWIKI-6688 (Possible race condition when clicking on a tab at the bottom of a page in
    +        // view mode) is fixed.
    +        vp.waitForDocExtraPaneActive("comments");
    +
    +        historyTab = vp.openHistoryDocExtraPane();
    +        vp = historyTab.rollbackToVersion("1.1");
    +
    +        // Rollback doesn't wait...
    +        // Wait for the comment tab to be selected since we're currently on the history tab and rolling
    +        // back is going to load a new page and make the focus active on the comments tab.
    +        vp.waitForDocExtraPaneActive("comments");
    +
    +        // Assert that scripts are executed again after restoring the version.
    +        assertEquals(CONTENT1, vp.getContent());
    +    }
    +
     }
    
  • xwiki-platform-core/xwiki-platform-legacy/xwiki-platform-legacy-oldcore/src/main/aspect/com/xpn/xwiki/doc/XWikiDocumentCompatibilityAspect.aj+2 1 modified
    @@ -282,7 +282,8 @@ privileged public aspect XWikiDocumentCompatibilityAspect
                             XDOM headerXDOM = new XDOM(Collections.<Block> singletonList(header));
     
                             // transform
    -                        TransformationContext context = new TransformationContext(headerXDOM, getSyntax());
    +                        TransformationContext context =
    +                            new TransformationContext(headerXDOM, getSyntax(), isRestricted());
                             Utils.getComponent(TransformationManager.class).performTransformations(headerXDOM, context);
     
                             // render
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Document.java+11 0 modified
    @@ -3200,6 +3200,17 @@ public boolean isMostRecent()
             return this.doc.isMostRecent();
         }
     
    +    /**
    +     * @return if rendering transformations shall be executed in restricted mode and the title not be executed
    +     * @since 14.10.7
    +     * @since 15.2RC1
    +     */
    +    @Unstable
    +    public boolean isRestricted()
    +    {
    +        return this.doc.isRestricted();
    +    }
    +
         @Override
         public String toString()
         {
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java+44 0 modified
    @@ -624,6 +624,11 @@ public Set<Entry<DocumentReference, List<BaseObject>>> entrySet()
          */
         private XWikiDocument originalDocument;
     
    +    /**
    +     * If the document should always be rendered in restricted mode.
    +     */
    +    private boolean restricted;
    +
         /**
          * Used to display the title and the content of this document. Do not inject the component here to avoid any simple
          * new XWikiDocument to cause many useless initialization, in particular, during initialization of the stub context
    @@ -1337,6 +1342,7 @@ private String display(Syntax targetSyntax, boolean executionContextIsolated, bo
                     DocumentDisplayerParameters parameters = new DocumentDisplayerParameters();
                     parameters.setExecutionContextIsolated(executionContextIsolated);
                     parameters.setTransformationContextIsolated(transformationContextIsolated);
    +                // Don't consider isRestricted() here as this could invoke a sheet.
                     parameters.setTransformationContextRestricted(transformationContextRestricted);
                     // Render the translated content (matching the current language) using this document's syntax.
                     parameters.setContentTranslated(tdoc != this);
    @@ -1664,6 +1670,7 @@ public String getRenderedContent(String text, Syntax sourceSyntaxId, Syntax targ
                 XWikiDocument fakeDocument = new XWikiDocument(getDocumentReference());
                 fakeDocument.setSyntax(sourceSyntaxId);
                 fakeDocument.setContent(text);
    +            fakeDocument.setRestricted(sDocument != null && sDocument.isRestricted());
     
                 // We don't let displayer take care of the context isolation because we don't want the fake document to be
                 // context document
    @@ -4583,6 +4590,7 @@ private XWikiDocument cloneInternal(DocumentReference newDocumentReference,
                 doc.setMinorEdit(isMinorEdit());
                 doc.setSyntax(getSyntax());
                 doc.setHidden(isHidden());
    +            doc.setRestricted(isRestricted());
     
                 if (this.xClass != null) {
                     doc.setXClass(this.xClass.clone());
    @@ -4744,6 +4752,14 @@ public void loadAttachments(XWikiContext context) throws XWikiException
             }
         }
     
    +    /**
    +     * Indicates whether some other document is "equal to" this one.
    +     * <p>
    +     * This ignores the {@link #isRestricted()} property as it is not considered to be part of the data.
    +     *
    +     * @param object the document to compare to
    +     * @return {@code true} if this document is the same as the object argument; {@code false} otherwise
    +     */
         @Override
         public boolean equals(Object object)
         {
    @@ -9516,4 +9532,32 @@ public void initialize()
             // There is no syntax by default in a new document and the default one is retrieved from the configuration
             setSyntax(getSyntax());
         }
    +
    +    /**
    +     * @return if rendering transformations shall be executed in restricted mode and the title not be executed
    +     * @since 14.10.7
    +     * @since 15.2RC1
    +     */
    +    @Override
    +    @Unstable
    +    public boolean isRestricted()
    +    {
    +        return this.restricted;
    +    }
    +
    +    /**
    +     * Set the restricted property that disables scripts and other dangerous content.
    +     * <p>
    +     * This property is not stored in the database as it is only supposed to be {@code true} on documents that do not
    +     * correspond to the current version of the document.
    +     *
    +     * @param restricted if rendering transformations shall be executed in restricted mode and the title not be executed
    +     * @since 14.10.7
    +     * @since 15.2RC1
    +     */
    +    @Unstable
    +    public void setRestricted(boolean restricted)
    +    {
    +        this.restricted = restricted;
    +    }
     }
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/filter/output/XWikiDocumentOutputFilterStream.java+4 0 modified
    @@ -233,6 +233,10 @@ private void begin(FilterEventParameters parameters) throws FilterException
                 this.entity.setLocale(this.currentLocale);
             }
     
    +        // Mark the document as restricted to avoid that any scripts are executed as scripts should only be executed
    +        // on the current, saved version, see https://jira.xwiki.org/browse/XWIKI-20594
    +        this.entity.setRestricted(true);
    +
             // Find default author
             DocumentReference defaultAuthorDocumentReference;
             // TODO: move to UserReference based APIs in DocumentInstanceOutputProperties
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java+3 0 modified
    @@ -714,6 +714,9 @@ public void saveXWikiDoc(XWikiDocument doc, XWikiContext inputxcontext, boolean
     
                         doc.setNew(false);
     
    +                    // Make sure that properly saved documents aren't restricted.
    +                    doc.setRestricted(false);
    +
                         // We need to ensure that the saved document becomes the original document
                         doc.setOriginalDocument(doc.clone());
                     } finally {
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/ApplicationResources.properties+4 0 modified
    @@ -740,6 +740,8 @@ core.footer.creation=Created by {0} on {1}
     core.footer.translationCreation=Translated into {0} by {1} on {2}
     core.footer.modification=Last modified by {0} on {1}
     core.document.modificationWithVersion=Version {0} by {1} on {2}
    +core.document.restrictedInfo=For security reasons, the document is displayed in restricted mode as it is not the \
    +  current version. There may be differences and errors due to this.
     
     core.document.error.failedParse=Failed to parse document content
     
    @@ -1685,6 +1687,8 @@ web.history.changes.privateInformation=Private information
     web.history.changes.attachment.notAvailable=The content diff is not available. One attachment might have been deleted from the recycle bin.
     web.history.changes.showContext=Show context
     web.history.changes.hideContext=Hide context
    +web.history.changes.restrictedInfo=For security reasons, at least one of the compared documents is displayed in \
    +  restricted mode as it is not the current version. There may be differences and errors due to this.
     
     core.viewers.diff.title=Changes for page <a href="{1}">{0}</a>
     core.viewers.diff.from=From version {0}
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/doc/XWikiDocumentMockitoTest.java+3 0 modified
    @@ -1583,6 +1583,9 @@ void tofromXMLDocument() throws XWikiException
             newDocument.fromXML(document, false);
     
             assertEquals(this.document, newDocument);
    +        // Assert that the document restored from XML is restricted in contrast to the original document.
    +        assertFalse(this.document.isRestricted());
    +        assertTrue(newDocument.isRestricted());
         }
     
         @Test
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/doc/XWikiDocumentRenderingTest.java+76 1 modified
    @@ -21,6 +21,7 @@
     
     import java.io.Reader;
     import java.util.Arrays;
    +import java.util.List;
     import java.util.Locale;
     import java.util.Properties;
     
    @@ -31,6 +32,8 @@
     import org.xwiki.display.internal.DisplayConfiguration;
     import org.xwiki.model.reference.DocumentReference;
     import org.xwiki.rendering.syntax.Syntax;
    +import org.xwiki.rendering.transformation.RenderingContext;
    +import org.xwiki.security.authorization.Right;
     import org.xwiki.test.annotation.AllComponents;
     import org.xwiki.test.junit5.mockito.InjectComponentManager;
     import org.xwiki.test.mockito.MockitoComponentManager;
    @@ -47,6 +50,8 @@
     import com.xpn.xwiki.test.junit5.mockito.InjectMockitoOldcore;
     import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;
     
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.startsWith;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.mockito.ArgumentMatchers.any;
     import static org.mockito.ArgumentMatchers.eq;
    @@ -134,7 +139,14 @@ public void setup() throws Exception
             this.oldcore.getXWikiContext().put("isInRenderingEngine", true);
     
             when(this.oldcore.getMockAuthorizationManager().hasAccess(any(), any(), any())).thenReturn(true);
    -        when(this.oldcore.getMockContextualAuthorizationManager().hasAccess(any())).thenReturn(true);
    +        when(this.oldcore.getMockContextualAuthorizationManager().hasAccess(any())).thenAnswer(invocationOnMock -> {
    +            if (List.of(Right.SCRIPT, Right.PROGRAM).contains(invocationOnMock.getArgument(0))) {
    +                RenderingContext renderingContext = this.oldcore.getMocker().getInstance(RenderingContext.class);
    +                return !renderingContext.isRestricted();
    +            } else {
    +                return true;
    +            }
    +        });
             when(this.xwiki.getRightService().hasProgrammingRights(any())).thenReturn(true);
     
             this.componentManager
    @@ -174,6 +186,16 @@ public void getRenderedTitleWhenTitleIsSet()
             assertEquals("title", this.document.getRenderedTitle(Syntax.XHTML_1_0, this.oldcore.getXWikiContext()));
         }
     
    +    @Test
    +    void getRenderedTitleRestricted()
    +    {
    +        this.document.setRestricted(true);
    +        // Title with velocity that shouldn't be evaluated
    +        String title = "#set($key = \"title\")$key";
    +        this.document.setTitle(title);
    +        assertEquals(title, this.document.getRenderedTitle(Syntax.XHTML_1_0, this.oldcore.getXWikiContext()));
    +    }
    +
         @Test
         public void getRenderedTitleInHTMLWhenExtractedFromContent()
         {
    @@ -208,6 +230,19 @@ public void getRenderedTitleInHTMLWhenExtractedFromContent()
             assertEquals("Page", this.document.getRenderedTitle(Syntax.XHTML_1_0, this.oldcore.getXWikiContext()));
         }
     
    +    @Test
    +    void getRenderedTitleWhenRestricted()
    +    {
    +        // Configure XWiki to extract title from content
    +        this.oldcore.getConfigurationSource().setProperty("xwiki.title.compatibility", "1");
    +        this.document.setRestricted(true);
    +
    +        this.document.setContent("content not in section\n"
    +            + "= {{groovy}}print \"value\"{{/groovy}}=\nheader 1 content\n" + "== header 2==\nheader 2 content");
    +        assertThat(this.document.getRenderedTitle(Syntax.XHTML_1_0, this.oldcore.getXWikiContext()),
    +            startsWith("<span class=\"xwikirenderingerror\">Failed to execute the [groovy] macro."));
    +    }
    +
         @Test
         public void getRenderedTitleInPlainWhenExtractedFromContent()
         {
    @@ -347,6 +382,46 @@ public void getRenderedContentIsForcingCurrentDocumentAsTheSecurityDocument() th
             assertEquals("<p>Space.Page</p>", this.document.getRenderedContent(this.oldcore.getXWikiContext()));
         }
     
    +    @Test
    +    void getRenderedContentSetsRestrictedRendering() throws Exception
    +    {
    +        XWikiDocument otherDocument = new XWikiDocument(new DocumentReference("otherwiki", "otherspace", "otherpage"));
    +        otherDocument.setContentAuthorReference(new DocumentReference("otherwiki", "XWiki", "othercontentauthor"));
    +        XWikiDocument sdoc = new XWikiDocument(new DocumentReference("callerwiki", "callerspace", "callerpage"));
    +        Document apiDocument = this.document.newDocument(this.oldcore.getXWikiContext());
    +
    +        String content = "{{velocity}}test{{/velocity}}";
    +
    +        this.document.setRestricted(true);
    +        this.document.setContent(content);
    +        this.oldcore.getXWikiContext().setDoc(null);
    +
    +        // Verify that the Velocity macro is not executed.
    +        assertThat(this.document.getRenderedContent(this.oldcore.getXWikiContext()),
    +            startsWith("<div class=\"xwikirenderingerror\">Failed to execute the [velocity] macro."));
    +
    +        this.document.setRestricted(false);
    +
    +        assertEquals("<p>test</p>", this.document.getRenderedContent(this.oldcore.getXWikiContext()));
    +
    +        this.oldcore.getXWikiContext().setDoc(otherDocument);
    +
    +        assertEquals("<p>test</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString()));
    +
    +        otherDocument.setRestricted(true);
    +
    +        assertThat(apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString()),
    +            startsWith("<div class=\"xwikirenderingerror\">Failed to execute the [velocity] macro."));
    +
    +        this.oldcore.getXWikiContext().put("sdoc", sdoc);
    +        assertEquals("<p>test</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString()));
    +
    +        sdoc.setRestricted(true);
    +
    +        assertThat(apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString()),
    +            startsWith("<div class=\"xwikirenderingerror\">Failed to execute the [velocity] macro."));
    +    }
    +
         @Test
         public void getRenderedContentTextWithSourceSyntaxSpecified()
         {
    
  • xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/test/MockitoOldcore.java+5 0 modified
    @@ -629,6 +629,8 @@ public Void answer(InvocationOnMock invocation) throws Throwable
                     document.setStore(getMockStore());
     
                     XWikiDocument savedDocument = document.clone();
    +                // Make sure the saved version is not restricted.
    +                savedDocument.setRestricted(false);
     
                     documents.put(document.getDocumentReferenceWithLocale(), savedDocument);
     
    @@ -832,6 +834,9 @@ public Void answer(InvocationOnMock invocation) throws Throwable
                         }
     
                         XWikiDocument savedDocument = document.clone();
    +                    // Make sure the saved version is not restricted.
    +                    savedDocument.setRestricted(false);
    +
                         documents.put(document.getDocumentReferenceWithLocale(), savedDocument);
     
                         if (isNew) {
    
  • xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-context/src/main/java/org/xwiki/rendering/internal/macro/context/ContextMacro.java+2 1 modified
    @@ -199,7 +199,8 @@ private List<Block> executeContext(XDOM xdom, ContextMacroParameters parameters,
                         // IMPORTANT: This can be dangerous since it means executing macros, and thus also script macros
                         // defined in the referenced document. To be used with caution.
                         TransformationContext referencedTxContext =
    -                        new TransformationContext(referencedXDOM, referencedDoc.getSyntax());
    +                        new TransformationContext(referencedXDOM, referencedDoc.getSyntax(),
    +                            referencedDoc.isRestricted());
                         this.transformationManager.performTransformations(referencedXDOM, referencedTxContext);
                     }
     
    
  • xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authorization/xwiki-platform-security-authorization-bridge/src/main/java/org/xwiki/security/authorization/internal/DefaultContextualAuthorizationManager.java+3 5 modified
    @@ -136,11 +136,9 @@ private EntityReference getFullReference(EntityReference reference)
         private boolean checkPreAccess(Right right)
         {
             if (CONTENT_AUTHOR_RIGHTS.contains(right)) {
    -            if (this.renderingContext.isRestricted()) {
    -                return false;
    -            } else if (right == Right.PROGRAM && this.xcontextProvider.get().hasDroppedPermissions()) {
    -                return false;
    -            }
    +            XWikiDocument doc = getProgrammingDocument();
    +            boolean restricted = this.renderingContext.isRestricted() || (doc != null && doc.isRestricted());
    +            return !(restricted || (right == Right.PROGRAM && this.xcontextProvider.get().hasDroppedPermissions()));
             }
     
             return true;
    
  • xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authorization/xwiki-platform-security-authorization-bridge/src/test/java/org/xwiki/security/authorization/internal/DefaultContextualAuthorizationManagerTest.java+48 0 modified
    @@ -19,25 +19,36 @@
      */
     package org.xwiki.security.authorization.internal;
     
    +import java.util.stream.Stream;
    +
     import org.junit.jupiter.api.BeforeEach;
     import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.MethodSource;
     import org.xwiki.model.reference.DocumentReference;
     import org.xwiki.model.reference.LocalDocumentReference;
     import org.xwiki.model.reference.WikiReference;
    +import org.xwiki.rendering.transformation.RenderingContext;
     import org.xwiki.security.authorization.AuthorizationManager;
     import org.xwiki.security.authorization.Right;
     import org.xwiki.test.junit5.mockito.InjectMockComponents;
     import org.xwiki.test.junit5.mockito.MockComponent;
     
    +import com.xpn.xwiki.doc.XWikiDocument;
     import com.xpn.xwiki.test.MockitoOldcore;
     import com.xpn.xwiki.test.junit5.mockito.InjectMockitoOldcore;
     import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;
     import com.xpn.xwiki.test.reference.ReferenceComponentList;
     
    +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.ArgumentMatchers.eq;
     import static org.mockito.ArgumentMatchers.isNull;
     import static org.mockito.ArgumentMatchers.same;
    +import static org.mockito.Mockito.mock;
     import static org.mockito.Mockito.verify;
    +import static org.mockito.Mockito.when;
     
     /**
      * Validate {@link DefaultContextualAuthorizationManager}.
    @@ -51,6 +62,9 @@ class DefaultContextualAuthorizationManagerTest
         @MockComponent
         private AuthorizationManager authorizationManager;
     
    +    @MockComponent
    +    private RenderingContext renderingContext;
    +
         @InjectMockComponents
         private DefaultContextualAuthorizationManager contextualAuthorizationManager;
     
    @@ -89,4 +103,38 @@ void hasAccess()
             verify(this.authorizationManager).hasAccess(same(Right.VIEW), isNull(),
                 eq(new DocumentReference(localReference, this.currentWikiReference)));
         }
    +
    +    @ParameterizedTest
    +    @MethodSource("contentRightsSource")
    +    void contentAuthorRightPreAccess(Right right)
    +    {
    +        when(this.authorizationManager.hasAccess(eq(right), any(), any())).thenReturn(true);
    +
    +        assertTrue(this.contextualAuthorizationManager.hasAccess(right));
    +
    +        // Check restricted rendering context (once).
    +        when(this.renderingContext.isRestricted()).thenReturn(true).thenReturn(false);
    +        assertFalse(this.contextualAuthorizationManager.hasAccess(right));
    +
    +        XWikiDocument contextDocument = mock(XWikiDocument.class);
    +        this.oldcore.getXWikiContext().setDoc(contextDocument);
    +        assertTrue(this.contextualAuthorizationManager.hasAccess(right));
    +        verify(contextDocument).isRestricted();
    +        // Check restricted document denies script right.
    +        when(contextDocument.isRestricted()).thenReturn(true).thenReturn(false);
    +        assertFalse(this.contextualAuthorizationManager.hasAccess(right));
    +
    +        // Check dropping permissions keeps script but not programming right
    +        this.oldcore.getXWikiContext().dropPermissions();
    +        if (right == Right.PROGRAM) {
    +            assertFalse(this.contextualAuthorizationManager.hasAccess(right));
    +        } else {
    +            assertTrue(this.contextualAuthorizationManager.hasAccess(right));
    +        }
    +    }
    +
    +    static Stream<Right> contentRightsSource()
    +    {
    +        return Stream.of(Right.SCRIPT, Right.PROGRAM);
    +    }
     }
    
  • xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java+15 0 modified
    @@ -116,6 +116,21 @@ public ViewPage rollbackToVersion(String version)
             return new ViewPage();
         }
     
    +    /**
    +     * View the document at the given version.
    +     *
    +     * @param version the version to view
    +     * @return the viewpage
    +     * @since 14.10.7
    +     * @since 15.2RC1
    +     */
    +    public ViewPage viewVersion(String version)
    +    {
    +        this.pane.findElement(By.xpath(".//table//tr//td[position()=3]/a[contains(., '" + version + "')]")).click();
    +
    +        return new ViewPage();
    +    }
    +
         public HistoryPane deleteVersion(String version)
         {
             getDriver().makeConfirmDialogSilent(true);
    
  • xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/changesdoc.vm+4 0 modified
    @@ -136,6 +136,10 @@
                 $escapetool.xml($services.localization.render('web.history.changes.failedToCompute'))
               </div>
             #else
    +          #if (($origdoc.isRestricted() || $newdoc.isRestricted()) && ($services.user.allProperties.type == 'ADVANCED'
    +            || $stringtool.contains($htmlDiff, 'xwikirenderingerror')))
    +            #warning($escapetool.xml($services.localization.render('web.history.changes.restrictedInfo')))
    +          #end
               $htmlDiff
             #end
           #end
    

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

News mentions

0

No linked articles in our index yet.