EventPrime – Events Calendar, Bookings and Tickets <= 4.3.0.0 - Authenticated (Subscriber+) Insecure Direct Object Reference
Description
The EventPrime – Events Calendar, Bookings and Tickets plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 4.3.0.0 due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform unauthorized actions.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=4.3.0.0What Changed in the Fix
Changes introduced in v4.3.0.1
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-39518 ## 1. Vulnerability Summary The **EventPrime** plugin (<= 4.3.0.0) contains an **Insecure Direct Object Reference (IDOR)** vulnerability in its AJAX handling logic, specifically within the `EventM_Ajax_Service::cancel_current_booking_process` method (an…
Show full research plan
Exploitation Research Plan - CVE-2026-39518
1. Vulnerability Summary
The EventPrime plugin (<= 4.3.0.0) contains an Insecure Direct Object Reference (IDOR) vulnerability in its AJAX handling logic, specifically within the EventM_Ajax_Service::cancel_current_booking_process method (and potentially others like save_checkout_field). The cancel_current_booking_process function accepts an event_id and ticket_data directly from user input and modifies the em_seat_data post meta of the specified event without verifying if the current user has permission to manage that event or if they are the owner of the booking process being cancelled. This allows a Subscriber-level user to manipulate the seating status (e.g., releasing "held" seats) of any event.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
ep_cancel_current_booking_process(Note: Actions in this plugin are typically prefixed withep_based on the method name). - HTTP Method:
POST - Payload Parameters:
action:ep_cancel_current_booking_processsecurity: A valid WordPress nonce for the actionevent-registration-form-nonce.event_id: The ID of the target event to manipulate.ticket_data: A JSON string representing the seats to "cancel" (revert from 'hold' to 'general').
- Authentication: Required (Subscriber level or higher).
- Preconditions: The target event must have seating data stored in the
em_seat_datapost meta.
3. Code Flow
- Entry Point: The AJAX request is received by
admin-ajax.phpand dispatched toEventM_Ajax_Service::cancel_current_booking_process. - Nonce Check:
wp_verify_nonce( $_POST['security'], 'event-registration-form-nonce' )is called. This nonce is available to any user viewing an event page. - Parameter Extraction:
$event_id = absint( $_POST['event_id'] );(User-controlled object reference).$ticket_data = json_decode( stripslashes( $_POST['ticket_data'] ) );
- Data Retrieval:
$event_seat_data = get_post_meta( $event_id, 'em_seat_data', true );. It fetches meta for the provided ID without checking permissions. - Modification Logic: The code iterates through the user-provided
$ticket_dataand matchesarea_idand seatuid(row/column). If a seat is found with typehold, it is reset:if( $seat->type == 'hold' ) { $seat->type = 'general'; $seat->hold_time = ''; // ... } - Sink:
update_post_meta( $event_id, 'em_seat_data', maybe_serialize( $event_seat_data ) );saves the modified data back to the database for the target event.
4. Nonce Acquisition Strategy
The nonce event-registration-form-nonce is required. It is generated using wp_create_nonce('event-registration-form-nonce') and is typically localized for the frontend booking scripts.
- Identify Script Loading: Create an event and enable bookings.
- Navigate: Use
browser_navigateto visit the event's permalink. - Extract Nonce: Use
browser_evalto extract the nonce from theep_ajax_objglobal variable:// Verbatim from common EventPrime localization patterns window.ep_ajax_obj?.security - Alternative: If not in
ep_ajax_obj, check for a hidden input field:document.querySelector('input[name="security"]')?.value
5. Exploitation Strategy
Step 1: Data Setup
- Create a "Target Event" (ID $X$) as Admin.
- Manually set the
em_seat_datameta for Event $X$ to simulate a seat on hold. - Create a Subscriber user.
Step 2: Payload Construction
Construct a ticket_data JSON that targets a specific area and seat:
[
{
"seats": [
{
"area_id": "1",
"seat_data": [
{
"uid": "0-0"
}
]
}
]
}
]
Step 3: Execution
Perform the attack as the Subscriber user using the http_request tool:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Body:
action=ep_cancel_current_booking_process&security=[NONCE]&event_id=[TARGET_EVENT_ID]&ticket_data=[JSON_PAYLOAD] - Content-Type:
application/x-www-form-urlencoded
6. Test Data Setup
Use WP-CLI
Summary
The EventPrime plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) via several AJAX endpoints because it fails to perform adequate authorization checks on user-supplied IDs. This allows authenticated attackers with Subscriber-level access or higher to perform unauthorized actions such as releasing event seats, modifying checkout fields, and accessing or updating booking information belonging to other users or events.
Vulnerable Code
// File: includes/class-ep-ajax.php // Lines 11-82 (approx.) 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'] ) ); $event_seat_data = get_post_meta( $event_id, 'em_seat_data', true ); if( ! empty( $event_seat_data ) ) { // ... (Logic to iterate through ticket_data and reset seats) ... $update = update_post_meta( $event_id, 'em_seat_data', maybe_serialize( $event_seat_data ) ); wp_send_json_success($update); } } } --- // File: includes/class-ep-ajax.php // Lines 87-94 (approx.) public function save_checkout_field() { check_ajax_referer( 'save-checkout-fields', 'security' ); $response = array(); parse_str( wp_unslash( $_POST['data'] ), $data ); // ... (logic to insert or update checkout fields in database) ... } --- // File: includes/class-ep-ajax.php // Lines 2581-2610 (approx.) public function update_event_booking_action() { $sanitizer = new EventPrime_sanitizer; parse_str( wp_unslash( $_POST['data'] ), $data ); $ep_functions = new Eventprime_Basic_Functions; if( wp_verify_nonce( $data['ep_update_event_booking_nonce'], 'ep_update_event_booking' ) ) { $ep_event_booking_id = ( ! empty( $data['ep_event_booking_id'] ) ? $data['ep_event_booking_id'] : '' ); if( ! empty( $ep_event_booking_id ) ) { // ... (Fetches and updates attendee meta without checking if the user owns the booking) ... update_post_meta( $ep_event_booking_id, 'em_attendee_names', $ep_booking_attendde_field );
Security Fix
@@ -77,11 +77,14 @@ - public function save_checkout_field() { - check_ajax_referer( 'save-checkout-fields', 'security' ); - - $response = array(); - parse_str( wp_unslash( $_POST['data'] ), $data ); + public function save_checkout_field() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'You are not allowed to manage checkout fields.', 'eventprime-event-calendar-management' ) ) ); + } + check_ajax_referer( 'save-checkout-fields', 'security' ); + + $response = array(); + parse_str( wp_unslash( $_POST['data'] ?? '' ), $data ); @@ -135,9 +138,12 @@ - // delete the checkout field - public function delete_checkout_field(){ - check_ajax_referer( 'delete-checkout-fields', 'security' ); + // delete the checkout field + public function delete_checkout_field(){ + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'You are not allowed to manage checkout fields.', 'eventprime-event-calendar-management' ) ) ); + } + check_ajax_referer( 'delete-checkout-fields', 'security' ); @@ -2578,37 +2584,71 @@ - public function update_event_booking_action() { - $sanitizer = new EventPrime_sanitizer; - parse_str( wp_unslash( $_POST['data'] ), $data ); - $ep_functions = new Eventprime_Basic_Functions; - if( wp_verify_nonce( $data['ep_update_event_booking_nonce'], 'ep_update_event_booking' ) ) { - $ep_event_booking_id = ( ! empty( $data['ep_event_booking_id'] ) ? $data['ep_event_booking_id'] : '' ); - if( ! empty( $ep_event_booking_id ) ) { - $booking_controller = new EventPrime_Bookings; - $single_booking = $booking_controller->load_booking_detail( $ep_event_booking_id ); - if( ! empty( $single_booking ) ) { - if( ! empty( $data['ep_booking_attendee_fields'] ) ) { - $ep_booking_attendde_field = $sanitizer->sanitize($data['ep_booking_attendee_fields']); - update_post_meta( $ep_event_booking_id, 'em_attendee_names', $ep_booking_attendde_field ); - } - } - wp_send_json_success( array( 'message' => esc_html__( 'Booking Updated Successfully.', 'eventprime-event-calendar-management' ), 'redirect_url' => esc_url( $ep_functions->ep_get_custom_page_url( 'profile_page' ) ) ) ); - } else{ - wp_send_json_error( array( 'message' => esc_html__( 'Booking id can\'t be null. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); - } - } else{ - wp_send_json_error( array( 'message' => esc_html__( 'Security check failed. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); - } - } + + public function update_event_booking_action() { + $sanitizer = new EventPrime_sanitizer; + parse_str( wp_unslash( $_POST['data'] ?? '' ), $data ); + $ep_functions = new Eventprime_Basic_Functions; + if ( empty( $data['ep_update_event_booking_nonce'] ) || ! wp_verify_nonce( $data['ep_update_event_booking_nonce'], 'ep_update_event_booking' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'Security check failed. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); + } + + if ( ! is_user_logged_in() ) { + wp_send_json_error( array( 'message' => esc_html__( 'You are not authorised to update this booking.', 'eventprime-event-calendar-management' ) ) ); + } + + $ep_event_booking_id = ( ! empty( $data['ep_event_booking_id'] ) ? absint( $data['ep_event_booking_id'] ) : 0 ); + if( ! empty( $ep_event_booking_id ) ) { + $booking_post = get_post( $ep_event_booking_id ); + if ( empty( $booking_post ) || $booking_post->post_type !== 'em_booking' ) { + wp_send_json_error( array( 'message' => esc_html__( 'Invalid booking id. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); + } + + $current_user_id = get_current_user_id(); + $booking_user_id = (int) get_post_meta( $ep_event_booking_id, 'em_user', true ); + $can_update_booking = current_user_can( 'edit_post', $ep_event_booking_id ) + || ( $booking_user_id > 0 && $booking_user_id === $current_user_id ); + + if ( ! $can_update_booking ) { + wp_send_json_error( array( 'message' => esc_html__( 'You are not authorised to update this booking.', 'eventprime-event-calendar-management' ) ) ); + } + + $booking_controller = new EventPrime_Bookings; + $single_booking = $booking_controller->load_booking_detail( $ep_event_booking_id ); + if( ! empty( $single_booking ) ) { + if( ! empty( $data['ep_booking_attendee_fields'] ) ) { + $ep_booking_attendde_field = $sanitizer->sanitize($data['ep_booking_attendee_fields']); + update_post_meta( $ep_event_booking_id, 'em_attendee_names', $ep_booking_attendde_field ); + } + } + wp_send_json_success( array( 'message' => esc_html__( 'Booking Updated Successfully.', 'eventprime-event-calendar-management' ), 'redirect_url' => esc_url( $ep_functions->ep_get_custom_page_url( 'profile_page' ) ) ) ); + } else{ + wp_send_json_error( array( 'message' => esc_html__( 'Booking id can\'t be null. Please refresh the page and try again later.', 'eventprime-event-calendar-management' ) ) ); + } + }
Exploit Outline
To exploit this vulnerability, an attacker must be authenticated (e.g., as a Subscriber). They first obtain a valid security nonce from the frontend (such as `event-registration-form-nonce` or `ep_update_event_booking`), which is typically available in localized JavaScript objects or hidden input fields on event pages. The attacker then sends a POST request to `/wp-admin/admin-ajax.php`. For example, to manipulate event seating via the `ep_cancel_current_booking_process` action, the attacker provides the `event_id` of the target event and a JSON-formatted `ticket_data` payload specifying the seats to reset. Because the backend code uses `get_post_meta` and `update_post_meta` using the user-provided `event_id` without verifying the user's permissions or relationship to the event, the attacker can effectively release 'held' seats for any event on the site. Similar methodologies apply to endpoints managing checkout fields or booking details.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.