CVE-2026-72702
Grav CMS before 2.0.16 Origin Validation Bypass via Referer
Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (str_starts_with($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.3
Grav CMS before 2.0.16 Origin Validation Bypass via Referer
Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (str_starts_with($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
- Record
CVE-2026-72702- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-346
- Updated
- 2026-11-02
EUVDEUVD-2026-65216
9.3
Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (str_starts_with($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
- Record
EUVD-2026-65216- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-qh7h-6c7g-x8m6
9.3
Duplicate Advisory: Grav: Origin validation bypass in Uri::referrer() and Pages::referrerRoute() via unanchored prefix match
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-9ccq-2jfg-qw33. This link is maintained to preserve external references.
Original Description
Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (str_starts_with($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
- Record
GHSA-qh7h-6c7g-x8m6- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/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
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-346
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9ccq-2jfg-qw33 No score
Grav: Origin validation bypass in Uri::referrer() and Pages::referrerRoute() via unanchored prefix match
Summary
Grav\Common\Uri::referrer() and Grav\Common\Page\Pages::referrerRoute() both check whether an incoming request's Referer header "came from our site" using str_starts_with($referrer, $base), where $base is the site's own absolute root URL (for example https://example.com, no trailing slash). Because the comparison has no boundary character after the prefix, any Referer value that merely starts with that string is accepted, including a Referer from a completely different host such as https://example.com.attacker.tld.
This is the same class of bug already fixed once in 2.0.15 for the fast static asset server (GHSA-4v9q-p283-qc2m, "also allowing any neighbouring directory whose name starts with the same letters"). The identical pattern is still present in both places that trust the Referer header, and neither is covered by that fix.
Affected product and version
Product: Grav CMS, getgrav/grav Confirmed present in: 2.0.15, commit c2b46866857a93a0aa7048e7ed707ed3ed45dbc3 The pattern is not touched by any of the 2.0.15 security fixes, so earlier 2.x releases are likely affected too. I have not checked how far back it goes.
Affected code
system/src/Grav/Common/Uri.php, method referrer():
$referrer = $_SERVER['HTTP_REFERER'] ?? null;
...
$base = $this->rootUrl(true); // e.g. "https://example.com", no trailing slash
// Referrer should always have host set and it should come from the same base address.
if (!is_string($referrer) || !str_starts_with($referrer, $base)) {
$referrer = $default ?: $this->route(true, true);
}
$referrer = substr($referrer, strlen($base));
system/src/Grav/Common/Page/Pages.php, method referrerRoute():
$referrer = $_SERVER['HTTP_REFERER'] ?? null;
$root = $this->grav['base_url_absolute']; // e.g. "https://example.com"
if (!is_string($referrer) || !str_starts_with($referrer, (string) $root)) {
return null;
}
Note that the inner per-language loop later in the same referrerRoute() method does anchor the check correctly (str_starts_with($referrer, "{$base}/")), and system/src/Grav/Common/Themes.php line 300 does the same thing correctly ($current === $base || str_starts_with($current, $base . '/')). So the codebase already has the correct pattern elsewhere. Only the two outer checks quoted above compare against the bare root URL with no trailing delimiter.
Root cause
str_starts_with($referrer, $base) treats $base as a plain string prefix. Since $base has no trailing /, a string is accepted as long as it begins with those exact characters, regardless of what character follows. An attacker fully controls their own domain name, so producing a string that begins with the victim's origin is trivial, for example by registering example.com.attacker.tld or example.com-attacker.tld.
Under the default browser Referrer Policy (strict-origin-when-cross-origin), a cross-origin click or form submission from the attacker's page sends only the origin (scheme://host) as Referer, which is exactly the granularity $base is compared at, so no unusual browser configuration is required.
Proof of concept, verified, real output
This was run directly against the actual, unmodified source file from the repository, not a reimplementation. Steps and exact output below.
Step 1, clone the repo and confirm the commit under test:
$ git clone --depth 1 https://github.com/getgrav/grav.git
$ cd grav && git log -1 --format="%H %ai"
c2b46866857a93a0aa7048e7ed707ed3ed45dbc3 2026-08-03 15:14:50 +0100
Step 2, install PHP to execute the real class:
$ apt-get install -y php-cli
$ php -v
PHP 8.3.6 (cli) (built: Jul 16 2026 18:30:41) (NTS)
Step 3, PoC harness. Full site bootstrap, composer install, database, config, is not required to demonstrate this specific bug, since referrer() only needs the $root property, which init() would normally compute from the site config. The harness sets that one property with PHP…
- Record
GHSA-9ccq-2jfg-qw33- Weakness
- CWE-346
- Updated
- 2026-11-02
NVD
9.3
Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (str_starts_with($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
- Record
CVE-2026-72702- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/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
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-346
- Updated
- 2026-11-02
OSV
9.3
Grav CMS before 2.0.16 Origin Validation Bypass via Referer
Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (str_starts_with($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
- Record
CVE-2026-72702- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-346
- Updated
- 2026-11-02
OSVGHSA-qh7h-6c7g-x8m6
9.3
Duplicate Advisory: Grav: Origin validation bypass in Uri::referrer() and Pages::referrerRoute() via unanchored prefix match
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-9ccq-2jfg-qw33. This link is maintained to preserve external references.
Original Description
Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (str_starts_with($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
- Record
GHSA-qh7h-6c7g-x8m6- CVSS 4.0
- 9.3 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/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
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-346
- Updated
- 2026-11-02
OSVGHSA-9ccq-2jfg-qw33 No score
Grav: Origin validation bypass in Uri::referrer() and Pages::referrerRoute() via unanchored prefix match
Summary
Grav\Common\Uri::referrer() and Grav\Common\Page\Pages::referrerRoute() both check whether an incoming request's Referer header "came from our site" using str_starts_with($referrer, $base), where $base is the site's own absolute root URL (for example https://example.com, no trailing slash). Because the comparison has no boundary character after the prefix, any Referer value that merely starts with that string is accepted, including a Referer from a completely different host such as https://example.com.attacker.tld.
This is the same class of bug already fixed once in 2.0.15 for the fast static asset server (GHSA-4v9q-p283-qc2m, "also allowing any neighbouring directory whose name starts with the same letters"). The identical pattern is still present in both places that trust the Referer header, and neither is covered by that fix.
Affected product and version
Product: Grav CMS, getgrav/grav Confirmed present in: 2.0.15, commit c2b46866857a93a0aa7048e7ed707ed3ed45dbc3 The pattern is not touched by any of the 2.0.15 security fixes, so earlier 2.x releases are likely affected too. I have not checked how far back it goes.
Affected code
system/src/Grav/Common/Uri.php, method referrer():
$referrer = $_SERVER['HTTP_REFERER'] ?? null;
...
$base = $this->rootUrl(true); // e.g. "https://example.com", no trailing slash
// Referrer should always have host set and it should come from the same base address.
if (!is_string($referrer) || !str_starts_with($referrer, $base)) {
$referrer = $default ?: $this->route(true, true);
}
$referrer = substr($referrer, strlen($base));
system/src/Grav/Common/Page/Pages.php, method referrerRoute():
$referrer = $_SERVER['HTTP_REFERER'] ?? null;
$root = $this->grav['base_url_absolute']; // e.g. "https://example.com"
if (!is_string($referrer) || !str_starts_with($referrer, (string) $root)) {
return null;
}
Note that the inner per-language loop later in the same referrerRoute() method does anchor the check correctly (str_starts_with($referrer, "{$base}/")), and system/src/Grav/Common/Themes.php line 300 does the same thing correctly ($current === $base || str_starts_with($current, $base . '/')). So the codebase already has the correct pattern elsewhere. Only the two outer checks quoted above compare against the bare root URL with no trailing delimiter.
Root cause
str_starts_with($referrer, $base) treats $base as a plain string prefix. Since $base has no trailing /, a string is accepted as long as it begins with those exact characters, regardless of what character follows. An attacker fully controls their own domain name, so producing a string that begins with the victim's origin is trivial, for example by registering example.com.attacker.tld or example.com-attacker.tld.
Under the default browser Referrer Policy (strict-origin-when-cross-origin), a cross-origin click or form submission from the attacker's page sends only the origin (scheme://host) as Referer, which is exactly the granularity $base is compared at, so no unusual browser configuration is required.
Proof of concept, verified, real output
This was run directly against the actual, unmodified source file from the repository, not a reimplementation. Steps and exact output below.
Step 1, clone the repo and confirm the commit under test:
$ git clone --depth 1 https://github.com/getgrav/grav.git
$ cd grav && git log -1 --format="%H %ai"
c2b46866857a93a0aa7048e7ed707ed3ed45dbc3 2026-08-03 15:14:50 +0100
Step 2, install PHP to execute the real class:
$ apt-get install -y php-cli
$ php -v
PHP 8.3.6 (cli) (built: Jul 16 2026 18:30:41) (NTS)
Step 3, PoC harness. Full site bootstrap, composer install, database, config, is not required to demonstrate this specific bug, since referrer() only needs the $root property, which init() would normally compute from the site config. The harness sets that one property with PHP…
- Record
GHSA-9ccq-2jfg-qw33- Weakness
- CWE-346
- 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-72702 is a critical vulnerability (CVSS 9.3) 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.15%, the 4th percentile. Fixed in getgrav/grav 2.0.16.
References
github.com/getgrav/grav/security/advisories/GHSA-9ccq-2jfg-qw33
vulncheck.com/advisories/grav-cms-before-origin-validation-bypass-via-referer
nvd.nist.gov/vuln/detail/CVE-2026-72702
github.com/advisories/GHSA-9ccq-2jfg-qw33
github.com/advisories/GHSA-qh7h-6c7g-x8m6
github.com/CVEProject/cvelistV5/tree/main/cves/2026/72xxx/CVE-2026-72702.json
github.com/getgrav/grav
Left out here: 300 versions and the rest of 2 source descriptions. The JSON record has them.