CVE-2026-33229 Bypassing Apache Velocity Sandbox to Achieve RCE in XWiki
Bypassing Apache Velocity Sandbox to Achieve RCE in XWiki
Description
A Remote Code Execution (RCE) vulnerability exists in both the Velocity macro and the page title parameter during page creation in XWiki. This flaw can be exploited by an authenticated user who has scripting permissions.
The application allows users to create Velocity scripts, enabling an attacker to inject arbitrary Velocity expressions or other malicious template syntax. These inputs are evaluated server-side, effectively bypassing the Velocity sandbox.
By crafting a specially designed payload, the attacker can execute arbitrary commands on the underlying operating system with the privileges of the web application.
Timeline (CVE disclosure)
- 7 November 2025: Vulnerability discovered and reported
- 11 November 2025: Vendor acknowledgement and first response
- 10 December 2025: Patch developed and committed
- 10 December 2025: Issue resolved
- 8 April 2026: Public disclosure / CVE published (based on last update)
Details
We will focus on macros Velocity, but the same technique works for the page title parameter.
In XWiki, during page creation, we have the possibility to add Velocity macros on our page — using Velocity Template Language to create scripts (declare variables, implement logic, call functions of objects).
The Challenge
Our goal is to achieve code execution. Looking online, we find that Velocity has some SSTI (Server-Side Template Injection) payloads to get code execution. However, in our case, these don't work because XWiki has the Velocity sandbox activated.
It is not working because of the sandbox.
Understanding Java Reflection for Payloads
Before diving into our bypass technique, it's important to understand how traditional SSTI payloads work. Many payloads use Java Reflection mechanisms to access sensitive classes and execute arbitrary code.
Here is an example of a typical Java Reflection payload:
#set($class = $class.forName("java.lang.Runtime"))
#set($runtime = $class.getMethod("getRuntime").invoke(null))
#set($exec = $runtime.exec("id"))
The decomposition of this payload is as follows:
- Instantiating the class — Using
forName()to load the target class - The forName method — Loads other classes dynamically at runtime
- Retrieving the Runtime class — We obtain the Runtime class to execute system commands
- String instantiation — Creating strings to pass as arguments to methods
Important Limitation: To use Java Reflection, the Velocity sandbox mode must be disabled, as this mode restricts access to many classes and packages, including those related to Java Reflection.
The Bypass Discovery
During my internship at Fenrisk, we studied the Velocity sandbox to find ways to bypass it. We were able to discover different bypass methods. In this article, we will discuss one of those methods, based on CVE-2020-13936.
But before discussing the bypass, we need to understand how the Velocity sandbox works. By looking at the code, we see that the sandbox uses a blacklist of classes and packages that we cannot use. This is interesting because there is no restriction on objects that are already instantiated — we can use them to navigate from one object to another until we find an object that can provide code execution.
Since the Velocity sandbox does not define any rules on objects already present in the context, it is possible to bypass it if we have the right object.
Analyzing the Velocity Context
Our main focus will be the objects available in the Velocity context. To identify them, we set a remote breakpoint using IntelliJ to see all objects used during rendering of the Velocity macro.
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
We see that we have 49 objects in the context — a lot for rendering a user script!
The Attack Chain
The object that is interesting for us is the request object. This
object represents the request context and all its attributes. We can use it to switch to the
application context and access more interesting objects used by the application.
$request.request.getServletContext()
From there, we found the org.apache.tomcat.InstanceManager object. This can be used
to instantiate classes without using Java Reflection. However, there is an
important limitation: we cannot instantiate classes that do not have a nullary constructor
(constructor without arguments).
// Retrieving the InstanceManager object
$request.request.getServletContext()
.getAttribute("org.apache.tomcat.InstanceManager")
Looking at the classes available in the classpath, we discovered JPythonInterpreter, which can be used to execute arbitrary Python code — and therefore system commands.
// Choosing the class to instantiate
.newInstance("org.apache.batik.script.jpython.JPythonInterpreter")
The Final Payload
Now we have everything we need to achieve code execution. Here is the complete breakdown of our payload:
$request.request.getServletContext() // Step 1: Access the application context
.getAttribute("org.apache.tomcat.InstanceManager") // Step 2: Retrieve InstanceManager
.newInstance("org.apache.batik.script.jpython.JPythonInterpreter") // Step 3: Instantiate JPythonInterpreter
.evaluate("import os; os.system('touch /tmp/RCE')") // Step 4: Execute Python code for RCE
This payload works by:
- Step 1: Transitioning from the
requestcontext to the application context usinggetServletContext() - Step 2: Retrieving the
InstanceManagerobject stored as an attribute in the ServletContext - Step 3: Using
InstanceManager.newInstance()to instantiateJPythonInterpreter(which has a nullary constructor) - Step 4: Calling
evaluate()to execute Python code, giving us full command execution
Key Insight: This bypass works because we never directly use Java Reflection. Instead, we leverage existing objects in the Velocity context and navigate through them until we find one that provides code execution capabilities. The sandbox's blacklist approach fails since we only interact with pre-instantiated trusted objects.
Impact
This vulnerability allows an authenticated attacker with scripting permissions to:
- Execute arbitrary system commands on the server running XWiki
- Gain a reverse shell
- Escalate privileges within the application server
- Access sensitive data or modify system files
Why This Works
- The sandbox uses a blacklist approach which fails because we're not directly calling forbidden classes
- We navigate through already instantiated objects that are trusted in the context
- No Java Reflection needed — we bypass the sandbox restrictions by using pre-existing objects
- The InstanceManager acts as a factory for objects with default constructors
- JPythonInterpreter provides native code execution capabilities
Conclusion: This finding demonstrates that even with sandboxing mechanisms in place, object graph traversal can lead to complete system compromise when the sandbox relies on incomplete blacklisting. By leveraging existing objects in the Velocity context rather than using Java Reflection directly, we successfully bypassed the sandbox restrictions. XWiki users should apply patches or mitigations as soon as they become available.
Refrences
- Fenrisk: https://fenrisk.com/
- CVE-2020-13936: https://securitylab.github.com/advisories/GHSL-2020-048-apache-velocity/
- Security Advisory: https://github.com/xwiki/xwiki-platform/security/advisories/GHSA-h259-74h5-4rh9