Digging for bugs in wordpress plugins: we rig the odds on a game of chance in a spinning prize wheel plugin (Spin Wheel < = v2.1.0), or how I found CVE-2026–0808.
I recently registered with Wordfence as a vulnerability researcher, because what's another hobby, right?! It's already been a good adventure into strengthening skills around secure code and exploitation techniques. Shout out to the Wordfence triage team, who are always helpful and shared some great insights.
I've already had several CVEs published, and thought I'd write some articles running through some of them, this is the first :) Written by human hand. lol.
Let's take a look at a spinning prize wheel Wordpress plugin, "Spin Wheel — Interactive spinning wheel that offers coupons".

This is an established WP plugin which has both a free and paid version, and has a decent range of features, over 11,000 downloads and over 600 active installs.
But we're not here to sell it, we want to hack it.
This plugin allows sites to feature a spinning prize wheel, a feature we're seeing alot in online stores lately .. a step up from the even more common "Enter your email — You've just earnt 10% off!" popups.

With Spin Wheel, a site owner can set up multiple prize wheels, and set an array of prizes, and each prize can have it's own weighting.
For example, if a prize wheel has 3 prizes A, B & C, and A has 60% weighting, and B and C each have 20% weighting, the visitor is most likely to win prize A (60% chance). They have a 20% chance of winning B, and again for C. So site owners can give a very low weighting to the most sought after/valuable prize, so it is rarely won.
Let's dig into how the spinning and prize selection of this spin wheel actually works.
I installed & activated the plugin in my local test environment, and added a new Spin Wheel with mostly default settings. Then I setup the prize list under the 'prizes' tab, with a big $100 grand prize with just 1% weighting:

When saving the spinning wheel, there's a shortcode for the wheel and a copy to clipboard button. In my case, [spin_wheel id="140"].
Let's copy that and put it into a test page, and see it in action;

We need to fill the form to trigger the spinning of the wheel — inspecting the form in Chrome devtools shows a form with no action (indicating JS handles it), and no hidden fields — but the spin wheel ID is revealed;
<form class="swp-entry-form" id="swp-entry-form-140" data-participant-limit="0" data-current-count="0">A search of the codebase in my IDE for that form ID `swp-entry-form` takes me to where the submit handling is binded to the form, in public/js/public.js;

So we want to look at handleFormSubmit method which is further down in the same file;

We can see after validation, and storing the form data within the SpinWheel object, it eventually calls this.handleSpin(), so lets take a peek at that;

So this is where it gets interesting — I expected to see it making an ajax post submission with the form data and getting a prize back from the plugin's PHP code.
But on line 857 above, it's setting prizeIndex = this.calculatePrize().
Wait..
Woah.
Client-side prize calculation.
And we can see further down, the chosen prize(prizeIndex) from this.calculatePrize() is then sent to the server to be stored, and the wheel is spun around to the prize at prizeIndex.
Let's look at this.calculatePrize() javascript:

So this works with the prize data from the wheel, and works with their weightings to pick a random prize. It then returns the index of the chosen prize (from the wheelData.prizes array), and we know it's then sent to the server via ajax, to save the winner in the database & run any actions.
Let's check the saveEntry function in that spin wheel js class, to see how it sends the winning prize index to the server;

Ok so it uses jQuery's ajax call with a nonce from swpData.nonce, so I checked swpData in the browser console to see if anything interesting was there, but the nonce was the only thing of real use.
But whilst in there, I checked the form and surrounding elements in dev tools, and spotted some json data! Sitting there between the page elements, we have the code for the grand prize!! (and the data about the rest of the prizes too when i scrolled right..)

This means a user can run a short snippet in the console to dump out the prize data, eg:
const wheelData = JSON.parse(document.querySelector('.swp-wheel-data').textContent);
console.log(wheelData.prizes);Let's try that out..

Sweet! .. we get all the prize labels, codes, and see their index.
With this, if those prize codes are active codes ready for use in their store, we can redeem any of those codes as we like — HELLO SWEET LOOT!
We could stop here and enjoy the loot.
But let's take this further and try and store the win so it's a registered prize win showing in the backend of the site..
We can see the prizes and their index shown above, and the index for the SWEETLOOT prize we want is 0. The saveEntry function that sends the winning prize index to the server uses jQuery's ajax function, so let's intercept that.
With a browser console snippet to run on a page with the spin wheel, we can hijack that ajax call, sending the prizeIndex of 0 & swpData.nonce.
(function() {
const originalAjax = jQuery.ajax;
jQuery.ajax = function(options) {
if (options.data && options.data.action === 'swp_spin_wheel') {
options.data.prize_index = 0;
console.log('prize index set to 0');
}
return originalAjax.call(this, options);
};
})();Let's try that out..
Ok, so.. I ran that js in the browser console, and filled the form & hit submit.
On the front end, I see this:

A lower end prize! No!
But remember, the exploit is tackling the ajax call to the server, it doesn't change what the js class holds as the value for prizeIndex.
Checking the ajax call in the dev tools network tab shows a different story:

And in the site backend where admins can view and verify prize wins:

Summary
Don't trust client side for things like prize selection, it can always be manipulated. Burpsuite and similar tools could have been used too, to intercept the ajax call and modify the prize index.
I submitted this to the Wordfence team in dec 2025 who notified the plugin developers, and to their credit they've been quick to fix this and released an update already, 2.1.1 :)
This finding was issued CVE-2026–0808.
🔗 Ref: https://www.cve.org/CVERecord?id=CVE-2026-0808
🔗 Wordfence listing: https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/spin-wheel/spin-wheel-210-unauthenticated-client-side-prize-manipulation-via-prize-index-parameter