CVE-2026-42732

Quads Ads Manager for Google AdSense <= 3.0.2 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
3.0.3
Patched in
3d
Time to patch

Description

The Quads Ads Manager for Google AdSense plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 3.0.2. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.0.2
PublishedMay 24, 2026
Last updatedMay 26, 2026
Affected pluginquick-adsense-reloaded

What Changed in the Fix

Changes introduced in v3.0.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-42732 ## 1. Vulnerability Summary The **Quads Ads Manager for Google AdSense** plugin (up to version 3.0.2) contains a **Missing Authorization** vulnerability in its ad-selling module. Specifically, the function `quads_authorize_payment_success` is hooked to …

Show full research plan

Exploitation Research Plan - CVE-2026-42732

1. Vulnerability Summary

The Quads Ads Manager for Google AdSense plugin (up to version 3.0.2) contains a Missing Authorization vulnerability in its ad-selling module. Specifically, the function quads_authorize_payment_success is hooked to WordPress's init action, making it accessible on every page load. The function lacks proper authorization checks (e.g., current_user_can) and fails to verify if the request originated from a legitimate payment gateway. While it contains a check for is_user_logged_in() and a nonce check in the provided source, the CVE identifies it as unauthenticated, suggesting either these checks are bypassable, were absent in earlier versions, or the nonce itself is exposed to unauthenticated users.

Successful exploitation allows an attacker to mark pending ad orders as "paid" without actual payment, effectively stealing advertising space.

2. Attack Vector Analysis

  • Endpoint: Any WordPress URL (via the init hook).
  • Vulnerable Function: quads_authorize_payment_success() in includes/ad-selling-helper.php.
  • HTTP Method: GET.
  • Payload Parameters:
    • status: Must be exactly success.
    • ad_slot_id: An integer representing the target ad slot ID.
    • refId: An integer representing the order ID (intended to be mapped to $order_id).
    • user_id: An integer representing the user ID of the ad buyer.
    • security: A WordPress nonce for the action 'security'.
  • Preconditions:
    • The "Sellable Ads" feature must be active.
    • A pending order must exist in the wp_quads_adbuy_data table.
    • A valid nonce for the 'security' action must be obtained.

3. Code Flow

  1. Entry Point: A GET request is made to any WordPress page.
  2. Hook: The init hook triggers quads_authorize_payment_success in includes/ad-selling-helper.php.
  3. Authentication Check: The function checks is_user_logged_in(). (If the CVE is unauthenticated, this check might be absent in vulnerable versions or the attacker simply needs a Subscriber account).
  4. Nonce Verification: wp_verify_nonce( $_GET['security'], 'security' ).
  5. Logic Path: If status == 'success', ad_slot_id > 0, refId != "", and user_id > 0, the code enters the update block.
  6. Sink: The plugin performs a database update:
    $wpdb->update(
        $table_name,
        array('payment_status' => 'paid' , 'payment_response'=> wp_json_encode($params)),
        array('id' => $order_id , 'user_id'=>$user->ID) 
    );
    
    Note: In the provided source, $order_id and $user_id are used but not explicitly initialized from $_GET before the absint() call. This is likely a developer error in the plugin's code that may rely on register_globals (unlikely) or an earlier extract($_GET) (possible).

4. Nonce Acquisition Strategy

The nonce is tied to the action 'security'. In this plugin, this generic nonce is typically localized for the frontend ad-buying form.

  1. Identify Page: The plugin creates a page with the slug buy-adspace containing the shortcode [quads_buy_form].
  2. Navigate: Use the browser to navigate to /buy-adspace/.
  3. Extract: Use browser_eval to find the localized JavaScript object. The plugin usually localizes variables under a name related to "quads".
    • Candidate Variable: quads_ajax_object or quads_vars.
    • Command: browser_eval("window.quads_ajax_object?.security") or browser_eval("window.quads_vars?.security").
    • Verification: If no specific variable is found, search the HTML source for "security":".

5. Exploitation Strategy

  1. Setup Phase:
    • Ensure the plugin is active and the wp_quads_adbuy_data table exists.
    • Create a "Sellable Ad" order in the database with payment_status = 'pending'.
  2. Nonce Extraction:
    • Access the /buy-adspace/ page and extract the 'security' nonce.
  3. Exploit Execution:
    • Construct the malicious GET request.
    • URL: http://localhost:8080/?status=success&ad_slot_id=[SLOT_ID]&refId=[ORDER_ID]&user_id=[USER_ID]&security=[NONCE]
    • Note: If the source code in the target environment matches the provided snippet exactly, the undefined $order_id may cause the exploit to fail unless the agent can determine if the plugin uses refId as the order ID.
  4. Confirmation:
    • Verify that the database record's payment_status has changed to paid.

6. Test Data Setup

# 1. Create a test ad slot (if applicable) or identify an existing one
# 2. Create the Buy Adspace page if it doesn't exist
wp post create --post_type=page --post_title="Buy Adspace" --post_status=publish --post_content='[quads_buy_form]' --post_name='buy-adspace'

# 3. Create a pending order in the custom table (requires identifying the table name)
# Assuming table: wp_quads_adbuy_data
# Columns: id, user_id, ad_id, payment_status, etc.
wp db query "INSERT INTO wp_quads_adbuy_data (user_id, ad_id, payment_status, start_date, end_date) VALUES (1, 123, 'pending', '2025-01-01', '2025-01-31');"
# Note the ID of the inserted row for refId

