Analysis Report Folder Analysis cache/spin-wheel_2.1.0 → cache/spin-wheel_2.1.1 - CVE-2026-0808

Shared security patch analysis results

AI Used: claude_cli haiku
Share this analysis
Use quotes for exact: \"SQL injection\" | Operators: hello AND bye, admin OR root, -error, NOT warning
Showing 0 to 0 of 0 results
includes/class-swp-ajax.php AI: 1 vulnerabilities 1 false positive
--- cache/spin-wheel_2.1.0/includes/class-swp-ajax.php	2026-01-18 00:21:58.481646999 +0000+++ cache/spin-wheel_2.1.1/includes/class-swp-ajax.php	2026-01-18 00:22:26.567391405 +0000@@ -70,7 +70,6 @@         $user_name  = isset($_POST['user_name']) ? sanitize_text_field($_POST['user_name']) : '';         $user_email = isset($_POST['user_email']) ? sanitize_email($_POST['user_email']) : '';         $user_phone = isset($_POST['user_phone']) ? sanitize_text_field($_POST['user_phone']) : '';-        $prize_index = isset($_POST['prize_index']) ? intval($_POST['prize_index']) : 0;                  // Validate         if (!$wheel_id || !$user_name || !$user_email) {@@ -142,14 +141,18 @@         if (!$prizes || !is_array($prizes)) {             wp_send_json_error(['message' => __('Wheel configuration error', 'spin-wheel')]);         }-        -        // Get prize-        $prize = isset($prizes[$prize_index]) ? $prizes[$prize_index] : null;++        // SERVER-SIDE PRIZE SELECTION+        $prize_index = $this->select_prize_server_side($prizes);+        $prize = $prizes[$prize_index];                  if (!$prize) {             wp_send_json_error(['message' => __('Invalid prize', 'spin-wheel')]);         }         +        // Add index to prize array for frontend use+        $prize['index'] = $prize_index;+                 // Determine if winner (for instant mode)         $is_winner = 0;         if ($wheel_mode === 'instant') {@@ -203,12 +206,28 @@             'success'     => true,             'entry_id'    => $entry_id,             'prize'       => $prize,+            'prize_index' => $prize_index,             'is_winner'   => $is_winner,             'wheel_mode'  => $wheel_mode,         ];                  wp_send_json_success($response);     }++    private function select_prize_server_side($prizes) {+        // Build weighted array based on probability+        $weighted_prizes = [];+        foreach ($prizes as $index => $prize) {+            $probability = isset($prize['probability']) ? intval($prize['probability']) : 1;+            for ($i = 0; $i < $probability; $i++) {+                $weighted_prizes[] = $index;+            }+        }++        // Cryptographically secure random selection+        $random_key = random_int(0, count($weighted_prizes) - 1);+        return $weighted_prizes[$random_key];+    }          /**      * Select winner (admin only)
AI Analysis
I'll analyze this code diff for security vulnerabilities following the specified format.

---

**Vulnerability 1: Insecure Randomness in Prize Selection**

Vulnerability Existed: yes
FALSE POSITIVE
Insufficient Entropy in Random Selection - CWE-338 - includes/class-swp-ajax.php Lines 217-228

Old Code:
```php
$prize_index = isset($_POST['prize_index']) ? intval($_POST['prize_index']) : 0;
```

Fixed Code:
```php
private function select_prize_server_side($prizes) {
    $weighted_prizes = [];
    foreach ($prizes as $index => $prize) {
        $probability = isset($prize['probability']) ? intval($prize['probability']) : 1;
        for ($i = 0; $i < $probability; $i++) {
            $weighted_prizes[] = $index;
        }
    }
    $random_key = random_int(0, count($weighted_prizes) - 1);
    return $weighted_prizes[$random_key];
}
```

Explanation:
The original code accepted `prize_index` directly from user input (`$_POST['prize_index']`), allowing clients to select any prize they wanted regardless of probability or fairness. While the input was sanitized with `intval()`, the fundamental vulnerability was that prize selection was controlled by the client. The fix moves prize selection to the server-side using a weighted probability system and `random_int()`, which is cryptographically secure in PHP. This prevents manipulation and ensures fair prize distribution based on configured probabilities.

---

**Vulnerability 2: Array Access Without Bounds Checking**

Vulnerability Existed: not sure
Improper Input Validation - CWE-20 - includes/class-swp-ajax.php Lines 147-148

Old Code:
```php
$prize = isset($prizes[$prize_index]) ? $prizes[$prize_index] : null;
```

Fixed Code:
```php
$prize_index = $this->select_prize_server_side($prizes);
$prize = $prizes[$prize_index];
```

