CVE-2026-72697
Grav CMS before 2.0.16 Path Traversal via media_directory
Grav CMS before 2.0.16 contains a path traversal vulnerability in the media_directory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to media_directory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
7.1
Grav CMS before 2.0.16 Path Traversal via media_directory
Grav CMS before 2.0.16 contains a path traversal vulnerability in the media_directory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to media_directory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
- Record
CVE-2026-72697- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-65211
7.1
Grav CMS before 2.0.16 contains a path traversal vulnerability in the media_directory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to media_directory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
- Record
EUVD-2026-65211- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-47ch-6w46-6xm7
7.1
Grav: media_directory() Twig function allows filesystem path traversal and file content disclosure from sandboxed page content
Summary
The media_directory() Twig function is allow-listed for use in sandboxed, editor-authored page content (system/config/security.yaml). Its implementation, GravExtension::mediaDirFunc(), only treats the input as unsafe when it looks like a Grav stream (user://, theme://, etc). If the input is instead a plain filesystem path, absolute or relative, the stream check is skipped entirely and the raw string is handed straight to new Media($media_dir), which lists every file in that directory whose extension matches a configured media type (which by default includes txt, json, xml, pdf, doc, docx, and more, not just images) and builds Medium objects for them.
Separately, the sandbox's own allow-list for the Medium class includes the filepath accessor. A code comment directly above that allow-list entry states the developers' intent was for filepath to be part of the "dangerous surface" that "stays blocked", but it is listed as an allowed method on the very same line, contradicting that stated intent.
Combined, a user who can enter page content that gets processed as Twig (process.twig: true in frontmatter, or any modular page, which is unsandboxed and unconditional per the code comment in processPage()) can point media_directory() at any directory the web server process can read, anywhere on the filesystem, and both enumerate and read the content of any file in it whose extension is a recognized media type.
Affected product and version
Product: Grav CMS, getgrav/grav Confirmed present in: 2.0.15, commit c2b46866857a93a0aa7048e7ed707ed3ed45dbc3
Affected code
system/src/Grav/Common/Twig/Extension/GravExtension.php, mediaDirFunc():
public function mediaDirFunc($media_dir)
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
if ($locator->isStream($media_dir)) {
$media_dir = $locator->findResource($media_dir);
}
if ($media_dir && file_exists($media_dir)) {
return new Media($media_dir);
}
return null;
}
There is no check that $media_dir, when it is not a recognized stream, is contained within any site-relative root. It is used exactly as supplied.
system/src/Grav/Common/Page/Media.php, init(), called from the constructor:
protected function init()
{
$path = $this->getPath();
// Handle special cases where page doesn't exist in filesystem.
if (!$path || !is_dir($path)) {
return;
}
...
$iterator = new FilesystemIterator($path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS);
foreach ($iterator as $file => $info) {
...
[$basename, $ext, $type, $extra] = $this->getFileParts($filename);
if (!in_array(strtolower((string) $ext), $media_types, true)) {
continue;
}
...
}
$path here is whatever was passed to the Media constructor, the raw, unvalidated string from mediaDirFunc().
system/config/security.yaml, the Medium sandbox allow-list and the comment directly above it:
# ...
# dangerous surface (save, set, copy, deleteFile, toArray, filepath, …) is
# absent from ALLOWED_ACTIONS and stays blocked.
- class: 'Grav\Common\Page\Medium\Medium'
methods: 'url, html, filepath, filename, metadata, srcset, parsedownelement, __tostring, @media_actions'
filepath is named in the comment as something that is supposed to stay blocked, and is then listed as an allowed method one line later.
media_directory in the sandbox's function allow-list:
- media_directory
Root cause
Two gaps, and the second one converts the first from "list filenames from a directory" into "read the content of files":
mediaDirFunc()'s containment check only fires for recognized Grav streams. A plain filesystem path, which is the normal, documented shape of a string, is not a stream by definition, so it always takes the unchecked path.- Once a…
- Record
GHSA-47ch-6w46-6xm7- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-rj4c-4q9x-543x
7.1
Duplicate Advisory: Grav: media_directory() Twig function allows filesystem path traversal and file content disclosure from sandboxed page content
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-47ch-6w46-6xm7. This link is maintained to preserve external references.
Original Description
Grav CMS before 2.0.16 contains a path traversal vulnerability in the media_directory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to media_directory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
- Record
GHSA-rj4c-4q9x-543x- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
NVD
7.1
Grav CMS before 2.0.16 contains a path traversal vulnerability in the media_directory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to media_directory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
- Record
CVE-2026-72697- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
7.1
Grav CMS before 2.0.16 Path Traversal via media_directory
Grav CMS before 2.0.16 contains a path traversal vulnerability in the media_directory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to media_directory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
- Record
CVE-2026-72697- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVGHSA-47ch-6w46-6xm7
7.1
Grav: media_directory() Twig function allows filesystem path traversal and file content disclosure from sandboxed page content
Summary
The media_directory() Twig function is allow-listed for use in sandboxed, editor-authored page content (system/config/security.yaml). Its implementation, GravExtension::mediaDirFunc(), only treats the input as unsafe when it looks like a Grav stream (user://, theme://, etc). If the input is instead a plain filesystem path, absolute or relative, the stream check is skipped entirely and the raw string is handed straight to new Media($media_dir), which lists every file in that directory whose extension matches a configured media type (which by default includes txt, json, xml, pdf, doc, docx, and more, not just images) and builds Medium objects for them.
Separately, the sandbox's own allow-list for the Medium class includes the filepath accessor. A code comment directly above that allow-list entry states the developers' intent was for filepath to be part of the "dangerous surface" that "stays blocked", but it is listed as an allowed method on the very same line, contradicting that stated intent.
Combined, a user who can enter page content that gets processed as Twig (process.twig: true in frontmatter, or any modular page, which is unsandboxed and unconditional per the code comment in processPage()) can point media_directory() at any directory the web server process can read, anywhere on the filesystem, and both enumerate and read the content of any file in it whose extension is a recognized media type.
Affected product and version
Product: Grav CMS, getgrav/grav Confirmed present in: 2.0.15, commit c2b46866857a93a0aa7048e7ed707ed3ed45dbc3
Affected code
system/src/Grav/Common/Twig/Extension/GravExtension.php, mediaDirFunc():
public function mediaDirFunc($media_dir)
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
if ($locator->isStream($media_dir)) {
$media_dir = $locator->findResource($media_dir);
}
if ($media_dir && file_exists($media_dir)) {
return new Media($media_dir);
}
return null;
}
There is no check that $media_dir, when it is not a recognized stream, is contained within any site-relative root. It is used exactly as supplied.
system/src/Grav/Common/Page/Media.php, init(), called from the constructor:
protected function init()
{
$path = $this->getPath();
// Handle special cases where page doesn't exist in filesystem.
if (!$path || !is_dir($path)) {
return;
}
...
$iterator = new FilesystemIterator($path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS);
foreach ($iterator as $file => $info) {
...
[$basename, $ext, $type, $extra] = $this->getFileParts($filename);
if (!in_array(strtolower((string) $ext), $media_types, true)) {
continue;
}
...
}
$path here is whatever was passed to the Media constructor, the raw, unvalidated string from mediaDirFunc().
system/config/security.yaml, the Medium sandbox allow-list and the comment directly above it:
# ...
# dangerous surface (save, set, copy, deleteFile, toArray, filepath, …) is
# absent from ALLOWED_ACTIONS and stays blocked.
- class: 'Grav\Common\Page\Medium\Medium'
methods: 'url, html, filepath, filename, metadata, srcset, parsedownelement, __tostring, @media_actions'
filepath is named in the comment as something that is supposed to stay blocked, and is then listed as an allowed method one line later.
media_directory in the sandbox's function allow-list:
- media_directory
Root cause
Two gaps, and the second one converts the first from "list filenames from a directory" into "read the content of files":
mediaDirFunc()'s containment check only fires for recognized Grav streams. A plain filesystem path, which is the normal, documented shape of a string, is not a stream by definition, so it always takes the unchecked path.- Once a…
- Record
GHSA-47ch-6w46-6xm7- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVGHSA-rj4c-4q9x-543x
7.1
Duplicate Advisory: Grav: media_directory() Twig function allows filesystem path traversal and file content disclosure from sandboxed page content
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-47ch-6w46-6xm7. This link is maintained to preserve external references.
Original Description
Grav CMS before 2.0.16 contains a path traversal vulnerability in the media_directory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to media_directory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
- Record
GHSA-rj4c-4q9x-543x- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
getgrav/gravPackagist |
through 2.0.15 | 2.0.16 |
getgrav/gravPackagist |
before 2.0.16, plus listed versions 0.8.00.9.00.9.10.9.100.9.11 Show 45 moreShow less0.9.120.9.130.9.140.9.150.9.160.9.170.9.180.9.190.9.20.9.200.9.210.9.220.9.230.9.240.9.250.9.260.9.270.9.280.9.290.9.30.9.300.9.310.9.320.9.330.9.340.9.350.9.360.9.370.9.380.9.390.9.40.9.400.9.410.9.420.9.430.9.440.9.450.9.50.9.60.9.70.9.80.9.91.0.01.0.0-rc.11.0.0-rc.2 |
2.0.16 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-72697 is a high vulnerability (CVSS 7.1) in Getgrav Grav, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.38%, the 31st percentile. Fixed in getgrav/grav 2.0.16.
References
github.com/getgrav/grav/security/advisories/GHSA-47ch-6w46-6xm7
vulncheck.com/advisories/grav-cms-before-path-traversal-via-media-directory
nvd.nist.gov/vuln/detail/CVE-2026-72697
github.com/advisories/GHSA-47ch-6w46-6xm7
github.com/advisories/GHSA-rj4c-4q9x-543x
github.com/CVEProject/cvelistV5/tree/main/cves/2026/72xxx/CVE-2026-72697.json
github.com/getgrav/grav
Left out here: 300 versions and the rest of 2 source descriptions. The JSON record has them.