CVE-2026-6498

Five Star Restaurant Reservations <= 2.7.16 - Unauthenticated Payment Bypass via PHP Type Juggling in 'payment_id' Parameter

mediumInsufficient Verification of Data Authenticity
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.7.17
Patched in
1d
Time to patch

Description

The Five Star Restaurant Reservations plugin for WordPress is vulnerable to a payment bypass via PHP type juggling in versions up to, and including, 2.7.16 This is due to the valid_payment() function using a PHP loose comparison (==) between the attacker-controlled payment_id POST parameter and the booking's stripe_payment_intent_id property. When an unauthenticated attacker submits a request to the nopriv AJAX handler rtb_stripe_pmt_succeed before the Stripe payment intent has been created for a booking (i.e., before the JavaScript-triggered create_stripe_pmtIntnt() call has stored an intent ID in post meta), the stripe_payment_intent_id property on the booking object remains null. The comparison sanitize_text_field('') == null evaluates to TRUE in PHP loose comparison, causing the payment verification check to pass with zero actual payment. This makes it possible for unauthenticated attackers to mark any existing payment_pending booking as paid without completing a Stripe payment by submitting an empty payment_id parameter.

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<=2.7.16
PublishedApril 29, 2026
Last updatedApril 30, 2026

What Changed in the Fix

Changes introduced in v2.7.17

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-6498 ## 1. Vulnerability Summary The **Five Star Restaurant Reservations** plugin (<= 2.7.16) contains a payment bypass vulnerability in its Stripe integration. The vulnerability exists in the logic that handles successful payment callbacks from Stripe's Stro…

Show full research plan

Exploitation Research Plan - CVE-2026-6498

1. Vulnerability Summary

The Five Star Restaurant Reservations plugin (<= 2.7.16) contains a payment bypass vulnerability in its Stripe integration. The vulnerability exists in the logic that handles successful payment callbacks from Stripe's Strong Customer Authentication (SCA) flow.

Specifically, the plugin uses a PHP loose comparison (==) to verify if a provided payment_id matches the stripe_payment_intent_id stored for a booking. When a booking is first created but before the client-side JavaScript has called Stripe to create a Payment Intent, the stripe_payment_intent_id property in the booking's metadata is null (or an empty string). Because sanitize_text_field('') == null evaluates to true in PHP, an attacker can submit an empty payment_id to the rtb_stripe_pmt_succeed AJAX handler to mark a booking as "Paid" without actually performing a transaction.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: rtb_stripe_pmt_succeed (accessible via wp_ajax_nopriv_rtb_stripe_pmt_succeed)
  • Vulnerable Parameter: payment_id (POST)
  • Required Parameters: booking_id, nonce
  • Authentication: Unauthenticated
  • Preconditions:
    1. Stripe payments must be enabled in the plugin settings.
    2. A booking must exist with a status that allows payment (typically payment_pending).
    3. The booking must not yet have a stripe_payment_intent_id assigned (the window between booking creation and JavaScript payment initiation).

3. Code Flow

  1. Entry Point: includes/PaymentGatewayStripe.class.php registers the AJAX handler:
    add_action( 'wp_ajax_nopriv_rtb_stripe_pmt_succeed', array( $this, 'stripe_sca_succeed' ) );
    
  2. Handler Execution: The stripe_sca_succeed() function (within rtbPaymentGatewayStripe) is triggered.
  3. Data Loading: The function retrieves booking_id and payment_id from $_POST.
  4. Validation Sink: The code performs a check similar to:
    // Conceptual representation of the vulnerable check
    if ( sanitize_text_field( $_POST['payment_id'] ) == $booking->stripe_payment_intent_id ) {
        $booking->mark_as_paid();
    }
    
  5. Type Juggling: If $_POST['payment_id'] is sent as an empty string and the booking's stripe_payment_intent_id has not been set yet (remaining null in the database/object), the loose comparison "" == null returns true.

4. Nonce Acquisition Strategy

The rtb_stripe_pmt_succeed action requires a nonce named rtb-stripe-payment. This nonce is localized when the payment form is rendered.

  1. Identify Trigger: The nonce is created in includes/PaymentGatewayStripe.class.php inside the print_payment_form() method:
    wp_localize_script(
      'rtb-stripe-payment',
      'rtb_stripe_payment',
      array(
        'nonce' => wp_create_nonce( 'rtb-stripe-payment' ),
        // ...
      )
    );
    
  2. Strategy:
    • Create a page with the [booking-form] shortcode.
    • Submit a booking via the form to reach the "Payment" step.
    • The plugin will render the payment form (which contains the Stripe JS and localized data).
    • Use browser_eval to extract the nonce.
  3. JS Path: window.rtb_stripe_payment?.nonce

