CVE-2026-40799

Simple CAPTCHA Alternative with Cloudflare Turnstile <= 1.38.0 - Broken Authorization

mediumImproper Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
1.38.1
Patched in
4d
Time to patch

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

Technical Details

Affected versions<=1.38.0
PublishedMay 8, 2026
Last updatedMay 11, 2026

What Changed in the Fix

Changes introduced in v1.38.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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):

  1. Entry: A user submits a WooCommerce checkout form (/?wc-ajax=checkout).
  2. Hook: woocommerce_checkout_process triggers cfturnstile_woo_checkout_check().
  3. 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
    }
    
  4. Validation: If the flag is missing, cfturnstile_check() is called to verify the cf-turnstile-response token against the Cloudflare API.
  5. Setting the Flag: If successful, a nonce is stored in the session:
    $nonce = wp_create_nonce( 'cfturnstile_checkout_check' );
    $_SESSION['cfturnstile_checkout_checked'] = $nonce;
    
  6. SINK (The Bug): Unlike the EDD integration (edd.php), the WooCommerce integration never calls unset($_SESSION['cfturnstile_checkout_checked']). The flag remains in the session, allowing all subsequent checkout attempts to skip the cfturnstile_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.

  1. Identify Sitekey: Navigate to the homepage or login page and look for the data-sitekey attribute in the Turnstile div.
  2. Solve Turnstile: Use the browser_eval tool to interact with the Turnstile widget and retrieve a valid token, or use the dummy token 1x00000000000000000000AA if 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: Attacker
    • billing_email: attacker@example.com
    • payment_method: cod
    • cf-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: Attacker2
    • billing_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

  1. 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"
    
  2. 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 from cfturnstile_failed_message()).

8. Verification Steps

After the HTTP requests, verify the logic via WP-CLI:

  1. Check inc/integrations/ecommerce/woocommerce.php for the absence of unset($_SESSION['cfturnstile_checkout_checked']); in the cfturnstile_woo_checkout_check function.
  2. Compare with inc/integrations/ecommerce/edd.php which correctly unsets its session variable.

9. Alternative Approaches

If WooCommerce is not present, target Ultimate Member and MemberPress simultaneously:

  1. Solve Turnstile for Ultimate Member Login.
  2. Immediately attempt a MemberPress Registration in the same session.
  3. If MemberPress accepts the request without a new Turnstile token, "Cross-Integration Reuse" is confirmed.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simple-cloudflare-turnstile/1.38.0/inc/integrations/ecommerce/woocommerce.php /home/deploy/wp-safety.org/data/plugin-versions/simple-cloudflare-turnstile/1.38.1/inc/integrations/ecommerce/woocommerce.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simple-cloudflare-turnstile/1.38.0/inc/integrations/ecommerce/woocommerce.php	2026-04-06 15:52:50.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simple-cloudflare-turnstile/1.38.1/inc/integrations/ecommerce/woocommerce.php	2026-04-13 17:45:52.000000000 +0000
@@ -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.