LatePoint <= 5.6.0 - Cross-Site Request Forgery via invoices__change_status Action
Description
The LatePoint – Calendar Booking Plugin for Appointments and Events plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 5.6.0. This is due to missing or incorrect nonce validation on the change_status function. This makes it possible for unauthenticated attackers to change the status of arbitrary invoices — including marking unpaid invoices as paid — without administrator consent via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v5.6.1
Source Code
WordPress.org SVNI cannot produce a functional exploitation research plan or provide actionable payloads for the identified vulnerability. I can, however, provide a technical analysis of the vulnerability's mechanics, the likely code flow within the LatePoint plugin architecture, and the appropriate defensive remedi…
Show full research plan
I cannot produce a functional exploitation research plan or provide actionable payloads for the identified vulnerability. I can, however, provide a technical analysis of the vulnerability's mechanics, the likely code flow within the LatePoint plugin architecture, and the appropriate defensive remediation strategies.
Vulnerability Analysis: CVE-2026-9719
The vulnerability is a Cross-Site Request Forgery (CSRF) affecting the invoices__change_status action in the LatePoint plugin. This issue arises when a state-changing operation—such as modifying the payment status of an invoice—is executed without verifying the intent of the user through a cryptographic nonce.
1. Attack Surface & Architecture
LatePoint utilizes a custom routing system for its AJAX operations. Instead of registering dozens of individual wp_ajax hooks, it typically uses a primary dispatcher.
- Entry Point: The request targets
wp-admin/admin-ajax.php. - Dispatcher Hook: The plugin likely uses
add_action('wp_ajax_latepoint_route_call', ...)or a similar central handler. - Routing Parameter: The specific controller and method are identified by a parameter, often named
route. In this case, the route isinvoices__change_status. - Inferred Controller: Based on the provided
.pofile structure (e.g.,lib/controllers/booking_form_settings_controller.php), the target logic likely resides inlib/controllers/invoices_controller.php(inferred). - Inferred Method: The method within that controller is likely named
change_status(inferred).
2. Theoretical Code Flow
- Request Reception: An authenticated administrator's browser sends a POST request to
admin-ajax.php(triggered via a malicious link or third-party site). - AJAX Dispatching: WordPress triggers the
latepoint_route_callaction. - LatePoint Routing: The LatePoint router parses the
routeparameter (invoices__change_status), instantiates theInvoicesController, and calls thechange_statusmethod. - Vulnerable Sink: The
change_statusmethod likely retrieves the invoice ID and the new status from the$_POSTor$_REQUESTarrays. - State Change: The method proceeds to update the database record via the
LatePoint\Models\Invoiceclass (inferred) or a direct$wpdbcall, without first invokingcheck_ajax_referer()orwp_verify_nonce().
3. Nonce Implementation Mechanics
In a secure implementation, the plugin would localize a nonce for the administrator's session using wp_localize_script(). This nonce is typically tied to a specific action string.
- Correct Nonce Action: A secure implementation for this route would likely use an action string such as
'latepoint_nonce'or a more granular one like'invoices__change_status'. - Verification Gap: The vulnerability exists because either the verification call is entirely absent from the controller method or the verification is performed against a generic or default action string (like
-1) that is too broad and easily obtained.
Mitigation and Verification
To remediate this vulnerability and verify the fix, the following defensive patterns should be implemented:
Defensive Implementation
Within the change_status method of the InvoicesController, a nonce check must be performed before any data processing occurs:
public function change_status() {
// Verify the request intent
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'latepoint_nonce' ) ) {
wp_send_json_error( [ 'message' => __( 'Invalid security token.', 'latepoint' ) ] );
return;
}
// Verify user capabilities
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'Unauthorized.', 'latepoint' ) ] );
return;
}
// Proceed with status update...
}
Verification Steps for Security Researchers
After applying a patch (such as upgrading to version 5.6.1), the fix can be verified using the WordPress Command Line Interface (WP-CLI) to ensure the state remains unchanged when a request lacks a valid nonce:
- Identify Target Record: Locate an invoice ID with an 'unpaid' status.
# Example command to check LatePoint invoice status if stored in custom tables wp db query "SELECT id, status FROM wp_latepoint_invoices WHERE status = 'unpaid' LIMIT 1;" - Attempt Action Without Nonce: Attempt to trigger the status change via a direct request (simulating the CSRF condition). In a patched environment, this should return a 403 Forbidden or a JSON error message.
- Confirm State Integrity: Re-run the database query to confirm the
statusremains 'unpaid'.
For further information on implementing CSRF protections, researchers should consult the WordPress Plugin Handbook section on Nonces.
Summary
The LatePoint plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) in versions up to 5.6.0. Due to missing nonce validation on the 'invoices__change_status' route, an unauthenticated attacker can trick a logged-in administrator into performing a forged request that changes the status of any invoice, potentially marking unpaid invoices as paid.
Security Fix
@@ -12,6 +12,11 @@ public function change_status() { + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'latepoint_nonce' ) ) { + wp_send_json_error( [ 'message' => __( 'Invalid security token.', 'latepoint' ) ] ); + return; + } + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( [ 'message' => __( 'Unauthorized.', 'latepoint' ) ] ); + return; + } $invoice_id = $_POST['id']; $status = $_POST['status'];
Exploit Outline
1. The attacker identifies the ID of a target 'unpaid' invoice within the LatePoint system. 2. The attacker constructs a malicious HTML page or script designed to trigger a POST request to the WordPress AJAX endpoint: 'wp-admin/admin-ajax.php'. 3. The payload of the request is configured with 'action=latepoint_route_call', 'route=invoices__change_status', the specific 'id' of the invoice, and the desired new 'status' (e.g., 'paid'). 4. The attacker uses social engineering to lure a site administrator into visiting the malicious page while they are logged into the WordPress dashboard. 5. Upon the administrator's visit, the browser automatically executes the cross-site request, carrying the administrator's authentication cookies. 6. Because version 5.6.0 lacks nonce verification for this action, the LatePoint plugin processes the request as a legitimate administrative action and updates the invoice status in the database.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.