7. Expected Results

  • The HTTP request should return a 200 OK (the function returns nothing or calls wp_mail).
  • An email will be sent to the admin and user (can be checked via wp mail list if a mail catcher is used).
  • The database entry in wp_quads_adbuy_data for the specific id will have payment_status set to 'paid'.

8. Verification Steps

# Check the database for the status change
wp db query "SELECT id, payment_status FROM wp_quads_adbuy_data WHERE id = [ORDER_ID];"

# Verify the output matches 'paid'

9. Alternative Approaches

  • Variable Context: If the GET parameters refId and user_id do not populate the internal $order_id and $user_id variables, try adding them directly as order_id=[ID] and user_id=[ID] in the query string, as some plugins use extract($_GET) or $_REQUEST.
  • Subscriber Level: If the is_user_logged_in() check is strictly enforced, create a Subscriber user, log in, and then perform the request. The vulnerability is still "Missing Authorization" because a Subscriber should not be able to authorize payments for ad slots.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Quads Ads Manager plugin for WordPress is vulnerable to unauthorized access via the quads_authorize_payment_success function. This function is hooked to 'init' and lacks proper authorization or capability checks, allowing authenticated attackers (like Subscribers) to mark pending ad orders as paid without completing actual payments.

Vulnerable Code

// includes/ad-selling-helper.php


add_action( 'init', 'quads_authorize_payment_success' );
function quads_authorize_payment_success(){
    
    if ( !is_user_logged_in() ) {
        return false;
    }
    if( !isset( $_GET[ 'security' ] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET[ 'security' ] ) ), 'security' )){ 
        return false;
    }
    if( isset( $_GET['status'] ) && $_GET['status']=='success' && isset( $_GET['ad_slot_id'] ) && $_GET['ad_slot_id'] > 0 && isset( $_GET['refId'] ) && $_GET['refId'] != "" && isset( $_GET['user_id'] ) && intval( $_GET['user_id'] ) >0 && !isset( $_GET['target'] )){
        $slot_id = absint( $_GET['ad_slot_id'] );
        $order_id = absint($order_id);
        $user_id = absint($user_id);
        $price = get_post_meta( $slot_id, 'ad_cost' );

---

            $params = array();
            $params['payment_date'] = gmdate('Y-m-d H:i:s');
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

            $wpdb->update(
                $table_name,
                array('payment_status' => 'paid' , 'payment_response'=> wp_json_encode($params)), // Data to update

                array('id' => $order_id , 'user_id'=>$user->ID) 
            );

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/quick-adsense-reloaded/3.0.2/includes/ad-selling-helper.php /home/deploy/wp-safety.org/data/plugin-versions/quick-adsense-reloaded/3.0.3/includes/ad-selling-helper.php
--- /home/deploy/wp-safety.org/data/plugin-versions/quick-adsense-reloaded/3.0.2/includes/ad-selling-helper.php	2026-04-16 14:19:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/quick-adsense-reloaded/3.0.3/includes/ad-selling-helper.php	2026-05-08 12:37:54.000000000 +0000
@@ -1710,6 +1838,21 @@
         }
     }
 
+    $price      = get_post_meta( $ad_slot_id, 'ad_cost', true );
+    $currency   = 'USD';
+    $days       = ( strtotime( $end_date ) - strtotime( $start_date ) ) / ( 60 * 60 * 24 ) + 1;
+    $total_cost = $price * $days;
+    $name       = get_the_title( $ad_slot_id );
+
+    $coupon_parse = quads_parse_coupon_discount( $coupon_code, $ad_slot_id, (float) $total_cost );
+    if ( 'invalid' === $coupon_parse['status'] || 'expired' === $coupon_parse['status'] ) {
+        $err_msg = ( 'expired' === $coupon_parse['status'] )
+            ? esc_html__( 'Coupon expired, please try another one.', 'quick-adsense-reloaded' )
+            : esc_html__( 'Invalid coupon, please try another one.', 'quick-adsense-reloaded );
+        wp_send_json_error( array( 'message' => $err_msg ) );
+    }
+    $total_cost = max( 0, (float) $total_cost - (float) $coupon_parse['discount'] );
+
     // Insert the ad buy record in the database
     global $wpdb;
     $table_name = $wpdb->prefix . 'quads_adbuy_data';

Exploit Outline

1. Authentication: The attacker authenticates as a low-privileged user (e.g., Subscriber). 2. Nonce Acquisition: The attacker visits the plugin's ad-selling page (typically /buy-adspace/) to extract a valid WordPress nonce for the action 'security'. This nonce is often localized in JavaScript variables like 'quads_ajax_object'. 3. Order Identification: The attacker initiates an ad purchase or identifies an existing pending order ID (refId) and their own user_id. 4. Request Construction: The attacker constructs a GET request to the site root with parameters mimicking a successful payment gateway callback: ?status=success&ad_slot_id=[SLOT_ID]&refId=[ORDER_ID]&user_id=[USER_ID]&security=[NONCE]. 5. Execution: Upon receiving the request, the init hook triggers quads_authorize_payment_success, which validates the nonce and user status but fails to verify that the user has administrative privileges or that the payment was verified by the provider. The plugin then updates the database to set the order status to 'paid'.

Check if your site is affected.

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