Ultimate Post Kit Addons for Elementor <= 4.0.21 - Missing Authorization
Description
The Ultimate Post Kit Addons for Elementor plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 4.0.21. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=4.0.21What Changed in the Fix
Changes introduced in v4.0.22
Source Code
WordPress.org SVN# Research Plan: CVE-2026-24362 - Missing Authorization in Ultimate Post Kit ## 1. Vulnerability Summary The **Ultimate Post Kit Addons for Elementor** plugin (up to 4.0.21) contains a missing authorization vulnerability in its `Biggopties` class. Specifically, the AJAX handlers registered for dism…
Show full research plan
Research Plan: CVE-2026-24362 - Missing Authorization in Ultimate Post Kit
1. Vulnerability Summary
The Ultimate Post Kit Addons for Elementor plugin (up to 4.0.21) contains a missing authorization vulnerability in its Biggopties class. Specifically, the AJAX handlers registered for dismissing notices and fetching remote API data lack capability checks (e.g., current_user_can( 'manage_options' )). This allows authenticated users with Subscriber-level permissions to trigger administrative actions, such as forcing the site to fetch promotional data from a remote API and updating internal transients.
2. Attack Vector Analysis
- Target Endpoint:
/wp-admin/admin-ajax.php - AJAX Actions:
upk_fetch_api_biggopties(Triggers remote API fetch and transient update)ultimate-post-kit-biggopties(Dismisses notices/updates notice state)
- Parameters:
action:upk_fetch_api_biggoptiesorultimate-post-kit-biggopties_wpnonce: A nonce associated with theUltimatePostKitBiggoptiConfigJavaScript object.- (For dismiss action):
id,meta,time.
- Preconditions:
- User must be authenticated (Subscriber or higher).
- The plugin
ultimate-post-kitmust be active.
3. Code Flow
- Entry Point: The
UltimatePostKit\Biggoptiesclass registers AJAX hooks in its__constructmethod (found inadmin/admin-biggopti.php):add_action('wp_ajax_ultimate-post-kit-biggopties', [$this, 'dismiss']); add_action('wp_ajax_upk_fetch_api_biggopties', [$this, 'ajax_fetch_api_biggopties']); - AJAX Handler: When a Subscriber sends a request with
action=upk_fetch_api_biggopties, theajax_fetch_api_biggoptiesmethod is executed. - Logic: The handler likely calls
get_api_biggopties_data(), which:- Performs an outbound HTTP request to
https://api.sigmative.io/prod/store/api/biggopti/api-data-records. - Saves the result to a transient named
bdt_api_biggoptiesusingset_transient().
- Performs an outbound HTTP request to
- Vulnerability: Neither
dismissnorajax_fetch_api_biggoptiescontains a check for administrative capabilities. While they check nonces, these nonces are exposed to all logged-in users who can access the admin dashboard (including Subscribers visiting/wp-admin/profile.php).
4. Nonce Acquisition Strategy
The nonce is localized for the script upk-biggopti (JS file: admin/assets/js/upk-biggopti.min.js).
- Login: Authenticate as a Subscriber.
- Navigate: Access the WordPress dashboard at
/wp-admin/profile.php. - Extract Nonce: The nonce is stored in the global JavaScript variable
UltimatePostKitBiggoptiConfig. - JS Variable:
window.UltimatePostKitBiggoptiConfig.nonce - Procedure:
- Use
browser_navigateto go to/wp-admin/profile.php. - Use
browser_evalto run:return window.UltimatePostKitBiggoptiConfig ? window.UltimatePostKitBiggoptiConfig.nonce : null;.
- Use
5. Exploitation Strategy
Phase 1: Force API Data Fetch
- Request: POST to
/wp-admin/admin-ajax.php. - Payload:
action=upk_fetch_api_biggopties&_wpnonce=[EXTRACTED_NONCE] - Expected Outcome: The server returns a JSON response (likely
success: trueor a data object) and performs an outbound request to the Sigmative API, overwriting thebdt_api_biggoptiestransient.
Phase 2: Dismiss Admin Notice (Alternative)
- Request: POST to
/wp-admin/admin-ajax.php. - Payload:
action=ultimate-post-kit-biggopties&id=test-id&meta=test-meta&time=3600&_wpnonce=[EXTRACTED_NONCE] - Expected Outcome: Modification of notice state (likely stored in options or user meta), demonstrating unauthorized state change.
6. Test Data Setup
- Users: Create a user with the
subscriberrole. - Plugin State: Ensure "Ultimate Post Kit" is active.
- Cleanup: Delete the transient
bdt_api_biggoptiesbefore testing to ensure the exploit actually recreates it:wp transient delete bdt_api_biggopties
7. Expected Results
- A successful response from the AJAX endpoint (HTTP 200).
- The
bdt_api_biggoptiestransient should exist/be updated in the database. - The logs (if monitored) should show a
wp_remote_getrequest tohttps://api.sigmative.io/....
8. Verification Steps
After performing the HTTP request as a Subscriber, verify the impact using WP-CLI:
- Check Transient:
If the transient contains data, the unauthorized action was successful.wp transient get bdt_api_biggopties - Verify Nonce Usage: Confirm the response body from the
http_requestmatches the expected format fromajax_fetch_api_biggopties.
9. Alternative Approaches
If upk_fetch_api_biggopties is patched or restricted, target the Setup_Wizard's manual request:
- Unauthorized UI Access: Navigate to
/wp-admin/index.php?upk_setup_wizard=showas a Subscriber. - Verification: Use
browser_evalto check if the class.bdt-setup-wizard-activeis added to thebodytag, indicating the wizard UI was successfully loaded for a low-privileged user.
Summary
The Ultimate Post Kit Addons for Elementor plugin is vulnerable to unauthorized access due to missing capability checks in several AJAX handlers, specifically those related to administrative notice management (Biggopties) and the Setup Wizard. This allows authenticated users with Subscriber-level permissions to perform administrative actions such as dismissing site-wide notices, forcing remote API data fetches that update transients, and importing Elementor templates from arbitrary URLs.
Vulnerable Code
// admin/admin-biggopti.php - AJAX handlers for notices and API fetching public function __construct() { add_action('wp_ajax_ultimate-post-kit-biggopties', [$this, 'dismiss']); add_action('wp_ajax_upk_fetch_api_biggopties', [$this, 'ajax_fetch_api_biggopties']); } public function ajax_fetch_api_biggopties() { if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'ultimate-post-kit-biggopties' ) ) { wp_send_json_error([ 'message' => 'forbidden' ]); } // No current_user_can() check before fetching and set_transient() $biggopties = $this->get_api_biggopties_data(); // ... (truncated) } --- // includes/setup-wizard/init.php - Template import handlers in 4.0.21 add_action('wp_ajax_import_elementor_template', function () { check_ajax_referer( 'setup_wizard_nonce', 'nonce' ); // Missing current_user_can check $json_url = isset( $_POST['import_url'] ) ? esc_url_raw( wp_unslash( $_POST['import_url'] ) ) : ''; $response = wp_remote_get($json_url, array( 'timeout' => 60, 'sslverify' => false )); // ... (truncated) });
Security Fix
@@ -34,14 +34,6 @@ * @return array|mixed */ private function get_api_biggopties_data() { - - // 6-hour transient cache for API response - $transient_key = 'bdt_api_biggopties'; - $cached = get_transient($transient_key); - if ($cached !== false && is_array($cached)) { - return $cached; - } - // API endpoint for biggopties - you can change this to your actual endpoint $api_url = 'https://api.sigmative.io/prod/store/api/biggopti/api-data-records'; @@ -63,8 +55,6 @@ if( isset($biggopties) && isset($biggopties->{'ultimate-post-kit'}) ) { $data = $biggopties->{'ultimate-post-kit'}; if (is_array($data)) { - $ttl = apply_filters('bdt_api_biggopties_cache_ttl', 6 * HOUR_IN_SECONDS); - set_transient($transient_key, $data, $ttl); return $data; } } @@ -312,23 +302,41 @@ wp_send_json_error([ 'message' => 'forbidden' ]); } + // Don't show biggopties on plugin/theme install and upload pages + $current_url = isset($_POST['current_url']) ? sanitize_text_field($_POST['current_url']) : ''; + + if (!empty($current_url)) { + $excluded_patterns = [ + 'plugin-install.php', + 'theme-install.php', + 'action=upload-plugin', + 'action=upload-theme' + ]; + + foreach ($excluded_patterns as $pattern) { + if (strpos($current_url, $pattern) !== false) { + wp_send_json_success([ 'html' => '' ]); + } + } + } + $biggopties = $this->get_api_biggopties_data(); $grouped_biggopties = []; if (is_array($biggopties)) { foreach ($biggopties as $index => $biggopti) { if ($this->should_show_biggopti($biggopti)) { - $biggopti_class = isset($biggopti->biggopti_class) ? $biggopti->biggopti_class : 'default-' . $index; - if (!isset($grouped_biggopties[$biggopti_class])) { - $grouped_biggopties[$biggopti_class] = $biggopti; + $display_id = isset($biggopti->display_id) ? $biggopti->display_id : 'default-' . $index; + if (!isset($grouped_biggopties[$display_id])) { + $grouped_biggopties[$display_id] = $biggopti; } } } } // Build biggopties using the same pipeline as synchronous rendering - foreach ($grouped_biggopties as $biggopti_class => $biggopti) { - $biggopti_id = isset($biggopti->id) ? $biggopti_class : $biggopti->id; + foreach ($grouped_biggopties as $display_id => $biggopti) { + $biggopti_id = isset($biggopti->id) ? $display_id : $biggopti->id; self::add_biggopti([ 'id' => 'api-biggopti-' . $biggopti_id, @@ -374,6 +382,14 @@ update_user_meta(get_current_user_id(), $id, true); } else { set_transient($id, true, $time); + + // Also store in options table for persistence + $dismissals_option = get_option('bdt_biggopti_dismissals', []); + $dismissals_option[$id] = [ + 'dismissed_at' => time(), + 'expires_at' => time() + intval($time), + ]; + update_option('bdt_biggopti_dismissals', $dismissals_option, false); } wp_send_json_success(); @@ -444,6 +460,22 @@ $expired = get_user_meta(get_current_user_id(), $biggopti_id, true); } elseif ('transient' === $biggopti['dismissible-meta']) { $expired = get_transient($biggopti_id); + + // If transient not found, check options table for persistent dismissal + if (false === $expired || empty($expired)) { + $dismissals_option = get_option('bdt_biggopti_dismissals', []); + if (isset($dismissals_option[$biggopti_id])) { + $dismissal = $dismissals_option[$biggopti_id]; + // Check if dismissal is still valid (not expired) + if (isset($dismissal['expires_at']) && time() < $dismissal['expires_at']) { + $expired = true; + } else { + // Clean up expired dismissal from options + unset($dismissals_option[$biggopti_id]); + update_option('bdt_biggopti_dismissals', $dismissals_option, false); + } + } + } } // Biggopties visible after transient expire. @@ -412,9 +412,14 @@ add_action('wp_ajax_import_elementor_template', function () { check_ajax_referer( 'setup_wizard_nonce', 'nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'ultimate-post-kit' ) ) ); + wp_die(); + } + $json_url = isset( $_POST['import_url'] ) ? esc_url_raw( wp_unslash( $_POST['import_url'] ) ) : ''; - $response = wp_remote_get($json_url, array( + $response = wp_safe_remote_get($json_url, array( 'timeout' => 60, 'sslverify' => false )); @@ -502,6 +507,11 @@ add_action('wp_ajax_import_upk_elementor_bundle_template', function () { check_ajax_referer('setup_wizard_nonce', 'nonce'); + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'ultimate-post-kit' ) ) ); + wp_die(); + } + $file_url = isset($_POST['import_url']) ? esc_url_raw(wp_unslash($_POST['import_url'])) : ''; if (!filter_var($file_url, FILTER_VALIDATE_URL) || 0 !== strpos($file_url, 'http')) { @@ -592,6 +602,11 @@ add_action('wp_ajax_import_upk_elementor_bundle_runner_template', function () { check_ajax_referer('setup_wizard_nonce', 'nonce'); + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'ultimate-post-kit' ) ) ); + wp_die(); + } + $runner = isset($_POST['runner']) ? sanitize_text_field(wp_unslash($_POST['runner'])) : ''; $sessionId = isset($_POST['sessionId']) ? sanitize_text_field(wp_unslash($_POST['sessionId'])) : '';
Exploit Outline
The exploit targets the missing `current_user_can` checks in AJAX handlers that rely solely on nonces. 1. **Authentication**: The attacker authenticates as a low-privileged user (Subscriber). 2. **Nonce Acquisition**: The attacker visits the WordPress dashboard (e.g., `/wp-admin/profile.php`). Because the plugin localizes script configurations for UI notices and the setup wizard globally in the admin area, nonces like `UltimatePostKitBiggoptiConfig.nonce` and `BDT_SetupWizard.nonce` are present in the page source. 3. **Unauthorized Action (API Fetch)**: The attacker sends a POST request to `/wp-admin/admin-ajax.php` with `action=upk_fetch_api_biggopties` and the acquired nonce. This forces the server to make a remote request to the Sigmative API and update the `bdt_api_biggopties` transient. 4. **Unauthorized Action (Template Import)**: The attacker sends a POST request to `/wp-admin/admin-ajax.php` with `action=import_elementor_template`, `import_url` (attacker-controlled URL), and the setup wizard nonce. The server will fetch the remote template data without verifying if the user has administrative privileges.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.