Quads Ads Manager for Google AdSense <= 3.0.2 - Missing Authorization
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:NTechnical Details
<=3.0.2What Changed in the Fix
Changes introduced in v3.0.3
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-42744 (Quads Ads Manager) ## 1. Vulnerability Summary The **Quads Ads Manager for Google AdSense** plugin (up to version 3.0.2) is vulnerable to **Missing Authorization**. The function `quads_authorize_payment_success` in `includes/ad-selling-helper.php` is ho…
Show full research plan
Exploitation Research Plan: CVE-2026-42744 (Quads Ads Manager)
1. Vulnerability Summary
The Quads Ads Manager for Google AdSense plugin (up to version 3.0.2) is vulnerable to Missing Authorization. The function quads_authorize_payment_success in includes/ad-selling-helper.php is hooked to WordPress init. While it performs a check to ensure a user is logged in (is_user_logged_in()), it fails to verify if the user has administrative privileges or if the user is the owner of the specific advertisement order being processed. This allows any authenticated user (e.g., a Subscriber) to trigger the payment confirmation logic, marking advertisement slots as "paid" in the database without an actual transaction.
2. Attack Vector Analysis
- Endpoint: Any frontend or backend page (since the function is hooked to
init). - Method:
GET - Action Hook:
add_action( 'init', 'quads_authorize_payment_success' ); - Required Parameters:
security: A valid WordPress nonce for the action'security'.status: Must be set tosuccess.ad_slot_id: The ID of the ad slot (must be> 0).refId: A non-empty reference string (likely corresponds to the order ID based on the code's usage of$order_id).user_id: The ID of the user whose ad is being marked as paid.
- Authentication: Authenticated user (Subscriber or higher).
3. Code Flow
- Entry: WordPress initializes and triggers the
inithook. - Trigger:
quads_authorize_payment_success()is called. - Auth Check:
is_user_logged_in()passes if the attacker is logged in as a Subscriber. - Nonce Check:
wp_verify_nonce( $_GET['security'], 'security' )validates the token. - Logic:
- The code attempts to retrieve the user via
get_user_by( 'id', $user_id ). - It queries the
quads_adbuy_datatable for the order details. - Vulnerability Sink: It calls
$wpdb->update()on thequads_adbuy_datatable, settingpayment_statusto'paid'. - Side Effect: Sends a confirmation email to both the user and the administrator via
wp_mail().
- The code attempts to retrieve the user via
Note: In the provided source snippet, $order_id is used but not explicitly initialized from $_GET. It is likely that $order_id is mapped to $_GET['refId'] or assigned elsewhere in the full file.
4. Nonce Acquisition Strategy
The nonce action string is 'security'. To obtain this nonce as a Subscriber:
- Identify the Source: The
quads_create_sellpage_on_activation()function indicates that a page with the shortcode[quads_buy_form]is created (usually at the slug/buy-adspace). - Setup: If the page doesn't exist, create it:
wp post create --post_type=page --post_title="Buy Ads" --post_status=publish --post_content='[quads_buy_form]' - Extraction:
- Log in as a Subscriber user.
- Navigate to the page containing the
[quads_buy_form]. - Use
browser_evalto search for the nonce. It may be in a hidden field or a localized JS variable. - Search Pattern: Look for
wp_create_nonce( 'security' )in the plugin source to find the exact JS localization key. If found in a form:browser_eval("document.querySelector('input[name=\"security\"]').value")
5. Exploitation Strategy
Login: Authenticate as a Subscriber user.
Capture Nonce: Visit the ad purchase page and extract the
'security'nonce.Identify Target: Determine a valid
ad_slot_idand the database ID of an unpaid ad purchase (refId/order_id).Execute Attack: Send a GET request to the site root with the forged parameters.
Request Example:
GET /?status=success&ad_slot_id=1&refId=1&user_id=2&security=abc123def4 HTTP/1.1 Host: target.local Cookie: [Subscriber Cookies]
6. Test Data Setup
- Enable Feature: Ensure the "Sellable Ads" feature is active.
- Create Order: Create an advertisement order in the database for the Subscriber user (ID 2).
- Use WP-CLI to insert a record into the
{wpdb->prefix}quads_adbuy_datatable withpayment_status = 'pending'.
- Use WP-CLI to insert a record into the
- Find Slot: Ensure an ad slot exists (Post Type
quads_ad_slotor similar, depending on plugin structure).
7. Expected Results
- The plugin identifies the request as a successful payment.
- The database record in
wp_quads_adbuy_datafor the givenrefIdis updated:payment_statuschanges frompendingtopaid. - An email is triggered to the admin and the user confirming the payment.
- The HTTP response may be a redirect or a success message, but the primary indicator is the database state.
8. Verification Steps
- Database Check: After the HTTP request, verify the payment status via WP-CLI:
wp db query "SELECT payment_status FROM wp_quads_adbuy_data WHERE id = 1"
Expected output:paid. - Log Check: Check the WordPress mail log (if a logger is installed) to see if the "Ad Payment Confirmation" email was sent.
9. Alternative Approaches
- Parameter Mapping: If
refIddoes not map to the databaseid, check if the plugin uses$_GET['order_id'](common in such plugins). - Unauthenticated Check: Verify if the
is_user_logged_in()check is truly enforced in all versions. If missing, the attack becomes CVSS 9.8 (Critical). - CSRF: Since the action uses GET and requires a nonce, if the nonce is obtainable by an attacker but the
is_user_logged_in()check is strict, the vulnerability could be leveraged as a CSRF against an Admin if they click a malicious link.
Summary
The Quads Ads Manager for Google AdSense plugin is vulnerable to unauthorized access because the `quads_authorize_payment_success` function, hooked to the WordPress `init` action, lacks proper capability and ownership checks. This allows any authenticated user (such as a Subscriber) to mark an advertisement order as 'paid' in the database without completing a transaction.
Vulnerable Code
// includes/ad-selling-helper.php line 52 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'] )){ // ... logic continues to update database -- line 92 // 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
@@ -7,6 +7,52 @@ if( !defined( 'ABSPATH' ) ) { exit; } + +/** + * Merge query args onto a return URL that may already contain ?key=value (e.g. ad_slot_id on the storefront). + * + * @param string $base_url Absolute URL. + * @param array $args Query parameters to merge. + * @return string + */ +function quads_add_return_query_args( $base_url, array $args ) { + $base_url = is_string( $base_url ) ? trim( $base_url ) : ''; + if ( '' === $base_url ) { + return ''; + } + return add_query_arg( $args, $base_url ); +} + +/** + * Absolute URL for the current request path plus sanitized query string (e.g. pre-selected ad slot). + * + * @return string + */ +function quads_get_checkout_redirect_base_url() { + global $wp; + $redirect_link = isset( $wp->request ) ? home_url( $wp->request ) : home_url( '/' ); + if ( isset( $_SERVER['QUERY_STRING'] ) && is_string( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] !== '' ) { + parse_str( wp_unslash( $_SERVER['QUERY_STRING'] ), $parsed_qs ); + if ( ! empty( $parsed_qs ) && is_array( $parsed_qs ) ) { + $clean_qs = array(); + foreach ( $parsed_qs as $qs_key => $qs_val ) { + $saf_key = sanitize_key( wp_unslash( (string) $qs_key ) ); + if ( '' === $saf_key ) { + continue; + } + if ( is_array( $qs_val ) ) { + continue; + } + $clean_qs[ $saf_key ] = sanitize_text_field( wp_unslash( (string) $qs_val ) ); + } + if ( ! empty( $clean_qs ) ) { + $redirect_link = add_query_arg( $clean_qs, $redirect_link ); + } + } + } + + return $redirect_link; +}
Exploit Outline
To exploit this vulnerability, an attacker must first authenticate with a Subscriber account. They then navigate to the plugin's ad purchase page (typically `/buy-adspace`) to extract a valid 'security' nonce generated for the user session. With this nonce, the attacker crafts a GET request to the site root containing parameters used by the payment gateway callback logic: 'status=success', 'security' (the nonce), 'ad_slot_id' (a valid ad slot ID), 'refId' (the target order ID from the database), and 'user_id' (the attacker's own user ID). When the request is processed, the plugin updates the 'payment_status' of the order to 'paid' in the 'quads_adbuy_data' table, effectively bypassing the actual payment requirement.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.