GHSA-9695-8FR9-HW5Q

Vulnerability from github – Published: 2026-05-05 21:27 – Updated: 2026-05-13 13:52
VLAI
Summary
Grav Vulnerable to Publisher-Level Stored XSS via Unquoted Event Attributes
Details

Summary

A stored Cross-Site Scripting (XSS) vulnerability in getgrav/grav allows publisher-level accounts to execute arbitrary JavaScript. The issue arises from a blacklist bypass in the detectXss() function when handling unquoted HTML event attributes.

Details

The detectXss() function relies on a blacklist pattern to filter malicious attributes. The specific regex pattern used to match on* events is flawed:

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

This pattern fails to properly identify on* event handlers that are constructed without quotation marks. This allows an attacker to completely bypass the filter. Note: It is highly recommended to replace this blacklist approach with a robust, established HTML sanitization library.

PoC

An attacker with publisher-level access can reproduce this by injecting the following payload into any vulnerable content field:

<img src=x onerror=eval(atob(/YWxlcnQoZG9jdW1lbnQuY29va2llKQ/.source))>

image1 image2 image3

Execution Details: The onerror event is written without quotes to bypass the regex. Because unquoted attributes are restricted in their character usage (e.g., the = symbol cannot be used easily), the payload leverages atob() and regex .source to decode the base64 string YWxlcnQoZG9jdW1lbnQuY29va2llKQ (which translates to alert(document.cookie)). The atob() function conveniently auto-completes the necessary = padding for the base64 string.

Impact

  • Vulnerability Type: Stored Cross-Site Scripting (XSS)
  • Impacted Parties: Any user (including administrators) who views the compromised content published by the attacker.
  • Consequences: Attackers can execute malicious scripts in a victim's browser, leading to session hijacking (cookie theft), unauthorized actions.

Maintainer note — fix applied (2026-04-24)

Fixed in Grav core on the 2.0 branch: commit 5a12f9be8 — will ship in 2.0.0-beta.2.

What changed: the on_events regex in Security::detectXss() no longer requires quotes or whitespace around =. The previous form:

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

