WSO2 #2: The many ways to bypass authentication in WSO2 products
CVE-2025-9152, CVE-2025-10611, and CVE-2025-9804 are critical authentication bypass and privilege escalation vulnerabilities I discovered in WSO2 API Manager and WSO2 Identity Server.
This is my series on vulnerabilities I discovered in WSO2 software in 2025:
CVE-2025-9152, CVE-2025-10611, and CVE-2025-9804 are critical authentication bypass and privilege escalation vulnerabilities I discovered in WSO2 API Manager and WSO2 Identity Server.
In this post, I detail three full authentication bypasses I found in WSO2 API Manager and WSO2 Identity Server, as well as one user-to-admin privilege escalation vulnerability.
I've never built anything in Java, so I'm not sure if what I'm about to describe is standard practice in Java-based web applications. However I hope that it isn't, because it seems like a bad idea:
WSO2 API Manager and Identity Server enforce access control using a configuration file that describes, for each API endpoint, a regular expression that covers a request path, and a set of permissions needed to access that path.
<Resource context="(.*)/api/server/v1/applications(.*)" secured="true" http-method="POST">
<Permissions>/permission/admin/manage/identity/applicationmgt/create</Permissions>
<Scopes>internal_application_mgt_create</Scopes>
</Resource>
<Resource context="(.*)/api/server/v1/applications(.*)" secured="true" http-method="PUT, PATCH">
<Permissions>/permission/admin/manage/identity/applicationmgt/update</Permissions>
<Scopes>internal_application_mgt_update</Scopes>
</Resource>
<Resource context="(.*)/api/server/v1/applications(.*)" secured="true" http-method="DELETE">
<Permissions>/permission/admin/manage/identity/applicationmgt/delete</Permissions>
<Scopes>internal_application_mgt_delete</Scopes>
</Resource>
This is what I'm talking about (repository/conf/identity/identity.xml) [1]
This list of regular expressions and permissions lives in a different place to the actual code of the API endpoints. In fact, it's in a different git repository.
That seems like a bad idea: I would think that it would probably be safer to have the set of permissions sit right next to the code, or even inside of it, and to not use regular expressions at all for this purpose. Regex mistakes are so common that they have their own vulnerability class: CWE-185: Incorrect Regular Expression.
What if a WSO2 developer gets a regular expression wrong? Or what if they make a change to the API code but forget to update the configuration file? On the hunt for these potential mistakes, I started testing the internal WSO2 APIs against the configuration file, and it didn't take long to discover:
Authentication bypass in WSO2 API Manager ≥ 3.2.0 (CVE-2025-9152)
WSO2 API Manager implements OAuth 2.0 for authentication. The settings around OAuth clients are managed with a super-sensitive internal API that lives under /keymanager-operations/.
A compromise of this API would be existential, as an attacker could use it to introduce their own authentication mechanism with custom credentials, allowing them to grant themselves administrative access. It is very important for those regexes to be accurate and complete.
Unfortunately, the regular expressions in the configuration document fail to capture all the important endpoints under /keymanager-operations/. To my horror, I noticed some endpoints aren't included at all, and others can have their regular expressions bypassed with a simple trailing slash.
For instance, the API endpoint at /keymanager-operations/dcr/register/<client_id> allows you to read everything about a particular OAuth client, including its very-very-sensitive client_secret. This endpoint is missing from the configuration entirely, meaning it has no access control rules. It can be accessed without any authentication whatsoever.
As another example, although the configuration file says that /keymanager-operations/dcr/register requires certain administrative privileges to access, the regular expression fails to capture /keymanager-operations/dcr/register/, which hits the same endpoint. That allows an unauthenticated attacker to register a new OAuth client, creating a new mechanism to log in to API Manager.
<Resource context="(.*)/keymanager-operations/dcr/register" secured="true"
http-method="POST"
>
<Permissions>/permission/admin/manage/identity/applicationmgt/create</Permissions>
<Scopes>internal_application_mgt_create</Scopes>
</Resource>This entry doesn't capture a trailing slash
Either of these mistakes is a disaster:
Granting yourself admin access with client_credentials
When an API Manager OAuth client supports the Client Credentials grant type, an attacker with the valid client ID and secret pair can generate an access token with arbitrary privileges.
By default, the built-in OAuth clients in API Manager don't support this grant type, however a single unauthenticated PUT request to one of those free-for-all endpoints can change that.
There are multiple ways to exploit CVE-2025-9152: an attacker can register their own OAuth client, or they can modify an existing one. Both paths lead to total compromise.
One exploitation of CVE-2025-9152 looks like this:
- The attacker finds an API Manager client ID, which is public by design. (It's in the URL bar when you click 'Log in'.)
- They make a
GET /keymanager-operations/dcr/register/<client_id>to leak theclient_secret. - If the OAuth client doesn't support
client_credentialsalready, the attacker makes it so with an unauthenticatedPUTrequest to the same endpoint, carrying the body:{"grant_types": ["client_credentials"]}. - Now, the attacker makes a request to
POST /oauth2/token, authenticating with theclient_idandclient_secretpair to issue an access token with an arbitraryscope, e.g.,grant_type=client_credentials&scope=apim:admin+apim:api_create+any-other-scope-goes-here. - That endpoint responds with an access token, which grants total administrative access across all of the REST APIs built into API Manager.
This flaw allows an unauthenticated attacker to gain total administrative access in just a few requests.
Admin access is game over in WSO2
In these WSO2 products, once you have an administrator-privileged account, you have access to read, modify, or delete everything. For example, you can arbitrarily change the passwords of other users.
Best of all, you can virtually always upgrade to remote code execution by taking your pick of the many known admin-to-RCE vulnerabilities in WSO2 software. After all, who patches CVEs that require administrator access to exploit?
It's not the first time these regexes have been broken
WSO2's response to my discovery was to tighten that list of regular expressions. While that solves this bug, I don't believe it gets at the root of the problem.
A few weeks after reporting this auth bypass, I came across the advisory for WSO2-2022-2177: a critical 'broken access control' vulnerability affecting WSO2 API Manager, Identity Server, and their open banking product.
(Like many critical WSO2 vulnerabilities, this one doesn't have a CVE ID, however you might find it on WSO2's website if you already know what it is you're looking for.)
Back in 2022, another researcher[2] figured out that if you just add ; somewhere in a path, you can avoid hitting any of the regular expressions, effectively bypassing virtually all access controls. For instance, the API endpoint /scim2/Users allows administrators to access and modify all user data in Identity Server. The researcher figured out that /scim2;/Users opens that up to everyone.
WSO2's response to that vulnerability was not to rethink the whole idea of using path regular expressions for access control. Instead, they doubled down and introduced a new expression:
private static final String URL_PATH_FILTER_REGEX = "(.*)/((\\.+)|(.*;+.*)|%2e)/(.*)";
private static final Pattern URL_MATCHING_PATTERN = Pattern.compile(URL_PATH_FILTER_REGEX);
...
private void validateRequestURI(String url) throws AuthenticationFailException {
if (url != null && URL_MATCHING_PATTERN.matcher(url).matches()) {
throw new AuthenticationFailException("Given URL contain un-normalized content. URL validation failed for "
+ url);
}
}The important part of the patch for WSO2-2022-2177
That new regular expression blocks paths that match (.*)/((\.+)|(.*;+.*)|%2e)/(.*), which covers bypasses like /scim2;/, /scim2/Me/../Users, and more variants of the same trick.
There's a huge problem with that regex, which I'll get to later.
Method-based authentication bypass in API Manager, Identity Server (CVE-2025-10611)
I mentioned before that the entries contain two things: the path regex and the set of required permissions. However, there is really a third item that's very important: the HTTP method.
It makes sense to ask for different permissions depending on the method. For example, a particular endpoint might allow anyone to read an item (with a GET request), but updating and deleting (POST, PATCH, DELETE, etc.) might be reserved for elevated users.
For instance, take another look at this snippet (note the http-method="POST"):
<Resource context="(.*)/keymanager-operations/dcr/register" secured="true"
http-method="POST"
>
<Permissions>/permission/admin/manage/identity/applicationmgt/create</Permissions>
<Scopes>internal_application_mgt_create</Scopes>
</Resource>The entry protecting /keymanager-operations/dcr/register
The above entry states that all POST requests to /keymanager-operations/dcr/register require administrator authentication. Requests to this path with other methods, like GET, PUT, etc., don't need authentication, but they also don't land anywhere, as this /register endpoint only handles POST.
The vulnerability here is equally as devastating as CVE-2025-9152:
To match an incoming HTTP request to its corresponding <Resource>, the request path and method are compared against each entry using the equals method in ResourceConfigKey.java:
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResourceConfigKey that = (ResourceConfigKey) o;
Matcher matcher = pattern.matcher(that.contextPath);
if (!matcher.matches()) {
return false;
}
if (httpMethod.equalsIgnoreCase("all")) {
return true;
}
return httpMethod.contains(that.httpMethod);
}The equals() method returns false if the path doesn't match the regular expression. Otherwise, it inspects the HTTP method:
When the entry's http-method attribute is all, meaning the <Resource> covers all HTTP methods indiscriminately, equals() returns true.
Otherwise, the code checks whether the <Resource>'s http-method string contains the incoming request's method. It uses .contains here rather than .equals because sometimes an entry spans multiple methods; e.g., http-method="PUT, PATCH".
The big problem: .contains is case-sensitive.
A <Resource http-method="POST"> only matches on all-caps POST. If you whisper the method in lowercase, equals() gives false. This allows an attacker to provide an alternatively cased method, e.g., Post versus POST, to totally bypass the imposed restriction.
Importantly, the method is normalised later on. When the HTTP request is greeted at this security checkpoint, Post and POST are distinct, however by the time it reaches the API request handler, there's no discernible difference between the two. Requests with Post, post, and PoST will reach the same endpoint as POST.
That makes it possible to invoke the sensitive /keymanager-operations/dcr/register endpoint to register a new OAuth client without any authentication whatsoever, and without any path trickery:
Post /keymanager-operations/dcr/register HTTP/1.1Registering a new OAuth client. This time without the trailing slash.
As explained before, the ability to register a new OAuth client leads to total compromise; its impact is the same as CVE-2025-9152.
In addition, this method-based bypass opens up a range of miscellaneous authentication bypass and privilege escalation problems in both API Manager and Identity Server.
Path-based authentication bypass in API Manager, Identity Server (CVE-2025-10611)
This is the same CVE ID as the above, as CVE-2025-10611 encompasses two vulnerabilities. While similar in that they each enable full authentication bypass in Identity Server and API Manager, they need to be explained individually.
The other half of CVE-2025-10611 similarly allows an attacker to invoke sensitive endpoints without authentication. In practice, this lets an attacker go from without an account to a system administrator in one or two requests.
For demonstration, I will pick on the Identity Server API endpoint /scim2/Users. Briefly mentioned earlier, this API will let an administrator register a new user. (A subsequent request to another /scim2/ endpoint can upgrade that user to a server administrator.)
Here's the entry protecting /scim2/Users:
<Resource context="(.*)/scim2/Users(.*)" secured="true" http-method="POST">
<Permissions>/permission/admin/manage/identity/usermgt/create</Permissions>
<Scopes>internal_user_mgt_create</Scopes>
</Resource>The entry protecting /scim2/Users in the default identity.xml
First, you might notice the http-method="POST". Yes, this entry is vulnerable to the method-based bypass too, with a caveat[3], however I can also reach it without touching the HTTP method.
An attacker's first step is to take a wider look at the identity.xml configuration to select a totally unrelated resource that doesn't require any authentication.[4] Here's one:
<Resource context="(.*)/.well-known/openid-configuration(.*)" secured="false" http-method="all"/>The entry saying that .well-known/openid-configuration doesn't need authentication
This entry says that unauthenticated requests to /.well-known/openid-configuration are allowed. That makes sense, because that's intended to be public. What doesn't make as much sense, however, is the (.*) at the beginning of that expression. Functionally, this means that requests to /whatever/.well-known/openid-configuration don't need authentication either.
But /whatever/.well-known/openid-configuration leads to a 404, so who cares?
The trick to the second half of CVE-2025-10611 is to convince the WSO2 server that you're requesting the safe thing, while you're doing something entirely different.
In the distant past, this used to work:
POST /scim2/Users;/.well-known/openid-configuration HTTP/1.1Identity Server auth bypass before 2022
However, that was fixed with the patch to WSO2-2022-2177 that I mentioned earlier, which added a regular expression to prevent these sorts of bypasses:
(.*)/((\.+)|(.*;+.*)|%2e)/(.*)The regular expression from WSO2-2022-2177's patch
In plain English, what that regular expression is saying is that you can't have a slash at any point after a semicolon. ;/ is rejected, as is ;a=a/, and the above ...;/.well-known.
To find the bypass, we need to step back and take a wider look at the logic in WSO2's authentication gate, paying close attention to the ordering of events. Here's the relevant snippets of WSO2's AuthenticationValve.java, responsible for these defences:
private static final String URL_PATH_FILTER_REGEX = "(.*)/((\\.+)|(.*;+.*)|%2e)/(.*)";
private static final Pattern URL_MATCHING_PATTERN = Pattern.compile(URL_PATH_FILTER_REGEX);
...
private void validateRequestURI(String url) throws AuthenticationFailException {
if (url != null && URL_MATCHING_PATTERN.matcher(url).matches()) {
throw new AuthenticationFailException("Given URL contain un-normalized content. URL validation failed for "
+ url);
}
}
...
public void invoke(Request request, Response response) throws IOException, ServletException {
...
validateRequestURI(request.getRequestURI());
String normalizedRequestURI = AuthConfigurationUtil.getInstance().getNormalizedRequestURI(request.getRequestURI());
ResourceConfig securedResource = authenticationManager.getSecuredResource(
new ResourceConfigKey(normalizedRequestURI, request.getMethod()));
...
}AuthenticationValve.java (trimmed by me it to its essentials for illustration)
The method invoke is where the enforcement takes place. As you can see, it first calls validateRequestURI, which is where the request path is checked against that regular expression. If the regex matches, the request is rejected.
After the regular expression test, but before the task of reconciling the request with its corresponding <Resource> begins, something important happens: the request path is 'normalised'. Here is getNormalizedRequestURI():
public String getNormalizedRequestURI(String requestURI) throws URISyntaxException, UnsupportedEncodingException {
if (requestURI == null) {
return null;
}
String decodedURl = URLDecoder.decode(requestURI, StandardCharsets.UTF_8.name());
return new URI(decodedURl).normalize().toString();
}The function getNormalizedRequestURI from AuthConfigurationUtil.java
This function uses Java's URI library to standardise the request path, removing unnecessary ../ segments, which prevents simple path traversal techniques. It also does something interesting: it undoes any superfluous encoding of characters that don't need to be encoded. For example, the path /hello%2Fworld is normalised to /hello/world.
With that in mind, let's change the payload:
POST /scim2/Users/;%2F.well-known%2Fopenid-configuration HTTP/1.1Identity Server auth bypass in 2025
That's it. The path doesn't hit the regular expression, and it's reconciled incorrectly with the unprotected resource. Identity Server's security guard believes that you're after openid-configuration, but the request is actually routed to /scim2/Users. (Everything after the semicolon, which denotes a matrix parameter, is ignored by the server.)
With that request, you can register a new user, and with a second similar request, you can make that user an administrator. Game over.
Privilege escalation in WSO2 API Manager ≤ 3.1.0 (CVE-2025-9804)
Before the introduction of the /keymanager-operations/ API, there was a similar SOAP-based API that existed under /services/APIKeyMgtSubscriberService. This API essentially did the same thing: the creation and management of OAuth clients. I presume that /keymanager-operations/ was its successor.
You can't access APIKeyMgtSubscriberService without valid credentials; you need to be a user. However, you don't need any special privileges beyond that: you only need the ability to log in.
This allows low-level users to gain administrative access in a procedure again almost identical to CVE-2025-9152.
Matters become worse when you realise that many API Manager deployments allow self-signup, and even when self-signup is manually disabled, there are two distinct different vulnerabilities that allow someone to sign up anyway. The latest of those vulnerabilities, CVE-2024-7097, affected all versions of API Manager before April 2024 and remains highly exploitable today.
(CVE-2025-9804 will come up again later in this series, as it's an umbrella for various unrelated insufficient-authorisation bugs in WSO2 software.)
Aftermath
After reporting these issues and many others to WSO2, they asked me to stop testing their products and declared they've "allocated a dedicated team to work under war room mode" to address their vulnerabilities.
Following your consistent efforts, we have decided to conduct a thorough review of our admin services and legacy code bases to ensure that all implementations align with security best practices. To carry out this initiative, we have allocated a dedicated team to work under war room mode.
In the meantime, we kindly request you to hold off on testing activities until our analysis is completed. Once the review is finalized, you may resume your testing and report any gaps or findings we may have overlooked.
We appreciate your understanding and cooperation in this matter.WSO2's email to me on 26 August 2025
I disregarded the request and reported more critical issues later that day.
Notes
[1] This configuration can alternatively be defined in deployment.toml with an equivalent syntax.
[2] Rick Roane.
[3] Identity Server is a bit different to API Manager in that requests that don't match any <Resource> are rejected, whereas in API Manager they are allowed. Therefore, in order to reach an endpoint with the lowercase-method bypass without any authentication whatsoever, it must be used in conjunction with the path-based bypass. The method-based auth bypass on its own exposes various major privilege escalation issues in Identity Server, but not a complete authentication bypass.
[4] The chosen unprotected resource needs to come before the protected resource in order for this to work. Otherwise, the request will be matched with the correct entry before it's tested against the decoy.
Disclosure
- 2025-08-19: Notified WSO2 of the first issue, CVE-2025-9152.
- 2025-09-04: WSO2 notifies their paying users of the vulnerability, providing a patch for CVE-2025-9152. Non-paying users won't be notified of a patch for another ~40 days.
- 2025-08-25: Notified WSO2 of the vulnerability affecting versions ≤ 3.1.0, CVE-2025-9804.
- 2025-08-28: WSO2: "The team is currently operating in war room mode to complete the permanent resolution."
- Approx. 2025-09-01: I begin making submissions to bug bounty programs variously exploiting both halves of CVE-2025-10611. One of the recipients of my research passes along the information to WSO2.
- 2025-09-18: I notice code changes on GitHub that address CVE-2025-10611. I reach out to WSO2; they confirm that they've been made aware.
- 2025-09-30 or earlier: WSO2 notifies their paying users of CVE-2025-10611 and CVE-2025-9804.
- 2025-10-17: WSO2 publishes security advisories for the general public.