Sep 18, 2026·4 min read·26 visits
A parser-differential flaw in Grav CMS's detectXss() filter allows authenticated editors to bypass event-handler detection using unpaired quotes in unquoted attribute values, resulting in stored cross-site scripting.
CVE-2026-75828 is a critical stored cross-site scripting (XSS) vulnerability in the getgrav Grav CMS before version 2.0.15. The vulnerability resides in the detectXss() security filter mechanism, where parser-differential mismatches between the regular-expression-based server-side validation and browser HTML5 tokenization allow authenticated editors to bypass event-handler detection and inject arbitrary JavaScript execution vectors.
Grav CMS is a flat-file content management system that processes content via Markdown and HTML rendering. To ensure safety, Grav employs a centralized security class containing a detectXss() method designed to intercept malicious scripts, event handlers, and unauthorized XML namespaces.
The attack surface of Grav CMS includes markdown-enabled input fields, pages, and metadata accessible to authenticated administrative users and editors. In certain configurations, plugins may expose this filter to unauthenticated inputs, widening the risk vector.
The core of the vulnerability is a parser-differential bug classified under CWE-79. The input validation filter relies on regular expressions that fail to accurately predict the state-machine logic used by modern web browsers when encountering malformed HTML.
The root cause lies in how Security::detectXss() tracks quote pairs. The filter uses regex patterns to identify inline event handlers (such as onerror=) while attempting to ignore harmless occurrences of these sequences within valid quoted attribute strings.
In HTML5, a quote character only starts a quoted attribute state if it immediately follows an equals sign (e.g., attr="value"). An unanchored quote inside an unquoted attribute string (such as the double quote in src=x") is parsed by browsers as a literal character rather than a string delimiter. The browser remains in the unquoted attribute state, and subsequent characters like space and onerror= are parsed as new, independent attributes.
In contrast, the PHP regex parser used in detectXss() treats any quote character as the start of an enclosed string literal. Upon encountering the unpaired quote inside src=x", the engine searches for a matching closing quote, consuming the rest of the element, including the real malicious event-handler attribute. Because the handler is matched inside what the regex assumes is a benign string, validation succeeds.
The vulnerable regular expression configuration in system/src/Grav/Common/Security.php used a generic matching sequence for quotes: (?:"[^"]"|'[^']'). This pattern matched quotes globally without checking if they were actually part of an attribute assignment.
Below is the code snippet comparing the vulnerable and patched regex patterns:
// Vulnerable configuration
'on_events' => '#<(?:"[^"]*"|\'[^']*\'|[^>"\'])*?(?:[\s\x00-\x20\"\u0027\/]|"[^"]*"|\'[^']*\')on\s*[a-z]+\s*=#iu',
// Patched configuration in 2.0.15
'on_events' => '#<(?:=\s*"[^"]*"|=\s*\'[^']*\'|[^>])*?(?:[\s\x00-\x20\"\u0027\/]|=\s*"[^"]*"|=\s*\'[^']*\')on\s*[a-z]+\s*=#iu',The updated regex binds the quote character to an assignment pattern (=\s*) before allowing the parser to consume the content as a string literal. If the quote is not preceded by an equals sign, the parser falls back to matching it as an ordinary tag-body character, forcing the scanner to process the subsequent onerror event handler correctly.
An authenticated editor with permissions to modify site markdown files can insert the malformed payload through the administration panel. Once saved, the raw string is written directly to the flat-file database.
The parser divergence can be visualized in the following diagram:
The payload <img src=x" onerror="alert(document.domain)"> cleanly bypasses the regular expression filter while remaining completely syntactically functional in Chromium-based and Firefox browsers, resulting in arbitrary JavaScript execution when the page is viewed.
The impact of stored cross-site scripting in a CMS environment like Grav is significant. An attacker can hijack the sessions of administrative users who visit the compromised pages, leading to complete control over the application.
Session hijacking allows attackers to execute administrative actions, such as adding backdoor accounts, installing malicious plugins, or reading confidential environment configurations.
Given the flat-file nature of Grav, access to the administration panel translates directly to remote code execution (RCE) on the underlying server. Since administrators can write templates or execute PHP files through standard features, XSS represents a gateway to complete host compromise.
Security administrators must update Grav installations to version 2.0.15 immediately. If an automatic update via the administration console is not possible, the command-line package manager can be used.
For legacy installations where upgrading the entire CMS is infeasible, the patches to system/src/Grav/Common/Security.php should be applied manually to anchor all quote validation steps to the = operator.
Temporary protections can be configured on web application firewalls (WAF) to intercept requests containing unquoted attributes containing internal quotes followed by script handles. An example detection signature has been verified for application-level proxies.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
grav getgrav | < 2.0.15 | 2.0.15 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Score | 9.3 (Critical) |
| EPSS Score | 0.00343 |
| Impact | Stored Cross-Site Scripting (XSS) leading to Session Hijacking / RCE |
| Exploit Status | Proof of Concept |
| KEV Status | Not Listed |
The application does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.