Skip to main content

league/commonmark CVE-2026-71478

| EUVDEUVD-2026-54261 MEDIUM
Cross-site Scripting (XSS) (CWE-79)
2026-08-06 https://github.com/thephpleague/commonmark GHSA-29pj-957v-52mc
6.1
CVSS 3.1 · Vendor: https://github.com/thephpleague/commonmark
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Recon
    Attacker submits Markdown with tab-split javascript: href
  2. Delivery
    AttributesExtension parses raw control byte into attribute value
  3. Exploit
    REGEX_UNSAFE_PROTOCOL regex fails to match obfuscated scheme
  4. Install
    Malicious href emitted verbatim into rendered HTML
  5. C2
    Victim clicks rendered link
  6. Execute
    Browser strips control bytes per WHATWG URL parser
  7. Impact
    javascript: payload executes in victim session

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A05:2025 - Injection
ASVS 5.0
V1 - Encoding and Sanitization Output encoding, context-aware escaping
MITRE ATT&CK

2 techniques across 2 tactics: Initial Access, Execution

The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up.

Exploitation Conditions

Three conditions must align for exploitation: first, the application must have explicitly loaded the AttributesExtension into its CommonMark environment - it is not active by default and requires a deliberate addExtension() call; second, the application must accept Markdown input from untrusted users and render the HTML output to other users, creating a stored or reflected XSS delivery path; third, a victim user must click the attacker-controlled link in a browser (UI:R per CVSS), as the javascript: scheme only executes on navigation. The allow_unsafe_links => false setting does NOT prevent exploitation - the bypass specifically defeats this protection. The attacker must also control the full Markdown document to supply a core link destination that the core parser judges unsafe (e.g., javascript:alert(0)), which prevents LinkRenderer from overwriting the attribute-supplied href; this is no practical obstacle since the attacker writes the entire document. What genuinely limits exploitation: the AttributesExtension must be intentionally enabled; victim interaction is required (no clickless execution path); and only href and src attributes on link elements are exploitable (href on block elements like p is inert, and ImageRenderer unconditionally overwrites src from the core URL regardless of safety verdict). Authentication at the application level is not required for Markdown submission - consistent with PR:N in the CVSS vector.

Impact

The CVSS 3.1 score of 6.1 with vector AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N appropriately captures the key risk dimensions: network-reachable, low-complexity, unauthenticated submission, with a scope change affecting the victim's browser and limited confidentiality/integrity impact consistent with XSS outcomes such as cookie theft and session manipulation. No EPSS score was provided in the source intelligence, and no CISA KEV listing was identified, so exploitation probability and active exploitation status are unconfirmed at time of analysis. However, the published advisory includes explicit PoC payloads with exact byte sequences and a step-by-step attack scenario, which substantially reduces attacker effort and narrows the gap between theoretical and practical exploitation. The most significant risk amplifier is that the bypass specifically defeats allow_unsafe_links => false - the configuration recommended by the library's own documentation for untrusted input. Applications that followed the library's hardening guidance remain fully vulnerable, a condition that increases realistic risk beyond what a raw CVSS score suggests. The moderate CVSS score reflects XSS rather than RCE, but for multi-user Markdown platforms (wikis, issue trackers, comment systems), stored XSS enabling cookie theft and account takeover is operationally significant. No conflicting signals between data sources were identified.

Exploit ScenarioDerived

An attacker with the ability to submit Markdown to a platform (wiki, issue tracker, comment system) using the AttributesExtension crafts a payload such as [Click me](javascript:alert(0)){href="java\tscript:alert(document.cookie)"} where \t is a literal 0x09 TAB byte. The library's filter does not detect the obfuscated scheme and emits the raw href into the HTML output. When a victim user views the page and clicks the rendered link, their browser silently strips the embedded TAB per the WHATWG URL Standard and executes javascript:alert(document.cookie) in the victim's session, enabling cookie exfiltration and potential account takeover. The advisory provides explicit PoC payloads confirming reproducibility, so no additional exploit development is required beyond embedding control bytes that any text editor or script can produce.

This Month Plan within 30 days. Why: MEDIUM severity (CVSS 4.0-6.9).
Patch available 0.2% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Aug 06, 2026

