CVE-2025-66370 Overview
CVE-2025-66370 is an XML External Entity (XXE) injection vulnerability in Kivitendo enterprise resource planning (ERP) software before version 3.9.2. An authenticated attacker can upload a crafted electronic invoice in the ZUGFeRD format that references external entities. The XML parser resolves those entities and returns file contents from the server's filesystem to the attacker. The flaw is tracked as CWE-611: Improper Restriction of XML External Entity Reference.
Critical Impact
An authenticated Kivitendo user can read arbitrary files readable by the application service account, including configuration files that may hold database credentials or API secrets.
Affected Products
- Kivitendo ERP versions prior to 3.9.2
- Kivitendo SL/XMLInvoice.pm ZUGFeRD/Factur-X parser
- Kivitendo SL/ZUGFeRD.pm XMP metadata parser
Discovery Timeline
- 2025-11-28 - CVE-2025-66370 published to the National Vulnerability Database (NVD)
- 2026-06-17 - Last updated in NVD database
Technical Details for CVE-2025-66370
Vulnerability Analysis
Kivitendo accepts ZUGFeRD electronic invoices, a German/European hybrid PDF+XML format used for structured B2B billing. The invoice upload workflow parses the embedded XML payload and, separately, the PDF's XMP metadata block. Both parsers used XML::LibXML->load_xml with default options, which allow external Document Type Definition (DTD) loading and general entity expansion.
An attacker who can upload an invoice supplies XML containing an external entity declaration that references a local file URI or an attacker-controlled URL. When the parser resolves the entity, the referenced content is inlined into the parsed document. Kivitendo then surfaces parser output or error messages containing the resolved data, enabling file disclosure and out-of-band exfiltration.
Root Cause
The root cause is unsafe defaults in the Perl XML::LibXML module. load_xml honors external DTDs and expands entities unless the caller explicitly disables both. Neither SL/XMLInvoice.pm nor SL/ZUGFeRD.pm passed hardening flags before the fix, leaving ZUGFeRD parsing exposed to standard XXE payloads.
Attack Vector
Exploitation requires an authenticated Kivitendo account with permission to upload or import an electronic invoice. The attacker crafts a ZUGFeRD-formatted file containing an XML external entity that points at a filesystem path such as /etc/passwd or at an attacker-controlled HTTP endpoint. Once the invoice is processed, the resolved entity contents are exposed through the application, allowing the attacker to enumerate and exfiltrate files reachable by the Kivitendo service account.
The patch applied to SL/XMLInvoice.pm and SL/ZUGFeRD.pm disables external DTD loading and entity expansion:
# Initial fix - disable external DTD loading
- $self->{dom} = eval { XML::LibXML->load_xml(string => $xml_data) };
+ $self->{dom} = eval { XML::LibXML->load_xml(string => $xml_data, load_ext_dtd => 0) };
# Hardening follow-up - also disable entity expansion
- $self->{dom} = eval { XML::LibXML->load_xml(string => $xml_data, load_ext_dtd => 0) };
+ $self->{dom} = eval { XML::LibXML->load_xml(string => $xml_data, expand_entities => 0) };
Source: Kivitendo ERP commit 1286dee7 and Kivitendo ERP commit f6ba56bd.
Detection Methods for CVE-2025-66370
Indicators of Compromise
- Uploaded ZUGFeRD or Factur-X invoices whose embedded XML contains <!DOCTYPE declarations or <!ENTITY definitions referencing file://, http://, or ftp:// URIs.
- Kivitendo application logs showing repeated invoice parsing errors that include filesystem paths or DTD retrieval attempts.
- Outbound HTTP or DNS requests from the Kivitendo server to unexpected destinations shortly after invoice imports.
Detection Strategies
- Inspect stored invoice attachments for XML doctypes and external entity declarations, since legitimate ZUGFeRD invoices do not require them.
- Correlate invoice upload events with subsequent read access to sensitive files such as /etc/passwd, kivitendo.conf, or database credential files.
- Alert on egress traffic from the Kivitendo host to non-business destinations, which can indicate out-of-band XXE exfiltration.
Monitoring Recommendations
- Enable verbose logging on the Kivitendo Perl workers to capture XML::LibXML parse failures and their input hashes.
- Forward web server, application, and network flow logs to a central platform for retention and correlation.
- Monitor filesystem audit logs (auditd) for reads of sensitive files by the Kivitendo service user.
How to Mitigate CVE-2025-66370
Immediate Actions Required
- Upgrade Kivitendo to version 3.9.2 or later on all production and staging instances.
- Restrict invoice upload permissions to a minimum set of trusted users until the patch is applied.
- Review Kivitendo audit and web logs for prior ZUGFeRD uploads containing external entity declarations.
- Rotate any credentials or secrets stored in files readable by the Kivitendo service account if evidence of exploitation is found.
Patch Information
The vendor released fixes in Kivitendo ERP 3.9.2. The remediation is implemented across two commits: 1286dee7 initially disables external DTD loading with load_ext_dtd => 0, and f6ba56bd adds expand_entities => 0 for broader XXE hardening. See the Kivitendo advisory blog post and the Kivitendo ERP changelog for release details.
Workarounds
- Temporarily disable the ZUGFeRD invoice import feature until the patch can be deployed.
- Restrict outbound network access from the Kivitendo host so external entities cannot be resolved over HTTP, HTTPS, FTP, or DNS.
- Run Kivitendo under a dedicated low-privilege service account that cannot read secrets, keys, or unrelated tenant data.
# Verify installed Kivitendo version and confirm the XXE hardening flags are present
grep -RIn "load_xml" /path/to/kivitendo-erp/SL/XMLInvoice.pm /path/to/kivitendo-erp/SL/ZUGFeRD.pm
# Expected output should include both flags:
# load_ext_dtd => 0
# expand_entities => 0
# Optional: block outbound egress from the Kivitendo host as a defense-in-depth measure
sudo iptables -A OUTPUT -m owner --uid-owner kivitendo -p tcp --dport 80 -j REJECT
sudo iptables -A OUTPUT -m owner --uid-owner kivitendo -p tcp --dport 443 -j REJECT
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.
