SECURITY ADVISORY / 01

CVE-2023-6553 Exploit & Vulnerability Analysis

Complete CVE-2023-6553 security advisory with proof of concept (PoC), exploit details, and patch analysis for backup-backup.

backup-backup products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An unauthenticated attacker can execute arbitrary PHP code by sending a specially crafted HTTP request with malicious values in the content-abs and content-dir header fields, which are then passed unsanitized to define() and require_once() statements.

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded

action=bmi_heart_beat&content-abs=php://filter/convert.base64-encode/resource=/etc/passwd&content-dir=/var/www&content-configdir=/var/www&content-content=/var/www&content-backups=/var/www

The server parses these fields directly from the POST body into the $fields array without validation, then uses them to redefine ABSPATH and include critical plugin files. An attacker observing the response will see error messages leaking filesystem paths, or in optimized attacks, will see the PHP filter chain executed — for example, base64-encoded copies of sensitive files reflected in error logs or debug output. When require_once is called with a filter-wrapped path, the injected filter runs before the file inclusion logic, allowing the attacker to read arbitrary files or, in chained variants, execute code.

What the Patch Did

Before

define('ABSPATH', $fields['content-abs']);
define('BMI_ROOT_DIR', $fields['content-dir']);
require_once BMI_INCLUDES . '/bypasser.php';
$request = new BMI_Backup_Heart(true,
  $fields['content-configdir'],
  $fields['content-content'],
  $fields['content-backups'],
  $fields['content-abs'],
  $fields['content-dir'],

After

define('ABSPATH', filterChainFix($fields['content-abs']));
define('BMI_ROOT_DIR', filterChainFix($fields['content-dir']));
require_once filterChainFix(BMI_INCLUDES) . '/bypasser.php';
$request = new BMI_Backup_Heart(true,
  $fields['content-configdir'],
  $fields['content-content'],
  $fields['content-backups'],
  filterChainFix($fields['content-abs']),
  filterChainFix($fields['content-dir']),

The patch introduced a custom validation function filterChainFix() that wraps every user-controlled input before it reaches a security-critical operation. This function implements multi-layered input filtering: it validates that the input is a string, enforces a maximum length of 256 characters (blocking PHP filter chains which are verbose), rejects any path containing the php: protocol prefix or pipe character (|, used to chain filters), and finally verifies that the resulting path actually exists on the filesystem using is_dir() or file_exists(). Together, these checks prevent PHP stream filter injection by making it impossible to pass filter URLs to define() or require_once().

Root Cause

CWE-74 (Improper Neutralization of Special Elements in Output Used by a Downstream Component—'Injection') combined with CWE-22 (Improper Limitation of a Pathname to a Restricted Directory).

The backup-heart.php file processes the AJAX action bmi_heart_beat, extracting user input from POST parameters (content-abs, content-dir, content-configdir, content-content, content-backups) into an associative array $fields without any prior sanitization. This input then flows directly into two dangerous sinks: the define() function (which sets PHP constants to arbitrary string values) and the require_once() statement (which loads PHP files). PHP's stream wrapper functionality means that a string like php://filter/convert.base64-encode/resource=/etc/passwd is a valid file path in these contexts, and the PHP engine will execute the filter chain before attempting file operations. The vulnerability exists because there is no whitelist, type check, or protocol validation between the request boundary and the sink — the attacker's controlled value travels the entire dataflow unchecked.

Why It Works

The load-bearing line is the blocklist check for php: and | characters inside filterChainFix(). Without it, an attacker bypasses all other checks: the length limit can be circumvented by chaining multiple smaller payloads, and filesystem existence checks fail when the attacker hasn't created the malicious file locally. The pipe character is essential because PHP filters are chained with the syntax php://filter/convert.X|convert.Y/resource=..., so rejecting | alone breaks the injection syntax. The length limit (256 characters) and type check (is_string()) are defense-in-depth: they catch obvious attacks early and prevent type juggling bypass tricks. The filesystem existence validation (is_dir() / file_exists()) ensures that only paths to real resources can be dereferenced, preventing attackers from inventing arbitrary paths wholesale. If the engineer had omitted only the php: and | blocklist, the exploit would still work; if they had omitted only the length limit, an attacker could still build a working filter chain; but together, these layers force an attacker to provide a real filesystem path with no protocol prefix and no filter syntax — turning the injection primitive into a harmless path disclosure.

Hardening Checklist

  • Use wp_safe_remote_post() with strict sslverify and timeout parameters for any inter-plugin or cross-origin communication; avoid custom header parsing without using WordPress HTTP APIs that apply baseline validation.
  • Apply sanitize_file_name() or sanitize_text_field() to any user input before passing it to define(), require(), include(), or file_get_contents(); never pass raw $_POST or $_GET values directly to these functions.
  • Validate file paths against a whitelist of allowed directories using realpath() and checking that the resolved path is within an expected prefix; for example, if (strpos(realpath($user_path), realpath(WP_CONTENT_DIR)) !== 0) { wp_die('Invalid path'); }.
  • Reject any input containing protocol schemes (://) or dangerous characters (|, ;, $, backticks) using a regex or blocklist before file operations; PHP stream wrappers are a common attack surface in file-handling plugins.
  • Test file inclusion paths with wp_cache_get_multiple() or similar caching to avoid repeated filesystem access, and log all file operations to catch injection attempts in the audit trail.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2023-6553

Frequently asked questions about CVE-2023-6553

What is CVE-2023-6553?

CVE-2023-6553 is a security vulnerability identified in backup-backup. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2023-6553?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2023-6553. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2023-6553 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology affecting backup-backup. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2023-6553?

CVE-2023-6553 affects backup-backup. Check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2023-6553?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for backup-backup.

What is the CVSS score for CVE-2023-6553?

The severity rating and CVSS scoring for CVE-2023-6553 affecting backup-backup is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.