5. Test Data Setup

  1. Enable Stripe: Use WP-CLI to configure the plugin to use Stripe and require a deposit.
    wp option patch update rtb-settings rtb-stripe-sca 1
    wp option patch update rtb-settings rtb-stripe-mode test
    wp option patch update rtb-settings rtb-payment-deposit-type fixed
    wp option patch update rtb-settings rtb-payment-deposit-amount 10
    
  2. Create Booking Page:
    wp post create --post_type=page --post_title="Book Now" --post_status=publish --post_content='[booking-form]'
    
  3. Generate a Booking: Use the http_request tool to submit a booking. Capture the booking_id from the response (it is often appended to the URL or returned in the response body after a successful reservation).

6. Exploitation Strategy

Step 1: Create the Booking

Perform a POST request to the page containing the [booking-form] to create a reservation.

  • URL: Site URL + /book-now/ (or wherever the shortcode is)
  • Payload: Standard booking fields (name, email, date, time, party size).
  • Goal: Obtain the booking_id of the new payment_pending booking.

Step 2: Extract the Nonce

Navigate to the payment page for that booking.

  • URL: Usually the same page or a redirect with ?booking_id=[ID] and ?step=payment.
  • Tool: browser_navigate followed by browser_eval("rtb_stripe_payment.nonce").

Step 3: Trigger the Bypass

Submit the malicious AJAX request with an empty payment_id.

  • Method: POST
  • URL: /wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=rtb_stripe_pmt_succeed&booking_id=[BOOKING_ID]&payment_id=&nonce=[NONCE]
    

7. Expected Results

  • The AJAX response should indicate success (likely {"success":true}).
  • The booking status in the database should change from payment_pending to confirmed or paid (depending on plugin settings).

8. Verification Steps

After the exploit attempt, verify the booking state using WP-CLI:

# Check the post meta for the booking
wp post meta list [BOOKING_ID]

Specifically, look for:

  1. payment_status metadata: Should be paid or confirmed.
  2. stripe_payment_intent_id: Should still be empty/null, proving bypass.
  3. Check the status via the plugin's query class:
wp eval 'global $rtb_controller; $booking = new rtbBooking( [BOOKING_ID] ); echo "Status: " . $booking->format_status();'

9. Alternative Approaches

If payment_id cannot be empty, try passing a boolean false or 0 via a JSON request (if the handler accepts application/json) to exploit broader PHP type juggling:

  • Payload: {"action": "rtb_stripe_pmt_succeed", "booking_id": [ID], "payment_id": 0, "nonce": "[NONCE]"}
  • Note: WordPress typically populates $_POST from the body, so payment_id=0 as a string might behave differently than a literal integer. However, the empty string payment_id= is the most reliable for "" == null.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Five Star Restaurant Reservations plugin for WordPress is vulnerable to a payment bypass because it uses a PHP loose comparison (==) to verify payment intent IDs. An unauthenticated attacker can exploit this by submitting an empty payment_id for a booking that hasn't yet had a Stripe intent ID stored, causing the check to evaluate as true and mark the booking as paid.

Vulnerable Code

// includes/PaymentGatewayStripe.class.php line 456
  public function valid_payment( $booking ) {

    return sanitize_text_field( $_POST['payment_id'] ) == $booking->stripe_payment_intent_id;
  }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/restaurant-reservations/2.7.16/includes/PaymentGatewayStripe.class.php /home/deploy/wp-safety.org/data/plugin-versions/restaurant-reservations/2.7.17/includes/PaymentGatewayStripe.class.php
--- /home/deploy/wp-safety.org/data/plugin-versions/restaurant-reservations/2.7.16/includes/PaymentGatewayStripe.class.php	2026-04-15 19:23:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/restaurant-reservations/2.7.17/includes/PaymentGatewayStripe.class.php	2026-04-29 19:23:10.000000000 +0000
@@ -455,7 +455,7 @@
    */
   public function valid_payment( $booking ) {
 
-    return sanitize_text_field( $_POST['payment_id'] ) == $booking->stripe_payment_intent_id;
+    return ! empty( $booking->stripe_payment_intent_id ) && sanitize_text_field( $_POST['payment_id'] ) === $booking->stripe_payment_intent_id;
   }

Exploit Outline

1. Identify a WordPress site using Five Star Restaurant Reservations with Stripe payments enabled. 2. Submit a reservation via the booking form to create a new booking with the status 'payment_pending'. 3. Capture the 'booking_id' and the 'rtb-stripe-payment' nonce (found in the localized JavaScript object 'rtb_stripe_payment' on the payment step page). 4. Send a POST request to /wp-admin/admin-ajax.php with the action 'rtb_stripe_pmt_succeed'. 5. Include the valid booking_id and nonce, but leave the 'payment_id' parameter empty. 6. Because the booking was just created and does not yet have a 'stripe_payment_intent_id' assigned in the database (it is null), the loose comparison '"" == null' returns true, bypassing the payment check and confirming the booking.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.