Introduction
The case study focuses on the CVE-2023–6553 vulnerability, discovered by the NEX Team, which exploits the WordPress Backup Migration plugin for version 1.3.7 and below. The CVSS score is 9.8, making this flaw critical for the target software. It provides an opportunity for unauthenticated remote code execution.
The problem can be traced back to an LFI flaw, which can be leveraged further to achieve a complete RCE via PHP filters.
Within this case study, we will look into how the flaw works internally and explore the data flow in terms of user input processing, reaching the vulnerable sink, and finally leading to code execution.
Target Overview
The Backup Migration plugin by BackupBliss is a WordPress plugin designed to facilitate website backups, restoration, and migration. It provides functionality such as scheduled backups and data transfer between environments.
From a security perspective, the plugin exposes an attack surface through its internal request handling mechanisms, within the file:
/wp-content/plugins/backup-backup/includes/backup-heart.phpThis file processes incoming requests and utilizes user-controlled input to construct file paths. Due to insufficient validation and sanitization, this behavior introduces a Local File Inclusion (LFI) vulnerability.
An attacker can manipulate this input to control the included file path, which under certain conditions can be escalated to Remote Code Execution (RCE) through techniques such as PHP filter chain abuse.
Discovery and Analysis
Source Point Identification
The vulnerability originates in the file:
/wp-content/plugins/backup-backup/includes/backup-heart.phpAt the top of this file, the code checks whether the getallheaders() function is available and, if so, uses it to populate a $fields array from all incoming HTTP request headers:
if (isFunctionEnabled('getallheaders')) {
$fields = getallheaders();
}getallheaders() is an alias for apache_request_headers() and returns an associative array of every header in the HTTP request — meaning all header values are entirely attacker-controlled.
Constant Initialization from Headers
The code then uses these header values to define several PHP constants:
define('ABSPATH', $fields['content-abs']);
define('WP_CONTENT_DIR', $fields['content-content']);
define('BMI_CONFIG_DIR', $fields['content-configdir']);
define('BMI_BACKUPS', $fields['content-backups']);
define('BMI_ROOT_DIR', $fields['content-dir']);
define('BMI_INCLUDES', BMI_ROOT_DIR . 'includes');
define('BMI_SAFELIMIT', intval($fields['content-safelimit']));The critical line here is:
define('BMI_ROOT_DIR', $fields['content-dir']);
define('BMI_INCLUDES', BMI_ROOT_DIR . 'includes');
The content-dir header value flows directly into BMI_ROOT_DIR, which is then concatenated with the string includes to form BMI_INCLUDES. No sanitization or validation is performed at any point.
The Vulnerable Sink
Further down in the file, within a try block, the constant reaches a require_once call:
require_once BMI_INCLUDES . '/bypasser.php';
This is the vulnerable sink. The final included path is constructed as:
{content-dir header value} + "includes" + "/bypasser.php"Data Flow Summary
Step Value Attacker sends header Content-Dir: hello/ BMI_ROOT_DIR hello/ BMI_INCLUDES hello/includes Final require_once path hello/includes/bypasser.php
This is a classic Local File Inclusion (LFI) vulnerability — attacker-controlled input reaches a file inclusion function without sanitization.
Why Direct LFI is Limited
A naive exploitation attempt (e.g., pointing content-dir to a known sensitive file) is blocked by the appended suffix includes/bypasser.php. Since modern PHP versions have removed null byte (%00) support in file paths, the traditional null-byte truncation technique is not viable here.
This shifts the exploitation strategy to PHP Filter Chains.
Exploitation via PHP Filter Chain
Concept
PHP's php://filter stream wrapper allows chaining multiple transformations on a data stream. By chaining iconv encoding transformations with base64-decode operations, it's possible to construct a filter chain that generates arbitrary PHP code at runtime — without needing to write any file to disk.
The key insight is that php://temp can serve as an in-memory resource. When a crafted filter chain is passed to require_once, the output of the chain is interpreted and executed as PHP code, achieving Remote Code Execution (RCE).
Reference: Synacktiv's PHP Filter Chain research
Generating the Payload
Using the php_filter_chain_generator:
python php_filter_chain_generator.py --chain '<?php system($_POST[0]); ?>'This generates a long filter chain string starting with:
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...
/resource=php://tempAt runtime, PHP processes these chained transformations and produces the payload:
<?php system($_POST[0]); ?>which is then executed by the PHP engine.
Manual Exploitation Steps
- Navigate to the plugin endpoint:
/wp-content/plugins/backup-backup/includes/backup-heart.php
2. The file enforces POST-only access:
// Allow only POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
exit;
}3. Change the request method to POST in Burp Suite.
4. Add the Content-Dir header with the generated filter chain as its value.
5. Send a POST parameter 0 with the OS command to execute (e.g., id, whoami).

Note: The response from
backup-heart.phpdoes not return command output directly, since the file doesn't echo responses. Command execution can be confirmed via time-based techniques (e.g.,sleep 10) or by uploading a dedicated webshell.
Automated Exploitation
I developed it's full exploit script automates the above process and provides a stable webshell shell by:

- Sending the filter chain to trigger RCE
- Using that RCE to upload a PHP webshell to a web-accessible path
- Providing a cleaner way for command execution
The script also includes a --check flag to safely verify whether a target is vulnerable without executing any commands, and formats output using rich tables for readability.
Patch Analysis
Here's the Patch provided by official developers.
Analzing this Patch reveals that they included a security check function which they used on other headers too including the vulnerable header Content-Dir
30
31 // Filter and prevent PHP filter injection
32 function filterChainFix($content) {
33
34 // Make sure it exist and is string
35 if (!is_string($content)) die("Incorrect parameters.");
36
37 // Check if it's not larger than max allowed path length (default systems)
38 if (strlen($content) > 256) die("Incorrect parameters.");
39
40 // Check if the path does not contain "php:"
41 if (strpos($content, "php:")) die("Incorrect parameters.");
42
43 // Check if the path contain "|", it's not possible to use this character with our backups paths
44 if (strpos($content, "|")) die("Incorrect parameters.");
45
46 // Check if the directory/file exist otherwise fail
47 if (!(is_dir($content) || file_exists($content))) die("Incorrect parameters.");
48
49 // Return correct content
50 return $content;
51
52 }It looks secure, first checking the content is it string or not
then length check because filterchains are usually too lengthy
on 3rd check it's checking that the string doesn't contain php://
checking if the value contains | or not
then final check is directory exist or not
then it will return the value
62 define('BMI_ROOT_DIR', $fields['content-dir']); // OLD and unsanitized
85 define('BMI_ROOT_DIR', filterChainFix($fields['content-dir'])); // Patched and sanatized
118 require_once BMI_INCLUDES . '/bypasser.php'; // OLD and Vulnerable to LFI to RCE using PHP Filter Chain
141 require_once filterChainFix(BMI_INCLUDES) . '/bypasser.php'; // Patched using sanatization function which prevents Attackers to input malicious LFI payloadthen the filterChainFix checking function is implemented on headers too like content-abs
Remediation
The root cause is the absence of any validation on HTTP header values before they are used in file inclusion. The fix, released in version 1.3.8, moves these values away from attacker-controlled headers and adds strict path validation. Users should update to 1.3.8 or later immediately.