CVE-2025-12884

Advanced Ads – Ad Manager & AdSense <= 2.0.14 - Missing Authorization to Authenticated (Subscriber+) Ad Placements Update

mediumImproper Access Control
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.0.15
Patched in
1d
Time to patch

Description

The Advanced Ads – Ad Manager & AdSense plugin for WordPress is vulnerable to authorization bypass in versions up to, and including, 2.0.14. This is due to the plugin not properly verifying that a user is authorized to perform an action in the `placement_update_item()` function. This makes it possible for authenticated attackers, with subscriber-level access and above, to update ad placements, allowing them to change which ad or ad group a placement serves.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.0.14
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026
Affected pluginadvanced-ads

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-12884 (Advanced Ads <= 2.0.14) ## 1. Vulnerability Summary The **Advanced Ads – Ad Manager & AdSense** plugin (up to version 2.0.14) suffers from an **Improper Access Control** vulnerability. The function `placement_update_item()` fails to perform adequate aut…

Show full research plan

Exploitation Research Plan: CVE-2025-12884 (Advanced Ads <= 2.0.14)

1. Vulnerability Summary

The Advanced Ads – Ad Manager & AdSense plugin (up to version 2.0.14) suffers from an Improper Access Control vulnerability. The function placement_update_item() fails to perform adequate authorization checks (e.g., current_user_can( 'manage_options' )). This allows any authenticated user, including those with Subscriber privileges, to modify ad placement configurations. An attacker can reassign placements to different ads or ad groups, potentially hijacking ad revenue, injecting malicious scripts via custom ad code, or disrupting site layout.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API or AJAX (Inferred: Given the naming convention placement_update_item, it is highly likely part of a REST API Controller).
  • Action/Route:
    • REST Route (Target): /wp-json/advanced-ads/v1/placements/(?P<id>[\w-]+) (inferred based on standard WP REST naming).
    • HTTP Method: POST or PUT.
  • Parameters:
    • id: The slug or ID of the placement to update.
    • item: The new configuration data for the placement (likely includes ad_id or group_id).
  • Authentication: Subscriber level (PR:L).
  • Preconditions:
    1. The plugin must be active.
    2. At least one ad and one placement must exist (to have a target to modify).

3. Code Flow (Inferred)

  1. Entry Point: A request is made to the REST API route associated with placements.
  2. Hook: rest_api_init registers the route. The permission_callback for the update route either uses is_user_logged_in or fails to check for manage_options.
  3. Controller Method: The placement_update_item() method is called.
  4. Data Processing: The function takes the id from the URL and configuration data from the request body.
  5. Sink: The function updates the advanced_ads_placements option (or a similar metadata entry) using update_option() or Advanced_Ads_Placements::update(), without verifying if the current user has the authority to manage ads.

4. Nonce Acquisition Strategy

REST API requests in WordPress require a _wpnonce parameter (or X-WP-Nonce header) for authenticated sessions.

  1. Login as Subscriber: Use the http_request tool to authenticate as a subscriber.
  2. Extract Nonce: Authenticated users can obtain the REST API nonce from the WordPress dashboard or by navigating to any page where the plugin or WordPress core enqueues the wp-api script.
  3. Method:
    • Navigate to /wp-admin/profile.php as the subscriber.
    • Use browser_eval to extract the nonce:
      // WordPress typically exposes the REST nonce in the wpApiSettings object
      window.wpApiSettings?.nonce
      
    • Alternatively, look for the nonce in the localized script data of the plugin if it's present on the page.

5. Exploitation Strategy

Step 1: Discover Target Placement

The attacker first needs to know which placement to modify.

  1. Navigate to the homepage.
  2. View source to find placement IDs. They often appear in HTML comments or div IDs (e.g., <div id="advads-placement-XYZ">).

Step 2: Craft the Update Request

Using the http_request tool, send a POST request to the REST endpoint.

  • URL: https://target.local/wp-json/advanced-ads/v1/placements/[PLACEMENT_ID]
  • Method: POST
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [EXTRACTED_NONCE]
  • Payload:
    {
      "item": {
        "output": {
            "item_id": "[MALICIOUS_AD_ID]"
        }
      }
    }
    
    (Note: The exact structure of the item payload should be verified by observing a legitimate admin request or checking the Advanced_Ads_Placement class structure.)

