---
# System prepended metadata

title: Phân tích CVE-2022-28219 Zoho ManageEngine ADAudit Plus XXE to RCE
tags: [Deserialize, CVE, XXE, ManageEngine, java]

---

###### tags: `CVE`,`XXE`, `Deserialize`, `ManageEngine`, `java`
# Phân tích CVE-2022-28219 Zoho ManageEngine ADAudit Plus XXE to RCE

## 1. Tổng quan
ManageEngine ADAudit Plus là một giải pháp phân tích hành vi người dùng và kiểm toán thay đổi thời gian thực, giúp giữ cho Active Directory, Azure AD, máy chủ thành viên, máy chủ tệp và máy trạm của doanh nghiệp được bảo mật và tuân thủ.

![](https://hackmd.io/_uploads/SyXHn7LD3.png)
https://www.manageengine.com/products/active-directory-audit/cve-2022-28219.html
Ta thấy rằng là ở CVE-2022-28219 này thì manageengine tồn tại khá nhiều lỗ hổng như XXE, Path traversal, Deserialize ... Và từ những cái này chain lại với nhau ta có thể RCE được ứng dụng.

## 2. Setup AD & remote debug
Phần mềm này chỉ chạy trên môi trường nên AD, nên cần ta setup môi trường AD để reproduce bug.

Ở đây mình sử dụng windows server 2012 để làm DC. Sau đó sử dụng win10 để join domain.
Sau đó tải ứng dụng về và install như bình thường trên máy win10 này.

![](https://hackmd.io/_uploads/S18ZB5IP3.png)

Đây là giao diện của nó, khi cài đặt thành công.
Thực hiện setup remote debug tương tự như bài viết bên dưới. 
https://hackmd.io/@d47/BkXm7-4P3
## 3. Phân tích
### 3.1 Bug Deserialize && Path traversal
Lỗ hổng này tồn tại ở trong Cewolf charting library, ở endpoint /cewolf/\*.
https://srcincite.io/advisories/src-2020-0011/
Tìm trong file web.xml thì ta thấy nó được xử lí bởi class **de.laures.cewolf.CewolfRenderer**
![](https://hackmd.io/_uploads/Hk--6FPD3.png)

![](https://hackmd.io/_uploads/r14-aKDw2.png)

Đọc code của class này thì ở hàm **doGet()** , xử lí các get request đến endpoint **/cewolf/***
![](https://hackmd.io/_uploads/Byir_-9P2.png)
Ta thấy nó có thể nhận vào nhiều tham số khác nhau. Trong đó có tham số **img**
Khi mà tham số này được truyền giá trị vào, thì nó sẽ tạo một biến storage, rồi gọi đến hàm **getChartImage()** với tham số **img** mà ta truyền vào.

Nhảy vào hàm **getChartImage()**
![](https://hackmd.io/_uploads/BJaBtZ5P3.png)
Ta thấy ở đây nó thực hiện đọc file từ tham số id, thông qua hàm getFileName(). Sau đó thực hiện deserialize
Hàm **getFileName()**
![](https://hackmd.io/_uploads/H1vUjKwwh.png)
Ta thấy hàm này dính bug Path trarversal, dẫn đến attacker có thể deserialize 1 file bất kì trên hệ thống.

Vậy để RCE được thì ta cần tìm cách nào đó để upload 1 file chứa serialize data lên server này. 

### 3.2 Bug XXE
Sink của bug này nằm ở hàm **com.adventnet.sym.adsm.auditing.server.category.ProcessTrackingListener#parseTaskContent**

![](https://hackmd.io/_uploads/rkwVO5IP2.png)
Ta thấy nếu như ta kiểm soát được giá trị của biến xmlString thì dễ dàng có bug XXE. Giá trị của xmlString sẽ được lấy từ key **Task Content** hoặc key **Task New Content** của object **eventTbl** 

hàm này được gọi bên trong hàm **com.adventnet.sym.adsm.auditing.server.category.ProcessTrackingListener#getEventRow**

![](https://hackmd.io/_uploads/Bkg3ztcLD2.png)

Giờ ta đã biết sink, cần tìm source.
Đọc file web.xml bênd dưới
![](https://hackmd.io/_uploads/Bk0yl3Iw2.png)

Class **com.adventnet.sym.adsm.auditing.webclient.ember.api.ADAPAgentAPIServlet** này sẽ xử lí các request có endpoint với pattern  **/api/agent/***

Vào source code để xem code của class

```java=
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.adventnet.sym.adsm.auditing.webclient.ember.api;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ADAPAgentAPIServlet extends HttpServlet {
    public ADAPAgentAPIServlet() {
    }

    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

    private void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            request.setCharacterEncoding("UTF-8");
            int errCode = RestAPIHandler.getInstance().executeAgentRequest(request, response);
            if (errCode != 200) {
                response.sendError(errCode);
            }
        } catch (Exception var4) {
            var4.printStackTrace();
        }

    }
}
```
Hàm **processRequest()** sẽ chịu trách nhiệm xử lí request, và kiểm tra request đang muốn đến route nào, sau đó nó sẽ forward đến chính xác cái route đó. Có vai trò giống như một reverse-proxy

![](https://hackmd.io/_uploads/SkO0lODP2.png)
Nhảy vào hàm executeAgentRequest
Ở trong hàm này ta thấy nó sử dụng reflection để gọi đến method receiveData của object AgentDataHandler 
![](https://hackmd.io/_uploads/BJR2-dDv3.png)
Kiểm tra body có hợp lệ ko, sau đó chuyển body sang một JSONArray
![](https://hackmd.io/_uploads/SJ3Jq_vDn.png)

![](https://hackmd.io/_uploads/SkLB9OvDn.png)
Sau đó kiểm tra nó có hợp lệ hay ko, sau đó sẽ duyệt qua các phần tử của array và gọi đến hàm notify của class EventDataAdapter
![](https://hackmd.io/_uploads/BJ9j9_vDn.png)
Hàm notify nó sẽ add cái data vào queue để xử lí
![](https://hackmd.io/_uploads/HJMtsOPwh.png)
Sau đó gọi đến hàm com.adventnet.sym.adsm.auditing.server.EventDataAdapter.EventDispatcher#run
![](https://hackmd.io/_uploads/rJ8pnuwDh.png)
Nó sẽ kiểm tra cái domainName truyền vào có đúng hay ko. Ta cần cung cấp đúng cái domain của DC
![](https://hackmd.io/_uploads/HkevauwD2.png)
Nếu sai nó sẽ quăng exception.
Sau khi duyệt qua các cặp key,value từ cái domainName, nó sẽ gọi đến hàm com.adventnet.sym.adsm.auditing.server.ProcessMonitor#addEventRows
![](https://hackmd.io/_uploads/BJWPCOwv2.png)
Sau đó gọi đến hàm com.adventnet.sym.adsm.auditing.server.category.EventCategoryListener#getEventRowList
![](https://hackmd.io/_uploads/HkblJYDwh.png)
Tiếp đến gọi hàm **com.adventnet.sym.adsm.auditing.server.category.DefaultCategoryListener#getEventRowList**
![](https://hackmd.io/_uploads/BJSIyYvP3.png)
Ta đã chạm được đến sink của chain 
![](https://hackmd.io/_uploads/S1xhJFDwn.png)
Trigger XXE
![](https://hackmd.io/_uploads/BkybeKPDn.png)

Vì là blind XXE, nên ta cần sử dụng OOB để đọc output
![](https://hackmd.io/_uploads/BkrixTUw3.png)
Test oob đã nhận được request
![](https://hackmd.io/_uploads/SkqulTLv3.png)
## 4. Khai thác
Giờ chúng ta cần upload 1 file chứa payload serialize lên trên server.
Để mà upload file thông qua XXE, thì trong java nó có hỗ trợ  jar protocol. 
https://docs.oracle.com/javase/8/docs/api/java/net/JarURLConnection.html
Ta có thể lợi dụng giao thức này để mà upload file bất kì lên hệ thống.


Khi test trên máy thật thì nó nằm ở thư mục **c:/Windows/Temp/**
Nhưng win10 trên máy ảo thì **c:/Users/Administrator/AppData/Local/Temp/**

![](https://hackmd.io/_uploads/S1C01ZDw2.png)

Vậy là từ bug XXE ta đã có thể upload 1 file bất kì lên server, nhưng vì file nó được tạo random nên ta ko thể biết chính xác filename.
Và file temp này sẽ được xoá, ngay khi connection bị đóng. Vì vậy ta phải tìm cách làm cho connection luôn mở, vì vậy file sẽ không bị xoá.

Tiếp tục sử dụng tool/script để làm việc này.
https://github.com/pwntester/BlockingServer
Vậy giờ là ta đã có cách khiến cho file upload lên không bị xoá. Vấn đề xoá file đã được giải quyết.
Nhưng làm sao để biết được chính xác tên file ?
Thì trong java nó hỗ trợ file protocol. Sử dụng file protocol ngoài việc đọc file, nếu ta cung cấp 1 thư mục nó sẽ listing cái thư mục đó ra cho chúng ta.
Các file trên thư mục Desktop win10
![](https://hackmd.io/_uploads/BkWHsPww2.png)
Request list các file trong thư mục
![](https://hackmd.io/_uploads/Sk_egdwD2.png)

Đã list thư mục thành công, giờ ta đã biết được chính xác tên file
![](https://hackmd.io/_uploads/r1aXjvvP3.png)

**Step by step to exploit**
Sử dụng chain CB1 từ ysoserial để tạo payload và ghi vào 1 file
```
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 "notepad.exe" > cb.bin
```
Upload file cb.bin lên server
![](https://hackmd.io/_uploads/BkjSC-5wn.png)
Chạy blocking server để cho file ko bị xoá, lúc này Blocking server sẽ nhận request từ webserver để download file

![](https://hackmd.io/_uploads/SJM8JfcDh.png)

Lấy tên file trên webserver, sau khi được upload lên
![](https://hackmd.io/_uploads/S1i50WqD3.png)
Tên file là jar_cache3720071065187505036.tmp
![](https://hackmd.io/_uploads/ByyqlMcw3.png)

Trigger deserialize
![](https://hackmd.io/_uploads/Hy0iRZcw2.png)
RCE timeee
![](https://hackmd.io/_uploads/rkyTRZcD3.png)

Notepad đã được popup
## 5. Patch
Ở bản vá lỗi XXE, thì ManageEngine đã gọi đến hàm getDocumentBuilder() của class SecurityUtil trước khi thực hiện parse XML
![](https://hackmd.io/_uploads/rkD72STvh.png)
Hàm này có chức năng disable các thuộc tính cho phép load các external entity, ...
![](https://hackmd.io/_uploads/HyaFlIpD3.png)

## 6. Tham khảo
https://www.horizon3.ai/red-team-blog-cve-2022-28219/
https://github.com/LandGrey/xxe-ftp-server