Simple CAPTCHA Alternative with Cloudflare Turnstile <= 1.38.0 - Broken Authorization
Description
The Simple CAPTCHA Alternative with Cloudflare Turnstile plugin for WordPress is vulnerable to broken authorization in all versions up to, and including, 1.38.0. This makes it possible for unauthenticated attackers to use one successful Turnstile submission for multiple checks
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=1.38.0What Changed in the Fix
Changes introduced in v1.38.1
Source Code
WordPress.org SVN# Detailed Research Plan: CVE-2026-40799 - Simple CAPTCHA Alternative with Cloudflare Turnstile ## 1. Vulnerability Summary The **Simple CAPTCHA Alternative with Cloudflare Turnstile** plugin (<= 1.38.0) is vulnerable to **Broken Authorization**. The plugin uses PHP sessions to store a flag indicat…
Show full research plan
Detailed Research Plan: CVE-2026-40799 - Simple CAPTCHA Alternative with Cloudflare Turnstile
1. Vulnerability Summary
The Simple CAPTCHA Alternative with Cloudflare Turnstile plugin (<= 1.38.0) is vulnerable to Broken Authorization. The plugin uses PHP sessions to store a flag indicating that a Turnstile challenge has been successfully completed. However, it fails to uniquely bind these validation flags to specific forms or actions, and in certain integrations (specifically WooCommerce), it fails to invalidate (unset) the session flag after use. This allows an unauthenticated attacker to solve a single CAPTCHA challenge and reuse that successful validation state for multiple subsequent submissions or across different protected forms.
2. Attack Vector Analysis
- Endpoint: Any form integrated with the plugin (e.g., WooCommerce Checkout, MemberPress Register, Ultimate Member Login).
- Vulnerable Integration: Specifically
inc/integrations/ecommerce/woocommerce.php. - Payload Parameter:
cf-turnstile-response(the Turnstile token). - Authentication: None required (Unauthenticated).
- Preconditions:
- The plugin must be active and configured with valid Cloudflare API keys.
- Turnstile must be enabled for WooCommerce Checkout or other supported forms.
3. Code Flow
The vulnerability stems from the persistence of session-based validation flags.
WooCommerce Integration Flow (inc/integrations/ecommerce/woocommerce.php):
- Entry: A user submits a WooCommerce checkout form (
/?wc-ajax=checkout). - Hook:
woocommerce_checkout_processtriggerscfturnstile_woo_checkout_check(). - Session Check:
if(isset($_SESSION['cfturnstile_checkout_checked']) && wp_verify_nonce( sanitize_text_field($_SESSION['cfturnstile_checkout_checked']), 'cfturnstile_checkout_check' )) { return; // Bypasses further checks if session flag exists } - Validation: If the flag is missing,
cfturnstile_check()is called to verify thecf-turnstile-responsetoken against the Cloudflare API. - Setting the Flag: If successful, a nonce is stored in the session:
$nonce = wp_create_nonce( 'cfturnstile_checkout_check' ); $_SESSION['cfturnstile_checkout_checked'] = $nonce; - SINK (The Bug): Unlike the EDD integration (
edd.php), the WooCommerce integration never callsunset($_SESSION['cfturnstile_checkout_checked']). The flag remains in the session, allowing all subsequent checkout attempts to skip thecfturnstile_check()entirely.
Cross-Form Reuse Flow:
Integrations like MemberPress (memberpress.php) and Ultimate Member (ultimate-member.php) both use the exact same session key cfturnstile_login_checked and nonce action cfturnstile_login_check. Solving Turnstile for one effectively authorizes the session for the other.
4. Nonce Acquisition Strategy
This exploit does not require obtaining a WordPress nonce from the frontend to initiate the attack. Instead, it exploits the plugin's internal use of nonces within the PHP session to track validation state.
However, to trigger the initial validation that sets the session flag, the attacker must solve a Turnstile challenge. In a test environment, this is done using Cloudflare's "Always Pass" dummy keys.
- Identify Sitekey: Navigate to the homepage or login page and look for the
data-sitekeyattribute in the Turnstilediv. - Solve Turnstile: Use the
browser_evaltool to interact with the Turnstile widget and retrieve a valid token, or use the dummy token1x00000000000000000000AAif the site is configured with test keys.
5. Exploitation Strategy
We will demonstrate the vulnerability by bypassing Turnstile on a WooCommerce checkout form after a single valid submission.
Step 1: Initial Submission (Valid Solve)
Perform a checkout attempt with a valid Turnstile token. Provide incomplete billing info so the checkout fails for non-CAPTCHA reasons (e.g., missing address), but the plugin still validates the CAPTCHA and sets the session flag.
- Request:
POST /?wc-ajax=checkout - Content-Type:
application/x-www-form-urlencoded - Parameters:
billing_first_name:Attackerbilling_email:attacker@example.compayment_method:codcf-turnstile-response:1x00000000000000000000AA(Always Pass token)woocommerce-process-checkout-nonce: (Extract from page source if needed)
- Expected Result: Response should contain WooCommerce validation errors (e.g., "Billing Address is required"), but no Turnstile error. The server will set a session cookie.
Step 2: Bypass Submission (Reusing Solve)
Perform a second checkout attempt without a Turnstile token, using the session cookie from Step 1.
- Request:
POST /?wc-ajax=checkout - Content-Type:
application/x-www-form-urlencoded - Cookies: Use the session cookie received from the first request.
- Parameters:
billing_first_name:Attacker2billing_email:attacker2@example.com- (Omit
cf-turnstile-response)
- Expected Result: The response will NOT contain the CAPTCHA error ("Please verify that you are human"). It will instead proceed to standard WooCommerce validation, proving the Turnstile check was bypassed.
6. Test Data Setup
- Configure Plugin: Use WP-CLI to set Cloudflare test keys and enable WooCommerce integration.
wp option update cfturnstile_key "1x00000000000000000000AA" wp option update cfturnstile_secret "1x00000000000000000000BB" wp option update cfturnstile_woo_checkout 1 wp option update cfturnstile_tested "yes" - Setup WooCommerce: Ensure WooCommerce is active, a product is created, and it's in the cart.
wp plugin activate woocommerce wp post create --post_type=product --post_title="Test Product" --post_status=publish # Use browser to add to cart
7. Expected Results
- Vulnerable: The second request (without Turnstile token) returns WooCommerce validation errors related to form fields, but does not trigger the CAPTCHA failure message.
- Fixed: The second request returns the error:
Please verify that you are human.(the default message fromcfturnstile_failed_message()).
8. Verification Steps
After the HTTP requests, verify the logic via WP-CLI:
- Check
inc/integrations/ecommerce/woocommerce.phpfor the absence ofunset($_SESSION['cfturnstile_checkout_checked']);in thecfturnstile_woo_checkout_checkfunction. - Compare with
inc/integrations/ecommerce/edd.phpwhich correctly unsets its session variable.
9. Alternative Approaches
If WooCommerce is not present, target Ultimate Member and MemberPress simultaneously:
- Solve Turnstile for Ultimate Member Login.
- Immediately attempt a MemberPress Registration in the same session.
- If MemberPress accepts the request without a new Turnstile token, "Cross-Integration Reuse" is confirmed.
Summary
The Simple CAPTCHA Alternative with Cloudflare Turnstile plugin for WordPress (<= 1.38.0) is vulnerable to Broken Authorization due to improper session management. The plugin stores a successful CAPTCHA validation flag in the PHP session but fails to invalidate it after use in certain integrations like WooCommerce, allowing unauthenticated attackers to solve one CAPTCHA and reuse the 'authorized' session for multiple automated submissions.
Vulnerable Code
// inc/integrations/ecommerce/woocommerce.php lines 117-121 // Start session if (!session_id()) { session_start(); } // Check if already validated if(isset($_SESSION['cfturnstile_checkout_checked']) && wp_verify_nonce( sanitize_text_field($_SESSION['cfturnstile_checkout_checked']), 'cfturnstile_checkout_check' )) { return; } --- // inc/integrations/ecommerce/woocommerce.php lines 128-135 if( !$skip && (!$guest || ( $guest && !is_user_logged_in() )) ) { $check = cfturnstile_check(); $success = $check['success']; if($success != true) { wc_add_notice( cfturnstile_failed_message(), 'error'); } else { $nonce = wp_create_nonce( 'cfturnstile_checkout_check' ); $_SESSION['cfturnstile_checkout_checked'] = $nonce; $cfturnstile_wc_checkout_ran = true; // Mark as executed } }
Security Fix
@@ -113,30 +113,28 @@ } } - // Start session - if (!session_id()) { session_start(); } - // Check if already validated - if(isset($_SESSION['cfturnstile_checkout_checked']) && wp_verify_nonce( sanitize_text_field($_SESSION['cfturnstile_checkout_checked']), 'cfturnstile_checkout_check' )) { - return; - } - // Check if guest only enabled $guest = esc_attr( get_option('cfturnstile_guest_only') ); - // Check + // Check — always require a fresh Turnstile token (tokens are single-use). if( !$skip && (!$guest || ( $guest && !is_user_logged_in() )) ) { $check = cfturnstile_check(); $success = $check['success']; if($success != true) { wc_add_notice( cfturnstile_failed_message(), 'error'); - } else { - $nonce = wp_create_nonce( 'cfturnstile_checkout_check' ); - $_SESSION['cfturnstile_checkout_checked'] = $nonce; - $cfturnstile_wc_checkout_ran = true; // Mark as executed } + // Always mark as executed so the second hook doesn't re-verify + // the same (now consumed) token and produce duplicate errors. + $cfturnstile_wc_checkout_ran = true; } } add_action('woocommerce_store_api_checkout_update_order_from_request', 'cfturnstile_woo_checkout_block_check', 10, 2); function cfturnstile_woo_checkout_block_check($order, $request) { + // Prevent duplicate execution within a single request. + static $cfturnstile_wc_block_checkout_ran = false; + if ( $cfturnstile_wc_block_checkout_ran ) { + return; + } + // Skip if Turnstile disabled for payment method $skip = 0; if ( $request->get_method() === 'POST' ) { @@ -177,31 +175,25 @@ } } - // Start session - if (!session_id()) { session_start(); } - // Check if already validated - if(isset($_SESSION['cfturnstile_checkout_checked']) && wp_verify_nonce( sanitize_text_field($_SESSION['cfturnstile_checkout_checked']), 'cfturnstile_checkout_check' )) { - return; - } - // Check if guest only enabled $guest = esc_attr( get_option('cfturnstile_guest_only') ); - // Check + // Check — always require a fresh Turnstile token (tokens are single-use). if( !$skip && (!$guest || ( $guest && !is_user_logged_in() )) ) { $extensions = $request->get_param( 'extensions' ); $token = ( is_array( $extensions ) && isset( $extensions['simple-cloudflare-turnstile']['token'] ) ) ? $extensions['simple-cloudflare-turnstile']['token'] : ''; if ( empty( $token ) ) { + $cfturnstile_wc_block_checkout_ran = true; throw new \Exception( cfturnstile_failed_message() ); } $check = cfturnstile_check( $token ); $success = $check['success']; + // Always mark as executed so duplicate hooks don't re-verify + // the same (now consumed) token and produce duplicate errors. + $cfturnstile_wc_block_checkout_ran = true; if($success != true) { throw new \Exception( cfturnstile_failed_message() ); - } else { - $nonce = wp_create_nonce( 'cfturnstile_checkout_check' ); - $_SESSION['cfturnstile_checkout_checked'] = $nonce; } } }
Exploit Outline
The exploit targets forms protected by the plugin (specifically WooCommerce Checkout). An unauthenticated attacker first submits a legitimate request with a valid `cf-turnstile-response` token. The plugin validates this token and sets a session-based flag (`cfturnstile_checkout_checked`) to indicate the user has passed the CAPTCHA. Because the plugin fails to unset this session variable after the initial form processing, the attacker can then perform subsequent automated POST requests while including the initial session cookie. The plugin sees the existing session flag and bypasses the requirement for a new Turnstile token, enabling automated bot attacks against the protected form.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.