CVE-2026-39518

EventPrime – Events Calendar, Bookings and Tickets <= 4.3.0.0 - Authenticated (Subscriber+) Insecure Direct Object Reference

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
4.3.0.1
Patched in
11d
Time to patch

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

Technical Details

Affected versions<=4.3.0.0
PublishedApril 20, 2026
Last updatedApril 30, 2026

What Changed in the Fix

Changes introduced in v4.3.0.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 with ep_ based on the method name).
  • HTTP Method: POST
  • Payload Parameters:
    • action: ep_cancel_current_booking_process
    • security: A valid WordPress nonce for the action event-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_data post meta.

3. Code Flow

  1. Entry Point: The AJAX request is received by admin-ajax.php and dispatched to EventM_Ajax_Service::cancel_current_booking_process.
  2. Nonce Check: wp_verify_nonce( $_POST['security'], 'event-registration-form-nonce' ) is called. This nonce is available to any user viewing an event page.
  3. Parameter Extraction:
    • $event_id = absint( $_POST['event_id'] ); (User-controlled object reference).
    • $ticket_data = json_decode( stripslashes( $_POST['ticket_data'] ) );
  4. Data Retrieval: $event_seat_data = get_post_meta( $event_id, 'em_seat_data', true );. It fetches meta for the provided ID without checking permissions.
  5. Modification Logic: The code iterates through the user-provided $ticket_data and matches area_id and seat uid (row/column). If a seat is found with type hold, it is reset:
    if( $seat->type == 'hold' ) {
        $seat->type = 'general';
        $seat->hold_time = '';
        // ...
    }
    
  6. 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.

  1. Identify Script Loading: Create an event and enable bookings.
  2. Navigate: Use browser_navigate to visit the event's permalink.
  3. Extract Nonce: Use browser_eval to extract the nonce from the ep_ajax_obj global variable:
    // Verbatim from common EventPrime localization patterns
    window.ep_ajax_obj?.security 
    
  4. 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

  1. Create a "Target Event" (ID $X$) as Admin.
  2. Manually set the em_seat_data meta for Event $X$ to simulate a seat on hold.
  3. 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

Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/eventprime-event-calendar-management/4.3.0.0/includes/class-ep-ajax.php /home/deploy/wp-safety.org/data/plugin-versions/eventprime-event-calendar-management/4.3.0.1/includes/class-ep-ajax.php
--- /home/deploy/wp-safety.org/data/plugin-versions/eventprime-event-calendar-management/4.3.0.0/includes/class-ep-ajax.php	2026-03-02 05:59:32.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/eventprime-event-calendar-management/4.3.0.1/includes/class-ep-ajax.php	2026-03-05 09:32:14.000000000 +0000
@@ -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.