Remote code execution as guest via SolrSearchMacros request in xwiki
Description
XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. Any guest can perform arbitrary remote code execution through a request to SolrSearch. This impacts the confidentiality, integrity and availability of the whole XWiki installation. To reproduce on an instance, without being logged in, go to <host>/xwiki/bin/get/Main/SolrSearch?media=rss&text=%7D%7D%7D%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dprintln%28"Hello%20from"%20%2B%20"%20search%20text%3A"%20%2B%20%2823%20%2B%2019%29%29%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D%20. If there is an output, and the title of the RSS feed contains Hello from search text:42, then the instance is vulnerable. This vulnerability has been patched in XWiki 15.10.11, 16.4.1 and 16.5.0RC1. Users are advised to upgrade. Users unable to upgrade may edit Main.SolrSearchMacros in SolrSearchMacros.xml on line 955 to match the rawResponse macro in macros.vm#L2824 with a content type of application/xml, instead of simply outputting the content of the feed.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.platform:xwiki-platform-search-solr-uiMaven | >= 5.3-milestone-2, < 15.10.11 | 15.10.11 |
org.xwiki.platform:xwiki-platform-search-solr-uiMaven | >= 16.0.0-rc-1, < 16.4.1 | 16.4.1 |
Affected products
1- Range: >= 5.3-milestone-2, < 15.10.11
Patches
167021db9b8edXWIKI-22149: Introduce a generic Velocity macro to set HTTP responses
10 files changed · +204 −53
xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/HTMLConverter.xml+1 −14 modified@@ -45,20 +45,7 @@ #set ($text = "$!request.text") #set ($stripHTMLEnvelope = $request.stripHTMLEnvelope == 'true') #set ($output = "#ckeditor_convert($text $toHTML $fromHTML $stripHTMLEnvelope)") - #set ($characterEncoding = 'utf-8') - ## Make sure the Character Encoding response header matches the character encoding used to write the response and - ## compute its length. See CKEDITOR-162: Cannot convert to source code - #set ($discard = $response.setCharacterEncoding($characterEncoding)) - ## We write the output directly to the response to avoid the execution of the Rendering Transformations. Another - ## option would be to specify which Rendering Transformations to execute in the query string (XWIKI-13167). - ## See CKEDITOR-51: Icon transformations are saved by CKEditor - #set ($discard = $response.writer.print($output)) - ## The content length is measured in bytes and one character can use more than one byte. - #set ($discard = $response.setContentLength($output.getBytes($characterEncoding).size())) - ## Make sure the entire content is send back to the client. - #set ($discard = $response.flushBuffer()) - ## Make sure XWiki doesn't write any more content to the response. - #set ($discard = $xcontext.setFinished(true)) + #rawResponse($output) #else The service used by the CKEditor source plugin to convert between HTML and wiki syntax. #end
xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/pom.xml+6 −0 modified@@ -160,5 +160,11 @@ <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> </dependencies> </project>
xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationRSSService.xml+1 −13 modified@@ -39,19 +39,7 @@ <content>{{velocity}} #set ($feedContent = $services.notification.notifiers.getFeed(20)) #if ($xcontext.action == 'get' && "$request.outputSyntax" == 'plain') - #set ($characterEncoding = 'utf-8') - #set ($discard = $response.setContentType('application/xml')) - ## Make sure the Character Encoding response header matches the character encoding used to write the response and - ## compute its length. - #set ($discard = $response.setCharacterEncoding($characterEncoding)) - ## We write the output directly to the response to avoid the execution of the Rendering Transformations. - #set ($discard = $response.writer.print($feedContent)) - ## The content length is measured in bytes and one character can use more than one byte. - #set ($discard = $response.setContentLength($feedContent.getBytes($characterEncoding).size())) - ## Make sure the entire content is send back to the client. - #set ($discard = $response.flushBuffer()) - ## Make sure XWiki doesn't write any more content to the response. - #set ($discard = $xcontext.setFinished(true)) + #rawResponse($feedContent, 'application/xml') #else {{code language="xml" source="script:feedContent" /}} #end
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-solr/xwiki-platform-search-solr-ui/pom.xml+48 −0 modified@@ -76,5 +76,53 @@ <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> </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> + <dependency> + <groupId>org.xwiki.platform</groupId> + <artifactId>xwiki-platform-feed-api</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.xwiki.platform</groupId> + <artifactId>xwiki-platform-user-default</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.xwiki.platform</groupId> + <artifactId>xwiki-platform-user-default</artifactId> + <version>${project.version}</version> + <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> \ No newline at end of file
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-solr/xwiki-platform-search-solr-ui/src/main/resources/Main/SolrSearchMacros.xml+1 −2 modified@@ -951,8 +951,7 @@ $value## ## ## Output the feed. ## - #set ($discard = $response.setContentType('application/rss+xml')) - $xwiki.feed.getFeedOutput($feed, 'rss_2.0') + #rawResponse($xwiki.feed.getFeedOutput($feed, 'rss_2.0'), 'application/rss+xml') #end #macro (handleSolrSearchRequest)
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-solr/xwiki-platform-search-solr-ui/src/test/java/org/xwiki/search/solr/ui/SolrSearchPageTest.java+116 −0 added@@ -0,0 +1,116 @@ +/* + * 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.search.solr.ui; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.rendering.RenderingScriptServiceComponentList; +import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList; +import org.xwiki.rendering.syntax.Syntax; +import org.xwiki.template.internal.macro.TemplateMacro; +import org.xwiki.test.annotation.ComponentList; +import org.xwiki.test.page.HTML50ComponentList; +import org.xwiki.test.page.PageTest; +import org.xwiki.test.page.TestNoScriptMacro; +import org.xwiki.test.page.XWikiSyntax21ComponentList; +import org.xwiki.text.StringUtils; +import org.xwiki.user.DefaultUserComponentList; +import org.xwiki.user.internal.AllUserPropertiesResolver; + +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.plugin.feed.FeedPlugin; +import com.xpn.xwiki.web.XWikiServletResponseStub; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Page test for {@code Main.SolrSearch}. + * + * @version $Id$ + */ +@ComponentList({ + TemplateMacro.class, + TestNoScriptMacro.class, + AllUserPropertiesResolver.class +}) +@RenderingScriptServiceComponentList +@DefaultRenderingConfigurationComponentList +@HTML50ComponentList +@XWikiSyntax21ComponentList +@DefaultUserComponentList +class SolrSearchPageTest extends PageTest +{ + private static final String WIKI_NAME = "xwiki"; + + private static final String MAIN_SPACE = "Main"; + + private static final DocumentReference SOLR_SEARCH_REFERENCE = + new DocumentReference(WIKI_NAME, MAIN_SPACE, "SolrSearch"); + + private static final DocumentReference SOLR_SEARCH_MACROS_REFERENCE = + new DocumentReference(WIKI_NAME, MAIN_SPACE, "SolrSearchMacros"); + + @BeforeEach + void setUp() throws Exception + { + this.xwiki.initializeMandatoryDocuments(this.context); + + this.xwiki.getPluginManager().addPlugin("feed", FeedPlugin.class.getName(), this.context); + + loadPage(SOLR_SEARCH_MACROS_REFERENCE); + } + + @Test + void checkRSSFeedContent() throws Exception + { + String unescapedText = "<b>}}}{{noscript}}</b>"; + String escapedText = "<b>}}}{{noscript}}</b>"; + + this.request.put("media", "rss"); + this.request.put("text", unescapedText); + this.context.setAction("get"); + + XWikiDocument solrSearchDocument = loadPage(SOLR_SEARCH_REFERENCE); + this.context.setDoc(solrSearchDocument); + + // Get directly the writer to check the RSS feed. + StringWriter out = new StringWriter(); + PrintWriter writer = new PrintWriter(out); + this.response = new XWikiServletResponseStub() { + @Override + public PrintWriter getWriter() + { + return writer; + } + }; + this.context.setResponse(this.response); + + String rssFeed = solrSearchDocument.displayDocument(Syntax.PLAIN_1_0, this.context); + assertTrue(StringUtils.isAllBlank(rssFeed)); + + rssFeed = out.toString(); + assertTrue(rssFeed.contains("<title>search.rss [[" + escapedText + "]]</title>")); + assertTrue(rssFeed.contains("<description>search.rss [[" + escapedText + "]]</description>")); + } +}
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/pom.xml+6 −0 modified@@ -68,6 +68,12 @@ <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> <dependency> <groupId>org.xwiki.platform</groupId> <artifactId>xwiki-platform-uiextension-api</artifactId>
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/Main/DatabaseSearch.xml+1 −14 modified@@ -124,20 +124,7 @@ #set ($discard = $feed.setLanguage("$xcontext.locale")) #set ($discard = $feed.setCopyright($xwiki.getXWikiPreference('copyright'))) #set ($feedOutput = $xwiki.feed.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'rss_2.0'))) - - #set ($discard = $response.setContentType('application/rss+xml')) - #set ($characterEncoding = 'utf-8') - ## Make sure the Character Encoding response header matches the character encoding used to write the response and - ## compute its length. - #set ($discard = $response.setCharacterEncoding($characterEncoding)) - ## We write the output directly to the response to avoid the execution of the Rendering Transformations. - #set ($discard = $response.writer.print($feedOutput)) - ## The content length is measured in bytes and one character can use more than one byte. - #set ($discard = $response.setContentLength($feedOutput.getBytes($characterEncoding).size())) - ## Make sure the entire content is send back to the client. - #set ($discard = $response.flushBuffer()) - ## Make sure XWiki doesn't write any more content to the response. - #set ($discard = $xcontext.setFinished(true)) + #rawResponse($feedOutput, 'application/rss+xml') #else {{include reference="XWiki.Results"/}}
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/DatabaseSearchPageTest.java+2 −1 modified@@ -33,6 +33,7 @@ import org.xwiki.test.page.PageTest; import org.xwiki.test.page.TestNoScriptMacro; import org.xwiki.test.page.XWikiSyntax21ComponentList; +import org.xwiki.text.StringUtils; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.plugin.feed.FeedPlugin; @@ -94,7 +95,7 @@ public PrintWriter getWriter() this.context.setResponse(this.response); String rssFeed = databaseSearchDocument.displayDocument(Syntax.PLAIN_1_0, this.context); - assertTrue(rssFeed.isEmpty()); + assertTrue(StringUtils.isAllBlank(rssFeed)); rssFeed = out.toString(); assertTrue(rssFeed.contains("<title>search.rss [" + escapedText + "]</title>"));
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/macros.vm+22 −9 modified@@ -2809,19 +2809,32 @@ Recursive title display detected!## * @param $data the data to be written as JSON on the HTTP response *# #macro (jsonResponse $data) - #set ($json = $jsontool.serialize($data)) - ## We write the output directly to the HTTP response in order to avoid going through the Rendering which executes - ## transformations that could break the JSON (e.g. the icon transformation). Another option would be to specify which - ## Rendering Transformations to execute in the query string (XWIKI-13167). - #set ($discard = $response.setContentType('application/json')) + #rawResponse($jsontool.serialize($data), 'application/json') +#end + +#** + * Writes content of any type on the HTTP response, setting the proper content type and length. + * + * @param $data the data to be written on the HTTP response + * @param $contentType the content type to use in the HTTP response (default: 'text/plain') + * @since 15.10.11 + * @since 16.4.1 + * @since 16.5.0RC1 + *# +#macro (rawResponse $data $contentType) + #if (!$contentType) + #set ($contentType = 'text/plain') + #end + #set ($characterEncoding = 'utf-8') + #set ($discard = $response.setContentType($contentType)) ## Make sure the Character Encoding response header matches the character encoding used to write the response and ## compute its length. - #set ($characterEncoding = 'utf-8') #set ($discard = $response.setCharacterEncoding($characterEncoding)) - #set ($discard = $response.writer.write($json)) + ## We write the output directly to the response to avoid the execution of the Rendering Transformations. + #set ($discard = $response.writer.print($data)) ## The content length is measured in bytes and one character can use more than one byte. - #set ($discard = $response.setContentLength($json.getBytes($characterEncoding).size())) - ## Make sure the entire content is send back to the client. + #set ($discard = $response.setContentLength($data.getBytes($characterEncoding).size())) + ## Make sure the entire content is sent back to the client. #set ($discard = $response.flushBuffer()) ## Make sure XWiki doesn't write any more content to the response. #set ($discard = $xcontext.setFinished(true))
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
8- github.com/advisories/GHSA-rr6p-3pfg-562jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-24893ghsaADVISORY
- github.com/xwiki/xwiki-platform/blob/568447cad5172d97d6bbcfda9f6183689c2cf086/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-solr/xwiki-platform-search-solr-ui/src/main/resources/Main/SolrSearchMacros.xmlghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/blob/67021db9b8ed26c2236a653269302a86bf01ef40/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/macros.vmghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/commit/67021db9b8ed26c2236a653269302a86bf01ef40ghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-rr6p-3pfg-562jghsax_refsource_CONFIRMWEB
- jira.xwiki.org/browse/XWIKI-22149ghsax_refsource_MISCWEB
- www.cisa.gov/known-exploited-vulnerabilities-catalogghsaWEB
News mentions
0No linked articles in our index yet.