CVE-2026-24362

Ultimate Post Kit Addons for Elementor <= 4.0.21 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
4.0.22
Patched in
10d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=4.0.21
PublishedMarch 18, 2026
Last updatedMarch 27, 2026
Affected pluginultimate-post-kit

What Changed in the Fix

Changes introduced in v4.0.22

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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_biggopties or ultimate-post-kit-biggopties
    • _wpnonce: A nonce associated with the UltimatePostKitBiggoptiConfig JavaScript object.
    • (For dismiss action): id, meta, time.
  • Preconditions:
    • User must be authenticated (Subscriber or higher).
    • The plugin ultimate-post-kit must be active.

3. Code Flow

  1. Entry Point: The UltimatePostKit\Biggopties class registers AJAX hooks in its __construct method (found in admin/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']);
    
  2. AJAX Handler: When a Subscriber sends a request with action=upk_fetch_api_biggopties, the ajax_fetch_api_biggopties method is executed.
  3. 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_biggopties using set_transient().
  4. Vulnerability: Neither dismiss nor ajax_fetch_api_biggopties contains 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).

  1. Login: Authenticate as a Subscriber.
  2. Navigate: Access the WordPress dashboard at /wp-admin/profile.php.
  3. Extract Nonce: The nonce is stored in the global JavaScript variable UltimatePostKitBiggoptiConfig.
  4. JS Variable: window.UltimatePostKitBiggoptiConfig.nonce
  5. Procedure:
    • Use browser_navigate to go to /wp-admin/profile.php.
    • Use browser_eval to run: return window.UltimatePostKitBiggoptiConfig ? window.UltimatePostKitBiggoptiConfig.nonce : null;.

5. Exploitation Strategy

Phase 1: Force API Data Fetch

  1. Request: POST to /wp-admin/admin-ajax.php.
  2. Payload:
    action=upk_fetch_api_biggopties&_wpnonce=[EXTRACTED_NONCE]
    
  3. Expected Outcome: The server returns a JSON response (likely success: true or a data object) and performs an outbound request to the Sigmative API, overwriting the bdt_api_biggopties transient.

Phase 2: Dismiss Admin Notice (Alternative)

  1. Request: POST to /wp-admin/admin-ajax.php.
  2. Payload:
    action=ultimate-post-kit-biggopties&id=test-id&meta=test-meta&time=3600&_wpnonce=[EXTRACTED_NONCE]
    
  3. Expected Outcome: Modification of notice state (likely stored in options or user meta), demonstrating unauthorized state change.

6. Test Data Setup

  1. Users: Create a user with the subscriber role.
  2. Plugin State: Ensure "Ultimate Post Kit" is active.
  3. Cleanup: Delete the transient bdt_api_biggopties before 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_biggopties transient should exist/be updated in the database.
  • The logs (if monitored) should show a wp_remote_get request to https://api.sigmative.io/....

8. Verification Steps

After performing the HTTP request as a Subscriber, verify the impact using WP-CLI:

  1. Check Transient:
    wp transient get bdt_api_biggopties
    
    If the transient contains data, the unauthorized action was successful.
  2. Verify Nonce Usage: Confirm the response body from the http_request matches the expected format from ajax_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=show as a Subscriber.
  • Verification: Use browser_eval to check if the class .bdt-setup-wizard-active is added to the body tag, indicating the wizard UI was successfully loaded for a low-privileged user.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.21/admin/admin-biggopti.php /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.22/admin/admin-biggopti.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.21/admin/admin-biggopti.php	2026-01-05 06:42:16.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.22/admin/admin-biggopti.php	2026-01-12 12:41:24.000000000 +0000
@@ -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.
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.21/includes/setup-wizard/init.php /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.22/includes/setup-wizard/init.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.21/includes/setup-wizard/init.php	2026-01-05 06:42:16.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ultimate-post-kit/4.0.22/includes/setup-wizard/init.php	2026-01-12 12:41:24.000000000 +0000
@@ -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.