GHSA-3446-6mgw-f79p is a medium-severity (CVSS 6.5) XML External Entity (XXE) vulnerability in getgrav/grav. A fix is available for getgrav/grav — see the affected versions and patch details below.
Grav is Vulnerable to XXE via SVG Upload
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for GHSA-3446-6mgw-f79p.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
GHSA-3446-6mgw-f79p plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 377,166 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
getgrav/gravReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.
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 (commit5a12f9be8, 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) — 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.tests/unit/Grav/Common/Security/SvgXxeSecurityTest.php— XXE neutralization + billion-laughs + plain-SVG regression.- dom-sanitizer:
src/DOMSanitizer.php+ two new XXE tests in its own suite.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | getgrav/grav | all versions | 2.0.0-beta.2composer require getgrav/grav:^2.0.0-beta.2 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for getgrav/grav, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update getgrav/grav to 2.0.0-beta.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-3446-6mgw-f79p is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-3446-6mgw-f79p can be triaged on real exposure rather than presence alone.
Tailored to GHSA-3446-6mgw-f79p. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-3446-6mgw-f79p in your dependencies?
O3 Security finds GHSA-3446-6mgw-f79p across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.