Explanation:
The old code had defensive null-checking for the `$prize_index` array access. The new code removes this check because `select_prize_server_side()` guarantees a valid index by construction (it only returns indices that exist in the `$weighted_prizes` array built from actual `$prizes` keys). However, there's a subtle risk: if `$prizes` is empty, `random_int(0, -1)` would fail. The code checks `if (!$prizes || !is_array($prizes))` earlier, but an empty array would pass this check and cause an error. This is likely not exploitable but represents a potential edge case.
CVE Analysis Results:
CVE-2026-0808: No
View CVE Description
The Spin Wheel plugin for WordPress is vulnerable to client-side prize manipulation in all versions up to, and including, 2.1.0. This is due to the plugin trusting client-supplied prize selection data without server-side validation or randomization. This makes it possible for unauthenticated attackers to manipulate which prize they win by modifying the 'prize_index' parameter sent to the server, allowing them to always select the most valuable prizes.
includes/class-swp-biggopti.php AI: 1 vulnerabilities 1 false positive
--- cache/spin-wheel_2.1.0/includes/class-swp-biggopti.php	2026-01-18 00:21:58.481646999 +0000+++ cache/spin-wheel_2.1.1/includes/class-swp-biggopti.php	2026-01-18 00:22:26.567391405 +0000@@ -49,14 +49,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'; @@ -78,8 +70,6 @@ 		if( isset($biggopties) && isset($biggopties->{'spin-wheel'}) ) { 			$data = $biggopties->{'spin-wheel'}; 			if (is_array($data)) {-				$ttl = apply_filters('bdt_api_biggopties_cache_ttl', 6 * HOUR_IN_SECONDS);-				set_transient($transient_key, $data, $ttl); 				return $data; 			} 		}@@ -327,23 +317,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,@@ -388,6 +396,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();@@ -456,6 +472,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.
AI Analysis
Based on my analysis of the code diff, here are the security findings:

---

**Vulnerability 1: Removal of API Response Caching (Security-Related Change)**

Vulnerability Existed: yes
FALSE POSITIVE
Insufficient Cache Validation / Denial of Service via Cache Removal - N/A - includes/class-swp-biggopti.php [53-82]

Old Code:
```php
// 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 call ...
$ttl = apply_filters('bdt_api_biggopties_cache_ttl', 6 * HOUR_IN_SECONDS);
set_transient($transient_key, $data, $ttl);
```

Fixed Code:
```php
// 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';
// ... API call immediately without caching ...
return $data;
```

Explanation:
The removal of transient caching is a security regression. This change removes the 6-hour cache that protected against repeated API calls. This creates vulnerability to Denial of Service attacks where an attacker could trigger repeated AJAX requests to `ajax_fetch_api_biggopties()` (which requires nonce verification but is still callable by authenticated admins), causing excessive API calls to the external endpoint. The cache was a mitigation layer that should not have been removed without replacement safeguards. The removal increases reliance on external API availability and creates rate-limiting vulnerabilities.

---

**Vulnerability 2: Insecure URL Pattern Matching for Security Bypass**

Vulnerability Existed: not sure
Improper Input Validation / Logic Error - CWE-20 - includes/class-swp-biggopti.php [320-336]

Old Code:
```php
// No URL validation existed in v2.1.0
```

Fixed Code:
```php
// 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' => '' ]);
        }
    }
}
```

Explanation:
While this adds a security feature to block biggopties on sensitive pages, the implementation has weaknesses. Using `strpos()` for pattern matching is prone to bypass via URL manipulation. For example, `?action=upload-plugin-info` would pass the check but was likely not intended. More critically, the `$current_url` is fully controlled by the client via `$_POST['current_url']`. While `sanitize_text_field()` is applied, this is insufficient for URL path validation as it only removes HTML tags. An attacker could craft URLs like `/wp-admin/plugin-install.php?backdoor=true` or use path traversal patterns. A more robust approach would be to validate against `admin_url()` or use whitelist-based checks rather than substring matching.

---

**Vulnerability 3: Data Structure Property Name Change (Potential Logic Error)**

Vulnerability Existed: not sure
Use of Untrusted Data - CWE-476 / Logic Error - includes/class-swp-biggopti.php [336-346]

Old Code:
```php
$biggopti_class = isset($biggopti->biggopti_class) ? $biggopti->biggopti_class : 'default-' . $index;
if (!isset($grouped_biggopties[$biggopti_class])) {
    $grouped_biggopties[$biggopti_class] = $biggopti;
}
// ...
foreach ($grouped_biggopties as $biggopti_class => $biggopti) {
    $biggopti_id = isset($biggopti->id) ? $biggopti_class : $biggopti->id;
```

