Total Upkeep <= 1.17.1 - Missing Authorization to Unauthenticated Rollback Cancellation
Description
The Total Upkeep – WordPress Backup Plugin plus Restore & Migrate by BoldGrid plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the 'wp_ajax_cli_cancel' function in all versions up to, and including, 1.17.1. This makes it possible for unauthenticated attackers to cancel a pending rollback, potentially preventing a WordPress installation from automatically reverting a failed update.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=1.17.1What Changed in the Fix
Changes introduced in v1.17.2
Source Code
WordPress.org SVNThis research plan targets **CVE-2026-3143**, a missing authorization vulnerability in the **Total Upkeep** plugin for WordPress. This vulnerability allows an unauthenticated attacker to cancel a pending site rollback, which is a critical safety feature that automatically restores a site if an updat…
Show full research plan
This research plan targets CVE-2026-3143, a missing authorization vulnerability in the Total Upkeep plugin for WordPress. This vulnerability allows an unauthenticated attacker to cancel a pending site rollback, which is a critical safety feature that automatically restores a site if an update fails.
1. Vulnerability Summary
- Vulnerability: Missing Authorization (Unauthenticated data modification).
- Vulnerable Action:
cli_cancel. - Plugin: Total Upkeep (slug:
boldgrid-backup). - Affected Versions: <= 1.17.1.
- Fixed Version: 1.17.2.
- Nature of Flaw: The plugin registers an AJAX handler (either
wp_ajax_cli_cancelorwp_ajax_nopriv_cli_cancel) that calls a function to delete theboldgrid_backup_pending_rollbacksite option and clear related cron jobs. The handler lacks capability checks (current_user_can) and nonce validation, allowing unauthenticated requests to trigger it.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php. - HTTP Method:
POST(standard for AJAX) orGET. - Action Parameter:
action=cli_cancel. - Authentication: Not required (Unauthenticated).
- Preconditions: A "pending rollback" must exist for the exploit to have a measurable impact (i.e., the
boldgrid_backup_pending_rollbacksite option must be set).
3. Code Flow
- Entry Point: An HTTP request hits
admin-ajax.php?action=cli_cancel. - Hook Trigger: WordPress fires the
wp_ajax_nopriv_cli_cancel(orwp_ajax_cli_cancel) hook. - Handler Execution: The plugin's registered handler function (likely in an admin core class, calling
Boldgrid_Backup_Admin_Auto_Rollback::cancel) is executed. - Vulnerable Method:
Boldgrid_Backup_Admin_Auto_Rollback::cancel()(inadmin/class-boldgrid-backup-admin-auto-rollback.php):- It calls
$this->core->cron->delete_cron_entries( 'restore' )to stop the scheduled rollback. - It calls
$this->core->settings->delete_rollback_option()to delete theboldgrid_backup_pending_rollbacksite option.
- It calls
- Sink:
delete_site_option( 'boldgrid_backup_pending_rollback' ).
4. Nonce Acquisition Strategy
The vulnerability description ("Missing Authorization") and the severity (Medium, Unauthenticated) strongly suggest that no nonce is validated for this specific action.
- Verification: If the exploit fails with a
403or-1response, check for nonce usage in the plugin files by searching forcheck_ajax_refererorwp_verify_noncenear thecli_cancelaction registration. - Localization: If a nonce is required, it would likely be localized in
boldgrid-backup-admin-backup-now.jsunder a variable name likeboldgrid_backup_admin_backup_now(inferred from the script handleboldgrid-backup-admin-backup-nowinenqueue_backup_scripts).
5. Exploitation Strategy
The goal is to prove that an unauthenticated user can delete the boldgrid_backup_pending_rollback option.
Step 1: Setup "Pending Rollback" State
An attacker can't easily trigger a real failed update, so we will simulate the state by manually creating the site option.
- Command:
wp site option update boldgrid_backup_pending_rollback '{"filepath":"/tmp/dummy-backup.zip","deadline":9999999999}' --format=json
Step 2: Send the Exploit Request
- Tool:
http_request - Method:
POST - URL:
{{base_url}}/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=cli_cancel
Step 3: Verification
Confirm that the site option has been deleted.
6. Test Data Setup
- Plugin Installation: Install Total Upkeep version 1.17.1.
- Configuration: Enable the plugin.
- Data Creation: Populate the rollback option to provide a target for deletion.
wp site option update boldgrid_backup_pending_rollback '{"filepath":"/tmp/poc.zip","mode":"backup"}' --format=json - Verification of Setup:
wp site option get boldgrid_backup_pending_rollback
7. Expected Results
- Successful Request: The server returns a
200 OKresponse (likely with0,1, or an empty response, as many AJAX handlers don't return specific data). - System Impact: The site option
boldgrid_backup_pending_rollbackis removed from the database. - Cron Impact: Any crons with the hook containing
restoreare removed (viewable viawp cron event list).
8. Verification Steps
- Database Check:
Expectation: The command should return an error or empty result (Option does not exist).wp site option get boldgrid_backup_pending_rollback - Cron Check:
Expectation: No entries should be found.wp cron event list | grep restore
9. Alternative Approaches
If cli_cancel does not respond to unauthenticated requests, check if the plugin registers a REST API endpoint for the same purpose:
- Search for
register_rest_routein the plugin directory. - Look for routes containing
rollbackorcancel. - If a REST endpoint exists, check its
permission_callbackfor__return_trueor missing capability checks.
If the action name is slightly different, use grep to find the registration:
grep -r "wp_ajax_.*cancel" .
Verify if wp_ajax_nopriv_ is present for that action.
Summary
Total Upkeep (<= 1.17.1) contains a missing authorization vulnerability in its rollback cancellation mechanism. An unauthenticated attacker can cancel a pending site rollback by sending a request to the unprivileged AJAX handler, potentially preventing the site from automatically reverting to a stable state after a failed update.
Vulnerable Code
// admin/class-boldgrid-backup-admin-auto-rollback.php line 1205 public function wp_ajax_cli_cancel() { $backup_id_match = ! empty( $_GET['backup_id'] ) && $this->core->get_backup_identifier() === sanitize_key( $_GET['backup_id'] ); // phpcs:ignore WordPress.CSRF.NonceVerification.NoNonceVerification if ( $backup_id_match ) { $this->cancel(); wp_send_json_success( __( 'Rollback canceled', 'boldgrid-backup' ) ); } else { wp_send_json_error( __( 'Error: Backup ID match failed', 'boldgrid-backup' ) ); } }
Security Fix
@@ -205,6 +205,9 @@ // Remove WP option boldgrid_backup_pending_rollback. $this->core->settings->delete_rollback_option(); + + // Remove the one-time CLI cancel secret. + delete_site_option( 'boldgrid_backup_cli_cancel_secret' ); } /** @@ -1194,16 +1197,23 @@ /** * Callback function for canceling a pending rollback from the cli process. * - * This admin-ajax call is unprovileged, so that the CLI script can make the call. - * The only validation that we use is the backup identifier. - * Nobody will be trying to cancel rollbacks (with a 15-minute window) anyways. + * This admin-ajax call is unprivileged, so that the CLI script can make the call. + * Validation requires both the backup identifier and a one-time random secret that + * was generated when the restore cron job was scheduled. * * @since 1.10.7 */ public function wp_ajax_cli_cancel() { - $backup_id_match = ! empty( $_GET['backup_id'] ) && $this->core->get_backup_identifier() === sanitize_key( $_GET['backup_id'] ); // phpcs:ignore WordPress.CSRF.NonceVerification.NoNonceVerification + // phpcs:ignore WordPress.CSRF.NonceVerification.Recommended + $backup_id_match = ! empty( $_GET['backup_id'] ) && $this->core->get_backup_identifier() === sanitize_key( $_GET['backup_id'] ); + + $stored_secret = get_site_option( 'boldgrid_backup_cli_cancel_secret', '' ); - if ( $backup_id_match ) { + // phpcs:ignore WordPress.CSRF.NonceVerification.Recommended + $secret_match = ! empty( $stored_secret ) && ! empty( $_GET['cli_cancel_secret'] ) && + hash_equals( $stored_secret, sanitize_text_field( wp_unslash( $_GET['cli_cancel_secret'] ) ) ); + + if ( $backup_id_match && $secret_match ) { $this->cancel(); wp_send_json_success( __( 'Rollback canceled', 'boldgrid-backup' ) ); } else {
Exploit Outline
To exploit this vulnerability, an attacker targets the unprivileged AJAX action used for CLI-initiated rollback cancellations. 1. Target Endpoint: The standard WordPress AJAX endpoint `/wp-admin/admin-ajax.php`. 2. Payload: The request must include the `action` parameter set to either `cli_cancel` or `boldgrid_cli_cancel_rollback` (depending on the internal mapping) and a valid `backup_id` parameter. 3. Identifying Backup ID: The `backup_id` is an identifier generated by the plugin for the site, often discoverable or deterministic (e.g., based on hashes of site metadata). 4. Execution: By sending a GET or POST request with these parameters, an unauthenticated user triggers the `Boldgrid_Backup_Admin_Auto_Rollback::cancel()` method. 5. Impact: This method deletes the `boldgrid_backup_pending_rollback` site option and clears any scheduled restoration cron jobs. This effectively neutralizes the safety net that would have automatically restored the site if a current update process failed, leaving the site in a broken state if the update does not complete successfully.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.