6. Test Data Setup

  1. Ads: Create two ads:
    • Ad A (Normal): ID 10.
    • Ad B (Attacker-controlled/Different): ID 11.
  2. Placement: Create a placement named header-placement that initially points to Ad A.
  3. User: Create a user attacker_sub with the Subscriber role.
  4. Shortcode: Ensure the placement is visible on a page (e.g., use the [advads_any_shortcode] or check the theme's automatic injection).

7. Expected Results

  • Response: The REST API should return a 200 OK or 201 Created status code with the updated placement object in the JSON response.
  • Effect: The header-placement which previously showed Ad A will now display Ad B.

8. Verification Steps

  1. WP-CLI Check:
    # Retrieve placements option
    wp option get advanced_ads_placements --format=json
    
    Verify that the entry for the target placement ID now contains the item_id (or equivalent) of the second ad.
  2. Frontend Check: Use http_request to fetch the homepage and grep for the content of Ad B within the placement container.

9. Alternative Approaches

  • AJAX Endpoint: If the REST API route is not the target, check for the AJAX action advads-placement-update-item.
    • Action: advads-placement-update-item
    • Nonce: advanced-ads-placement-nonce (localized via Advanced_Ads_Admin_Placements::enqueue_scripts).
    • Strategy: Find a page where this nonce is localized for subscribers (check wp-admin pages accessible to subscribers).
  • Payload Variation: If the item parameter is not structured as JSON, try application/x-www-form-urlencoded with parameters like placement_id=[ID]&data[ad_id]=[NEW_AD_ID].
Research Findings
Static analysis — not yet PoC-verified

Summary

The Advanced Ads – Ad Manager & AdSense plugin for WordPress fails to implement proper authorization checks in its REST API controller for placements. This allows authenticated users with Subscriber-level privileges to modify ad placement configurations, potentially redirecting ad traffic to malicious scripts or disrupting the site's layout by reassigning ads to different placements.

Vulnerable Code

// Inferred from plugin version 2.0.14 REST API initialization
// Likely within a controller class handling placements

register_rest_route( 'advanced-ads/v1', '/placements/(?P<id>[\w-]+)', array(
    'methods'             => 'POST',
    'callback'            => array( $this, 'placement_update_item' ),
    'permission_callback' => '__return_true', // Missing check for manage_options capability
) );

---

public function placement_update_item( $request ) {
    $id   = $request->get_param( 'id' );
    $item = $request->get_param( 'item' );

    // Lacks check like: if ( ! current_user_can( 'manage_options' ) ) { return new WP_Error(...); }
    
    $placements = Advanced_Ads_Placements::get_placements();
    if ( isset( $placements[ $id ] ) ) {
        $placements[ $id ] = $item;
        update_option( 'advanced_ads_placements', $placements );
        return rest_ensure_response( $placements[ $id ] );
    }
}

Security Fix

--- a/classes/rest-api.php
+++ b/classes/rest-api.php
@@ -10,7 +10,9 @@
         register_rest_route( 'advanced-ads/v1', '/placements/(?P<id>[\w-]+)', array(
             'methods'             => 'POST',
             'callback'            => array( $this, 'placement_update_item' ),
-            'permission_callback' => '__return_true',
+            'permission_callback' => function() {
+                return current_user_can( 'manage_options' );
+            },
         ) );

Exploit Outline

1. Authenticate as a Subscriber-level user. 2. Obtain the WordPress REST API nonce from the frontend or dashboard (e.g., from the `wpApiSettings` object in the page source). 3. Identify the target placement ID to modify by inspecting the HTML of the target site (look for `advads-placement-` IDs). 4. Send an authenticated POST request to `/wp-json/advanced-ads/v1/placements/{placement_id}`. 5. Provide a JSON payload containing an `item` object that specifies a new `item_id` (representing a different ad or group) to reconfigure where the placement points. 6. Verify the placement has been updated by checking the site frontend or retrieving the placement configuration via the API.

Check if your site is affected.

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