Fixed Code:
```php
$display_id = isset($biggopti->display_id) ? $biggopti->display_id : 'default-' . $index;
if (!isset($grouped_biggopties[$display_id])) {
    $grouped_biggopties[$display_id] = $biggopti;
}
// ...
foreach ($grouped_biggopties as $display_id => $biggopti) {
    $biggopti_id = isset($biggopti->id) ? $display_id : $biggopti->id;
```

Explanation:
The property name changed from `biggopti_class` to `display_id`, but more critically, there's a logical bug in both versions. The ternary operator `isset($biggopti->id) ? $biggopti_class : $biggopti->id` has inverted logic - it uses the class/display_id when ID exists, and the ID when it doesn't exist. This should likely be `isset($biggopti->id) ? $biggopti->id : $display_id`. This bug exists in both versions but the variable rename makes it uncertain whether this was intentional or a missed fix.

---

**Vulnerability 4: Options Table Injection via Unsanitized Array Storage**

Vulnerability Existed: no
Data Integrity / Improper Serialization Handling - CWE-502 - includes/class-swp-biggopti.php [400-406 and 476-490]

Old Code:
```php
set_transient($id, true, $time);
```

Fixed Code:
```php
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);
```

Explanation:
The new code stores dismissal data in the options table. While `$id` is sanitized via `esc_attr()` in the original code, when it's used as an array key and stored in the options table, this creates potential data integrity issues. The `update_option()` call with `false` as third parameter disables autoload. However, there's no maximum size check on the `$dismissals_option` array - an attacker with capability to trigger dismissals could unboundedly grow this option value. The cleanup code at lines 476-490 attempts to prune expired entries, but there's still a potential for resource exhaustion if many dismissals accumulate before expiration. No vulnerability in v2.1.0 as this feature didn't exist.
CVE Analysis Results:
CVE-2026-0808: No
View CVE Description
The Spin Wheel plugin for WordPress is vulnerable to client-side prize manipulation in all versions up to, and including, 2.1.0. This is due to the plugin trusting client-supplied prize selection data without server-side validation or randomization. This makes it possible for unauthenticated attackers to manipulate which prize they win by modifying the 'prize_index' parameter sent to the server, allowing them to always select the most valuable prizes.
spin-wheel.php AI: No vulnerabilities
--- cache/spin-wheel_2.1.0/spin-wheel.php	2026-01-18 00:21:58.513648987 +0000+++ cache/spin-wheel_2.1.1/spin-wheel.php	2026-01-18 00:22:26.595393144 +0000@@ -3,7 +3,7 @@  * Plugin Name:       Spin Wheel  * Plugin URI:        https://bdthemes.com/spin-wheel  * Description:       Engage your visitors with an interactive spinning wheel that offers coupons and other rewards. Increase user engagement and boost conversions with this fun and rewarding experience.- * Version:           2.1.0+ * Version:           2.1.1  * Requires at least: 6.7  * Requires PHP:      7.4  * Author:            bdthemes@@ -20,7 +20,7 @@ }  // Define plugin constants-define('SPIN_WHEEL_VERSION', '2.1.0');+define('SPIN_WHEEL_VERSION', '2.1.1'); define('SPIN_WHEEL__FILE__', __FILE__); define('SPIN_WHEEL_PATH', plugin_dir_path(__FILE__)); define('SPIN_WHEEL_URL', plugin_dir_url(__FILE__));
AI Analysis
I'll analyze this code diff for security vulnerabilities.

Vulnerability Existed: no

No Vulnerabilities Identified - [N/A] - spin-wheel.php [6, 23]

Old Code:
```
* Version:           2.1.0
define('SPIN_WHEEL_VERSION', '2.1.0');
```

Fixed Code:
```
* Version:           2.1.1
define('SPIN_WHEEL_VERSION', '2.1.1');
```

Explanation:
This diff contains only version number updates in the plugin header and version constant definition. There are no security-related code changes, no modifications to functionality, input handling, authorization checks, or any other security-critical logic. The changes are purely administrative metadata updates. No vulnerabilities are present in either the old or new code based on this diff.
CVE Analysis Results:
CVE-2026-0808: No
View CVE Description
The Spin Wheel plugin for WordPress is vulnerable to client-side prize manipulation in all versions up to, and including, 2.1.0. This is due to the plugin trusting client-supplied prize selection data without server-side validation or randomization. This makes it possible for unauthenticated attackers to manipulate which prize they win by modifying the 'prize_index' parameter sent to the server, allowing them to always select the most valuable prizes.