Stored XSS in league/commonmark's AttributesExtension allows remote attackers to inject and execute arbitrary JavaScript in victim browsers by embedding WHATWG URL control bytes (TAB, CR, LF, or leading C0 controls) within a javascript: scheme in href or src attribute values. The unsafe-link filter (RegexHelper::isLinkPotentiallyUnsafe()) uses an anchored prefix regex that does not normalize inputs as browsers do, so java<TAB>script: passes the filter but executes as javascript: in every compliant browser. Critically, this bypass defeats the allow_unsafe_links => false configuration - the library's own documented hardening recommendation for untrusted input - meaning even security-conscious deployments running versions 1.5.0 through 2.8.3 remain exploitable. No public exploit confirmed as KEV; however, the advisory provides explicit PoC payloads, substantially lowering attacker effort.

RemediationAI

Upgrade to league/commonmark version 2.9.0, confirmed as the fixed release (https://github.com/thephpleague/commonmark/releases/tag/2.9.0). The fix (commit 493a5aa7d65754b73846006eaff9c2c4431a8e2c) adds WHATWG-aligned normalization inside RegexHelper::isLinkPotentiallyUnsafe() - stripping tab, CR, and LF from anywhere in the URL string via str_replace, then left-trimming all C0 control characters and spaces via ltrim, before applying the regex check. This is a targeted one-line normalization that covers all call sites (LinkRenderer, ImageRenderer, and third-party callers) without requiring changes in the Attributes extension itself. For applications that cannot immediately upgrade, the most reliable compensating control is to disable or not register the AttributesExtension when processing untrusted Markdown; this eliminates the attack surface entirely but removes attribute-injection functionality. Alternatively, sanitize all Markdown attribute values before passing input to the library by stripping or percent-encoding bytes in the range 0x00-0x1F from href and src values; this is difficult to implement correctly and should be treated as a temporary measure only. Applying an HTML output sanitizer (e.g., HTML Purifier) as a post-processing step would also block the malicious href from reaching browsers, at the cost of additional processing overhead.

Affected VersionsENISA

commonmark 1.5.0, ≤ 2.8.3

Affected Packages

composer league/commonmark fix: 2.9.0
euvd commonmark_1_5_0 no fix

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 2 packages in composer, euvd

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

league/commonmark (composer package league/commonmark, pkg:composer/league_commonmark) is a widely-used PHP Markdown-to-HTML conversion library maintained by The PHP League. The AttributesExtension, available since v1.5.0, allows Markdown authors to inject HTML attributes into rendered elements via syntax like {href='...'}. The vulnerability root cause spans CWE-79, CWE-692, and CWE-86: the REGEX_UNSAFE_PROTOCOL constant ('/^(?:javascript|vbscript|file|data):/i' in src/Util/RegexHelper.php:69) performs an anchored prefix match without first applying WHATWG URL normalization. The WHATWG URL Standard's basic URL parser (step 3) strips all ASCII tab and newline bytes anywhere in the URL, and (step 1) strips leading/trailing C0 control characters before resolving a scheme - meaning java<TAB>script: and <0x01>javascript: both resolve to javascript: in every compliant browser. The Attributes extension's attribute-value grammar (PARTIAL_DOUBLEQUOTEDVALUE = '"[^"]*"') accepts any byte except a double-quote, and parseAttributes() only trims the default PHP charlist (' \t\n\r\0\x0B'), leaving 0x01 and similar C0 bytes intact. The core Markdown link path (LinkParserHelper → UrlEncoder::unescapeAndEncode()) already percent-encodes all control bytes before the safety check, closing this gap for normal links - but the Attributes extension's href/src handling has no equivalent normalization, so raw control bytes survive into both the filter check and the final HTML output (Xml::escape() only escapes &, <, >, ", ' and does not strip control bytes that are legal inside HTML attributes).

Know when PHP gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/thephpleague/commonmark) PRIMARY 6.1 MEDIUM
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
vuln.today AI 6.1 MEDIUM

Network-submitted Markdown with no auth required (AV:N, PR:N); victim must click link (UI:R); XSS executes in browser cross-site scope (S:C); impact limited to cookie/session theft, no availability loss.

3.1 AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
4.0 AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N

