GitHub Security Advisory: GHSA-c2q3-p4jr-c55f
Package: getgrav/grav (COMPOSER)
Severity: MODERATE (CVSS 5.4)
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Vulnerable versions: < 2.0.0-beta.2
Patched in: 2.0.0-beta.2

Summary: Grav Vulnerable to XSS via Taxonomy Field Values in Admin Panel

Details:
### Summary

A Stored Cross-Site Scripting (XSS) vulnerability exists in the Grav CMS Form plugin's select field template. Taxonomy tag and category values are rendered with the Twig `|raw` filter in the admin panel, bypassing the global autoescape protection. An editor-level user can inject arbitrary JavaScript that executes in any administrator's browser session when they view or edit any page in the admin panel.

Additionally, Grav's built-in XSS detection (`Security::detectXss()`) can be bypassed by using payloads that close the `<option>/<select>` context and use unquoted event handlers - the `on_events` regex fails to match event handlers without quotes or trailing spaces before `>`.

### Important

- The vulnerability is in the Form plugin (`select.html.twig`), which is installed by default with Grav
- The XSS is cross-page: a malicious taxonomy value on one page executes when an admin edits any page, because taxonomy options are rendered from a shared global pool
- An editor can exploit this without any other vulnerability - taxonomy fields are not in the server-side restricted fields list
- The `HttpOnly` flag on session cookies prevents direct session theft, but the XSS can steal the admin nonce and perform privileged actions via JavaScript

### Permissions Needed

- Editor: can create or edit pages and set taxonomy tag/category values

### Details

The Form plugin's select field template renders option values using the `|raw` Twig filter, which outputs content without HTML escaping:

File: `user/plugins/form/templates/forms/fields/select/select.html.twig`

```twig
{# Line 55 #}
 avalue|raw 

{# Line 65 #}
 suboption|t|raw 

{# Line 72 #}
 item_value|t|raw 
```

The taxonomy field in the page editor uses this select template. When a page has taxonomy values (tags, categories), these values are populated as `<option>` elements in the select dropdown. The `value` attribute is properly escaped by the browser's attribute encoding, but the **display text** between `<option>` tags is rendered raw:

```html
<option value="&lt;script&gt;alert(1)&lt;/script&gt;"><script>alert(1)</script></option>
```

Since taxonomy options are collected globally across all pages (to provide autocomplete/selection), a malicious taxonomy value on any page will appear in the taxonomy dropdown of every page editor - making this a cross-page stored XSS.

The server-side field restriction in the flex-objects plugin only blocks `['form', 'forms', 'process', 'twig']` for non-super users. Taxonomy fields are not restricted, so editors can freely set arbitrary taxonomy values.

### XSS Detection Bypass

Grav's `Security::detectXss()` checks for `dangerous_tags` (e.g., `<script>`, `<iframe>`), `on_events` (event handlers), and `invalid_protocols` (e.g., `javascript:`). However, the `on_events` regex:

```php
'on_events' => '#(<[^>]+[a-z\x00-\x20"\'\/)(?:on[a-z]+)\s*=[\s|\'"'].*[\s|\'"']>#iUu'
```

requires either quotes around the handler value or a trailing space before