required [\s|'"] immediately after the =, so <img src=x onerror=alert(1)> slid past. The new regex drops the value-matching tail entirely and just flags the presence of an on*= attribute anywhere inside a tag:

'on_events' => '#<[^>]*?[\s\x00-\x20\"\'\/](on\s*[a-z]+|xmlns)\s*=#iu'

Detecting the attribute name + = is enough for a tripwire — the trade-off is occasional false positives on legitimate attribute values containing on*= substrings, which the maintainer can hand-approve.

This same regex bypass was the detection-layer half of GHSA-c2q3-p4jr-c55f and GHSA-w8cg-7jcj-4vv2; the fix here knocks both down.

Files: - system/src/Grav/Common/Security.php. - tests/unit/Grav/Common/Security/DetectXssTest.php — 18 cases: unquoted PoCs, quoted-form regression, safe-content negatives.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getgrav/grav"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.0.0-beta.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42612"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-05T21:27:15Z",
    "nvd_published_at": "2026-05-11T16:17:34Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nA stored Cross-Site Scripting (XSS) vulnerability in `getgrav/grav` allows publisher-level accounts to execute arbitrary JavaScript. The issue arises from a blacklist bypass in the `detectXss()` function when handling unquoted HTML event attributes.\n\n### Details\nThe `detectXss()` function relies on a blacklist pattern to filter malicious attributes. The specific regex pattern used to match `on*` events is flawed:\n```php\n\u0027on_events\u0027 =\u003e \u0027#(\u003c[^\u003e]+[a-z\\x00-\\x20\\\"\\\u0027\\/])(on[a-z]+|xmlns)\\s*=[\\s|\\\u0027\\\"].*[\\s|\\\u0027\\\"]\u003e#iUu\u0027\n```\nThis pattern fails to properly identify `on*` event handlers that are constructed without quotation marks. This allows an attacker to completely bypass the filter. *Note: It is highly recommended to replace this blacklist approach with a robust, established HTML sanitization library.*\n\n### PoC\nAn attacker with publisher-level access can reproduce this by injecting the following payload into any vulnerable content field:\n```html\n\u003cimg src=x onerror=eval(atob(/YWxlcnQoZG9jdW1lbnQuY29va2llKQ/.source))\u003e\n```\n\u003cimg width=\"1889\" height=\"482\" alt=\"image1\" src=\"https://github.com/user-attachments/assets/0f1a339b-25a8-4b6e-91af-8c59e6a39297\" /\u003e\n\u003cimg width=\"3055\" height=\"920\" alt=\"image2\" src=\"https://github.com/user-attachments/assets/12680058-bbb3-4446-b58e-515533bb4e90\" /\u003e\n\u003cimg width=\"2909\" height=\"1339\" alt=\"image3\" src=\"https://github.com/user-attachments/assets/c7ed7e61-8dcf-402d-8589-98d18978c71a\" /\u003e\n\n\n**Execution Details:**\nThe `onerror` event is written without quotes to bypass the regex. Because unquoted attributes are restricted in their character usage (e.g., the `=` symbol cannot be used easily), the payload leverages `atob()` and regex `.source` to decode the base64 string `YWxlcnQoZG9jdW1lbnQuY29va2llKQ` (which translates to `alert(document.cookie)`). The `atob()` function conveniently auto-completes the necessary `=` padding for the base64 string.\n\n### Impact\n- **Vulnerability Type:** Stored Cross-Site Scripting (XSS)\n- **Impacted Parties:** Any user (including administrators) who views the compromised content published by the attacker.\n- **Consequences:** Attackers can execute malicious scripts in a victim\u0027s browser, leading to session hijacking (cookie theft), unauthorized actions.\n\n\n---\n\n## Maintainer note \u2014 fix applied (2026-04-24)\n\nFixed in Grav core on the `2.0` branch: commit [`5a12f9be8`](https://github.com/getgrav/grav/commit/5a12f9be8) \u2014 will ship in **2.0.0-beta.2**.\n\n**What changed:** the `on_events` regex in `Security::detectXss()` no longer requires quotes or whitespace around `=`. The previous form:\n\n```\n\u0027on_events\u0027 =\u003e \u0027#(\u003c[^\u003e]+[\\s\\x00-\\x20\\\"\\\u0027\\/])(on\\s*[a-z]+|xmlns)\\s*=[\\s|\\\u0027\\\"].*[\\s|\\\u0027\\\"]\u003e#iUu\u0027\n```\n\nrequired `[\\s|\u0027\"]` immediately after the `=`, so `\u003cimg src=x onerror=alert(1)\u003e` slid past. The new regex drops the value-matching tail entirely and just flags the presence of an `on*=` attribute anywhere inside a tag:\n\n```\n\u0027on_events\u0027 =\u003e \u0027#\u003c[^\u003e]*?[\\s\\x00-\\x20\\\"\\\u0027\\/](on\\s*[a-z]+|xmlns)\\s*=#iu\u0027\n```\n\nDetecting the attribute name + `=` is enough for a tripwire \u2014 the trade-off is occasional false positives on legitimate attribute *values* containing `on*=` substrings, which the maintainer can hand-approve.\n\nThis same regex bypass was the detection-layer half of GHSA-c2q3-p4jr-c55f and GHSA-w8cg-7jcj-4vv2; the fix here knocks both down.\n\n**Files:**\n- [`system/src/Grav/Common/Security.php`](https://github.com/getgrav/grav/blob/2.0/system/src/Grav/Common/Security.php).\n- [`tests/unit/Grav/Common/Security/DetectXssTest.php`](https://github.com/getgrav/grav/blob/2.0/tests/unit/Grav/Common/Security/DetectXssTest.php) \u2014 18 cases: unquoted PoCs, quoted-form regression, safe-content negatives.",
  "id": "GHSA-9695-8fr9-hw5q",
  "modified": "2026-05-13T13:52:24Z",
  "published": "2026-05-05T21:27:15Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/getgrav/grav/security/advisories/GHSA-9695-8fr9-hw5q"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42612"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getgrav/grav/commit/5a12f9be8314682c8713e569e330f11805d0a663"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/getgrav/grav"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Grav Vulnerable to Publisher-Level Stored XSS via Unquoted Event Attributes"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…