Primary rating from Vendor (https://github.com/thephpleague/commonmark).

CVSS VectorVendor: https://github.com/thephpleague/commonmark

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Confidentiality
Low
Integrity
Low
Availability
None

DescriptionCVE.org



Summary

The AttributesExtension's href/src unsafe-link filter (AttributesHelper::filterAttributes()) can be bypassed by embedding control bytes in a javascript: URL that browsers discard before parsing the scheme. Two variants:

  • Tab/newline inside the scheme - a literal ASCII TAB (0x09), CR (0x0D), or LF (0x0A), e.g. java<TAB>script:alert(1). Per the WHATWG URL Standard's "basic URL parser" step 3, browsers "remove all ASCII tab or newline from input".
  • Leading C0 controls - e.g. <0x01>javascript:alert(1). Per step 1 of the same algorithm, browsers remove any leading or trailing C0 control or space. (A leading *space* alone does not bypass, because parseAttributes() already trim()s the value; other C0 bytes are not trimmed.)

The filter is a literal anchored-prefix regex (RegexHelper::isLinkPotentiallyUnsafe() / REGEX_UNSAFE_PROTOCOL) that matches neither obfuscated form, so in both cases the browser still executes javascript:alert(1).

This is confirmed reproducible even with allow_unsafe_links => false set - i.e. even applications that have followed the library's own documented hardening guidance for untrusted input remain exploitable.

This is a *sibling gap* in the same defense that CVE-2025-46734 (GHSA-3527-qv2q-pfvx) fixed in v2.7.0 - that fix made href/src respect allow_unsafe_links, but did not normalize control bytes before checking, so these obfuscation techniques were never covered.

Vulnerability

Files:

  • src/Util/RegexHelper.php:69 (REGEX_UNSAFE_PROTOCOL), :239-242 (isLinkPotentiallyUnsafe())
  • src/Extension/Attributes/Util/AttributesHelper.php:149-179 (filterAttributes())

CWE: CWE-79 (Improper Neutralization of Input During Web Page Generation / XSS) - primary

  • CWE-692 (Incomplete Denylist to Cross-Site Scripting) - the anchored-prefix denylist in REGEX_UNSAFE_PROTOCOL is incomplete. This is a composite of CWE-184 and CWE-79, so it captures the full "incomplete denylist → XSS" chain on its own.
  • CWE-86 (Improper Neutralization of Invalid Characters in Identifiers in Web Pages) - the specific evasion technique: control bytes embedded within the URI scheme identifier, which the browser strips before resolving it.

Root Cause

php
// src/Util/RegexHelper.php
public const REGEX_UNSAFE_PROTOCOL = '/^(?:javascript|vbscript|file|data):/i';

public static function isLinkPotentiallyUnsafe(string $url): bool
{
    return \preg_match(self::REGEX_UNSAFE_PROTOCOL, $url) !== 0 && \preg_match(self::REGEX_SAFE_DATA_PROTOCOL, $url) === 0;
}

// src/Extension/Attributes/Util/AttributesHelper.php
foreach ($attributes as $name => $value) {
    $attrNameLower = \strtolower($name);
    if (! $allowUnsafeLinks && ($attrNameLower === 'href' || $attrNameLower === 'src') && \is_string($value) && RegexHelper::isLinkPotentiallyUnsafe($value)) {
        unset($attributes[$name]);
        continue;
    }
    ...

The Attributes extension's own quote-value grammar (PARTIAL_DOUBLEQUOTEDVALUE = '"[^"]*"') accepts any byte except " inside quotes, including raw tab/CR/LF and other C0 controls, and parseAttributes() only trim()s (leading/trailing, and only the default charlist " \t\n\r\0\x0B" - so a leading \x01 survives). Critically, the core Markdown link-destination path (LinkParserHelper → UrlEncoder::unescapeAndEncode()) percent-encodes every control byte before this same safety check ever runs - but the Attributes extension's href/src handling has no equivalent normalization step, so the raw control byte reaches both the check and the final HTML output (Xml::escape() only escapes & < > " ', not tab/CR/LF, since they're legal bytes inside an HTML attribute).

Attack Scenario

  1. An application enables the (commonly-used) AttributesExtension and sets allow_unsafe_links => false - the project's own documented hardening step for untrusted input.
  2. An attacker submits Markdown: [Click me](javascript:alert(0)){href="java<TAB>script:alert(document.cookie)"} (TAB is one literal 0x09 byte).
  3. The library emits <a href="java<TAB>script:alert(document.cookie)">Click me</a> - isLinkPotentiallyUnsafe() doesn't match the tab-split scheme, so the filter takes no action.
  4. A victim viewing/clicking the link has the browser strip the embedded TAB and execute javascript:alert(document.cookie) in the victim's session - stored XSS, cookie theft, account takeover potential.

Why the payload needs an unsafe core destination. Step 2 above deliberately uses [Click me](javascript:alert(0)) rather than a normal link. LinkRenderer overwrites attrs['href'] with the node's own URL *unless* that URL is itself judged unsafe - so x{href="java<TAB>script:..."} renders the harmless href="https://example.com", and an empty destination [x](){href="..."} renders href="". The attacker therefore supplies a core destination that the filter *does* catch, which suppresses the overwrite and lets the attribute-supplied href reach the final tag. This is no obstacle in practice - the attacker writes the entire Markdown document.

Two related forms that are not exploitable, noted so the fix isn't over-scoped:

  • Attaching the attribute to a non-link block - hi {href="java<TAB>script:alert(1)"} - does bypass the filter and emits <p href="java<TAB>script:alert(1)">, but href on a <p> is inert: there is nothing to navigate. (An earlier draft of this report described this as a "simpler, unconditional variant" of the attack; it is a filter bypass, not an XSS.)
  • <img src> is unaffected, since ImageRenderer unconditionally overwrites src from the core URL regardless of the safety verdict.

Recommended Fix

Normalize inside RegexHelper::isLinkPotentiallyUnsafe() before testing, mirroring the WHATWG URL parser's own normalization. This covers both variants, fixes every call site at once (LinkRenderer, ImageRenderer, and any third-party callers), and needs no changes in the Attributes extension.

Affected Versions

>= 1.5.0, <= 2.8.3 - every release that ships the AttributesExtension. Verified by installing each version and rendering the payloads with allow_unsafe_links => false. The attribute-value grammar (PARTIAL_DOUBLEQUOTEDVALUE = '"[^"]*"') has accepted raw control bytes since the extension was introduced, and none of the intervening parser rewrites narrowed it.

Prior Related Advisories

GHSA-3527-qv2q-pfvx / CVE-2025-46734 fixed a different Attributes-extension XSS (unallowlisted on* handlers, href/src not respecting allow_unsafe_links at all) in v2.7.0. This issue bypasses the specific href/src protection that fix introduced (the control-byte normalization gap was not part of that fix) - but the obfuscated inputs also work on older versions.

Threat Intelligence Overview

EPSS Score
0.2%
Top 90% exploitable
Prevalence
Medium
popular PHP Markdown library used across many CMS and developer platforms
Threat Intel
1
1 sources
Data Quality
7 sources
Confidence: High
EUVD NVD CVE.org EPSS Vulnrichment GHSA GitHub VulDB

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity MEDIUM
CISA KEV No
Public POC No
EPSS 0.2%
Patch available Yes
Active threat No
Deciding rule
MEDIUM severity (CVSS 4.0-6.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision· derived
Scheduled
Exploitation
Poc
System Exposure· derived
Open
Automatable
No
Human Impact· derived
Medium
Technical Impact
Partial

Priority Score

30
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +30
PoC: 0
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Cross-Site Scripting occurs when an application accepts untrusted data and sends it to a web browser without proper validation or encoding.

More in PHP

View all
CVE-2019-11043 CRITICAL POC
9.8 Oct 28

In PHP versions 7.1.x below 7.1.33, 7.2.x below 7.2.24 and 7.3.x below 7.3.11 in certain configurations of FPM setup it

CVE-2012-1823 CRITICAL POC
9.8 May 11

sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when configured as a CGI script (aka php-cgi), does not

CVE-2016-1555 CRITICAL POC
9.8 Apr 21

(1) boardData102.php, (2) boardData103.php, (3) boardDataJP.php, (4) boardDataNA.php, and (5) boardDataWW.php in Netgear

CVE-2018-11138 CRITICAL POC
9.8 May 31

The '/common/download_agent_installer.php' script in the Quest KACE System Management Appliance 8.0.318 is accessible by

CVE-2024-11680 CRITICAL POC
9.8 Nov 26

ProjectSend versions prior to r1720 are affected by an improper authentication vulnerability. Rated critical severity (C

CVE-2025-49113 CRITICAL POC
9.9 Jun 02

Roundcube Webmail contains a critical PHP object deserialization vulnerability (CVE-2025-49113, CVSS 9.9) that allows au

CVE-2017-9841 CRITICAL POC
9.8 Jun 27

Util/PHP/eval-stdin.php in PHPUnit before 4.8.28 and 5.x before 5.6.3 allows remote attackers to execute arbitrary PHP c

CVE-2025-0108 HIGH POC
8.8 Feb 12

Palo Alto Networks PAN-OS management web interface contains an authentication bypass allowing unauthenticated attackers

CVE-2021-25298 HIGH POC
8.8 Feb 15

Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re

CVE-2021-25296 HIGH POC
8.8 Feb 15

Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re

CVE-2013-4983 CRITICAL POC
10.0 Sep 10

The get_referers function in /opt/ws/bin/sblistpack in Sophos Web Appliance before 3.7.9.1 and 3.8 before 3.8.1.1 allows

CVE-2023-6553 CRITICAL POC
9.8 Dec 15

The Backup Migration plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 1

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy