getgrav/grav: Grav is Vulnerable to XXE via SVG Upload
What fixes it
- 2.0.0-beta.2getgrav/grav, composerGitHub
composer require "getgrav/grav:^2.0.0-beta.2" - 2.0.0-beta.2GravCVE List
- 2.0.0-beta.2getgrav/grav, PackagistOSV
composer require "getgrav/grav:^2.0.0-beta.2"
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
Dear Grav Security Team,
A security vulnerability was discovered in Grav CMS that allows authenticated attackers to read arbitrary files from the server through XML External Entity (XXE) injection.
Vulnerability Summary
| Field | Details | |-------|---------| | Vulnerability Type | XML External Entity (XXE) Injection | | Severity | High (CVSS 7.5) | | Affected Versions | Grav CMS <= 1.7.x | | Affected Component | SVG file upload/processing | | CWE | CWE-611: Improper Restriction of XML External Entity Reference | | Authentication Required | Yes (Admin panel access) |
Technical Details
Root Cause The application uses simplexml_load_string() to process uploaded SVG files without disabling external entity loading. This allows attackers to inject XXE payloads that are processed by the XML parser.
Vulnerable Code Pattern
// Current (Vulnerable): $svg = simplexml_load_string($content); // No LIBXML_NOENT flag or entity loader protection
Attack Vector
- Attacker authenticates to Grav admin panel
- Uploads malicious SVG file via Pages → Media or File Manager plugin
- Server parses SVG and processes XXE entities
- Arbitrary file contents are exfiltrated
Impact
An authenticated attacker can:
- Read sensitive files:
/etc/passwd- System user informationuser/accounts/*.yaml- Admin credentials and 2FA secretsuser/config/system.yaml- System configuration.envfiles - Environment secrets and API keys
- Perform SSRF - Access internal services via external entity URLs
- Potential DoS - Billion laughs attack via recursive entity expansion
Proof of Concept
Malicious SVG Payload
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> <text x="10" y="50">&xxe;</text> </svg>
Steps to Reproduce
- Login to Grav CMS admin panel
- Navigate to Pages → select any page → Media tab
- Upload the malicious SVG file
- Observe file contents in response/error or stored output
Recommended Fix
Option 1: Add XXE Protection Flags
libxml_use_internal_errors(true); $svg = simplexml_load_string($content, 'SimpleXMLElement', LIBXML_NOENT | LIBXML_DTDLOAD);
Option 2: Use SVG Sanitizer Library (Recommended)
use enshrined\svgSanitize\Sanitizer; $sanitizer = new Sanitizer(); $sanitizer->removeRemoteReferences(true); $cleanSVG = $sanitizer->sanitize($content);
The enshrined/svg-sanitize library properly strips XXE payloads and other malicious SVG content.
Request
- Please acknowledge receipt of this report within 5 business days
- Please provide an estimated timeline for a security patch
- I am happy to assist with testing the fix
- I request a CVE be assigned for this vulnerability
- If you have a security advisory process, please include me in the credits
Turki Almatrafi.
---
Maintainer note — fix applied (2026-04-24)
Fixed across two repos:
- **Grav core on the
2.0branch** (commit [5a12f9be8](https://github.com/getgrav/grav/commit/5a12f9be8), ships in 2.0.0-beta.2) —VectorImageMedium::__construct(the code path that reads width/height from an uploaded SVG) now strips<!DOCTYPE>and<!ENTITY>declarations before parsing, and callssimplexml_load_stringwithLIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING. On PHP < 8 it also callslibxml_disable_entity_loader(true)for the duration of the parse.
- rhukster/dom-sanitizer (commit [
02d08ec](https://github.com/rhukster/dom-sanitizer/commit/02d08ec)) — the library Grav ships as its SVG sanitizer.loadDocumentnow applies the same DOCTYPE/ENTITY strip and passesLIBXML_NONETtoloadXML/loadHTML.
With both layers in place, the PoC:
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> <text x="10" y="50">&xxe;</text> </svg>
no longer expands &xxe;, and the parser cannot make outbound filesystem or network requests for external entities/DTDs. Billion-laughs-style entity expansion is also neutralized because the declarations are stripped before libxml ever sees them.
Files:
- [
system/src/Grav/Common/Page/Medium/VectorImageMedium.php](https://github.com/getgrav/grav/blob/2.0/system/src/Grav/Common/Page/Medium/VectorImageMedium.php). - [
tests/unit/Grav/Common/Security/SvgXxeSecurityTest.php](https://github.com/getgrav/grav/blob/2.0/tests/unit/Grav/Common/Security/SvgXxeSecurityTest.php) — XXE neutralization + billion-laughs + plain-SVG regression. - dom-sanitizer: [
src/DOMSanitizer.php](https://github.com/rhukster/dom-sanitizer/blob/main/src/DOMSanitizer.php) + two new XXE tests in its own suite.
Text from GitHub Advisory Database
Affected versions 54
- getgrav/grav, composer< 2.0.0-beta.2GitHub
- Gravaffected: all versions before 2.0.0-beta.2CVE List
- Gravunaffected: 2.0.0-beta.2CVE List
- getgrav/grav, Packagistfrom first release, fixed in 2.0.0-beta.2OSV
- getgrav/grav, Packagist0.8.0OSV
- getgrav/grav, Packagist0.9.0OSV
- getgrav/grav, Packagist0.9.1OSV
- getgrav/grav, Packagist0.9.10OSV
- getgrav/grav, Packagist0.9.11OSV
- getgrav/grav, Packagist0.9.12OSV
- getgrav/grav, Packagist0.9.13OSV
- getgrav/grav, Packagist0.9.14OSV
- getgrav/grav, Packagist0.9.15OSV
- getgrav/grav, Packagist0.9.16OSV
- getgrav/grav, Packagist0.9.17OSV
- getgrav/grav, Packagist0.9.18OSV
- getgrav/grav, Packagist0.9.19OSV
- getgrav/grav, Packagist0.9.2OSV
- getgrav/grav, Packagist0.9.20OSV
- getgrav/grav, Packagist0.9.21OSV
- getgrav/grav, Packagist0.9.22OSV
- getgrav/grav, Packagist0.9.23OSV
- getgrav/grav, Packagist0.9.24OSV
- getgrav/grav, Packagist0.9.25OSV
- getgrav/grav, Packagist0.9.26OSV
- getgrav/grav, Packagist0.9.27OSV
- getgrav/grav, Packagist0.9.28OSV
- getgrav/grav, Packagist0.9.29OSV
- getgrav/grav, Packagist0.9.3OSV
- getgrav/grav, Packagist0.9.30OSV
- getgrav/grav, Packagist0.9.31OSV
- getgrav/grav, Packagist0.9.32OSV
- getgrav/grav, Packagist0.9.33OSV
- getgrav/grav, Packagist0.9.34OSV
- getgrav/grav, Packagist0.9.35OSV
- getgrav/grav, Packagist0.9.36OSV
- getgrav/grav, Packagist0.9.37OSV
- getgrav/grav, Packagist0.9.38OSV
- getgrav/grav, Packagist0.9.39OSV
- getgrav/grav, Packagist0.9.4OSV
- getgrav/grav, Packagist0.9.40OSV
- getgrav/grav, Packagist0.9.41OSV
- getgrav/grav, Packagist0.9.42OSV
- getgrav/grav, Packagist0.9.43OSV
- getgrav/grav, Packagist0.9.44OSV
- getgrav/grav, Packagist0.9.45OSV
- getgrav/grav, Packagist0.9.5OSV
- getgrav/grav, Packagist0.9.6OSV
- getgrav/grav, Packagist0.9.7OSV
- getgrav/grav, Packagist0.9.8OSV
- getgrav/grav, Packagist0.9.9OSV
- getgrav/grav, Packagist1.0.0OSV
- getgrav/grav, Packagist1.0.0-rc.1OSV
- getgrav/grav, Packagist1.0.0-rc.2OSV
References 5
- github.com/getgrav/grav/commit/5a12f9be8314682c8713e569e330f11805d0a663Fixvia GitHub
- vulncheck.com/advisories/grav-xml-external-entity-injection-via-svg-uploadAdvisoryvia CVE List
- github.com/advisories/GHSA-3446-6mgw-f79pAdvisoryvia GitHub
- github.com/getgrav/grav/security/advisories/GHSA-3446-6mgw-f79pExploitvia CVE List
- nvd.nist.gov/vuln/detail/CVE-2026-56701Referencevia GitHub