SECURITY ADVISORY / 01

CVE-2025-13964 Exploit & Vulnerability Analysis

Complete CVE-2025-13964 security advisory with proof of concept (PoC), exploit details, and patch analysis.

cve_patchdiff:learnpress NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

No authentication is required; an attacker only needs to control the CSS selector string passed into the affected symfony/css-selector parser.

php -r 'require "vendor/autoload.php"; use Symfony\Component\CssSelector\Parser\Parser; $parser = new Parser(); try { $parser->parse("div :scope"); echo "VULNERABLE\n"; } catch (Throwable $e) { echo "SAFE: ".$e->getMessage()."\n"; }'

When run against the vulnerable code, the malformed selector div :scope is accepted and the parser returns an AST instead of erroring. After the patch, the same selector raises SyntaxErrorException::notAtTheStartOfASelector('scope'), proving the parser now enforces valid :scope placement.

What the Patch Did

Before:

if (!$stream->getPeek()->isDelimiter(['('])) {
    $result = new Node\PseudoNode($result, $identifier);

    continue;
}

After:

if (!$stream->getPeek()->isDelimiter(['('])) {
    $result = new Node\PseudoNode($result, $identifier);
    if ('Pseudo[Element[*]:scope]' === $result->__toString()) {
        $used = \count($stream->getUsed());
        if (!(2 === $used
           || 3 === $used && $stream->getUsed()[0]->isWhiteSpace()
           || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([','])
           || $used >= 4
                && $stream->getUsed()[$used - 3]->isWhiteSpace()
                && $stream->getUsed()[$used - 4]->isDelimiter([','])
        )) {
            throw SyntaxErrorException::notAtTheStartOfASelector('scope');
        }
    }
    continue;
}

The patch adds a parser-side syntax guard for the :scope pseudo-class. It checks whether the parsed pseudo is exactly :scope and then rejects it unless it appears at the beginning of a selector or immediately after a comma in a selector list.

Root Cause

This is CWE-20: Improper Input Validation. The attacker-controlled selector string enters the parser via Parser::parse(), reaches the Node\PseudoNode creation path for a bare pseudo-class, and crosses a trust boundary without context-sensitive validation. The parser previously accepted :scope anywhere a pseudo-class could appear, so invalid selectors such as div :scope were treated as syntactically valid by the underlying library.

Why It Works

The load-bearing line is the throw SyntaxErrorException::notAtTheStartOfASelector('scope'); inside the new if block. Without that line, the parser would still build the PseudoNode and continue, leaving invalid :scope placements accepted. The surrounding checks on $used and delimiter/whitespace positions are there to distinguish valid cases: a selector starting with :scope, a leading whitespace form, or a selector list entry after a comma.

Hardening Checklist

  • update vendored dependencies promptly; keep vendor/symfony/css-selector current rather than shipping an outdated parser library.
  • validate untrusted CSS selector input before passing it to a generic parser, and treat parser exceptions as security-relevant failures.
  • if your plugin accepts user-provided selectors, reject or sanitize them server-side rather than relying solely on client-side validation.
  • in WordPress plugins, protect AJAX endpoints with current_user_can() and wp_verify_nonce() when selector input affects course structure or content.
  • use explicit syntax validation for special pseudo-classes like :scope instead of assuming all pseudo-elements are equally valid in all positions.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2025-13964

Frequently asked questions about CVE-2025-13964

What is CVE-2025-13964?

CVE-2025-13964 is a security vulnerability. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2025-13964?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2025-13964. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2025-13964 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2025-13964?

CVE-2025-13964 — check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2025-13964?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls.

What is the CVSS score for CVE-2025-13964?

The severity rating and CVSS scoring for CVE-2025-13964 is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.