Atomic Edge analysis of CVE-2026-42661:
The WP Customer Area plugin for WordPress (versions up to and including 8.3.4) contains a path traversal vulnerability in the private file handling functionality. This vulnerability allows authenticated attackers with Custom-level access or higher to copy files from outside the intended FTP directory into private storage, potentially exposing sensitive files from other parts of the server.
Root Cause:
The vulnerable code resides in private-file-default-handlers.class.php, specifically in the copy_file_to_private_storage method (around line 420). The original code constructed a source file path by directly concatenating the FTP folder path with the user-supplied filename without validating that the resulting path remained within the intended directory. It used the untrusted $initial_filename and $src_path variables without any path traversal checks. The method also lacked proper validation against null bytes, directory separators, or paths containing “..” sequences. This allowed attackers to supply filenames like “../../../etc/passwd” to read files outside the designated FTP root.
Exploitation:
An attacker authenticated with at least the Custom-level role can exploit this vulnerability by sending a crafted request to the AJAX handler responsible for copying files from the FTP folder to private storage. The attacker would POST to /wp-admin/admin-ajax.php with an action parameter that triggers the file copy functionality, providing a filename parameter containing path traversal sequences (e.g., “../../wp-config.php”). The plugin would then attempt to copy the specified file from outside the FTP directory into the private storage area, making it accessible to the attacker. The attack requires the attacker to have access to the Customer Area interface that allows file operations.
Patch Analysis:
The patch adds comprehensive path validation before processing the file copy. It uses realpath() to resolve both the source folder and the constructed source path to their canonical absolute paths. It then checks that the resolved source path begins with the resolved source folder path using strpos() comparison on normalized paths. Additionally, it validates the filename against null bytes and directory separators using strpos() and basename() checks. The copy() and unlink() calls now operate on the validated $src_real path instead of the concatenated $src_path. These changes prevent directory traversal by ensuring the source file must reside within the FTP folder hierarchy.
Impact:
Successful exploitation allows an authenticated attacker to read arbitrary files from the WordPress server that the web server user has access to. This could expose sensitive configuration files (wp-config.php), database credentials, uploaded media from other users, or system files. The severity is elevated by the fact that the Custom role is often assigned to customers or lower-privileged users. While the attacker cannot write arbitrary files, reading sensitive system files can lead to further privilege escalation or compromise of the entire WordPress installation.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/customer-area/customer-area.php
+++ b/customer-area/customer-area.php
@@ -3,7 +3,7 @@
Plugin Name: WP Customer Area
Description: WP Customer Area is a modular all-in-one solution to manage private content with WordPress.
Plugin URI: https://wp-customerarea.com
- Version: 8.3.4
+ Version: 8.3.5
Author: Aguila Technologies
Author URI: https://www.aguila.fr/
Text Domain: cuar
@@ -38,7 +38,7 @@
define('CUAR_LANGUAGE_DIR', basename(CUAR_PLUGIN_DIR) . '/languages');
-define('CUAR_PLUGIN_VERSION', '8.3.4');
+define('CUAR_PLUGIN_VERSION', '8.3.5');
define('CUAR_PLUGIN_URL', plugin_dir_url(__FILE__));
define('CUAR_SCRIPTS_URL', CUAR_PLUGIN_URL . 'scripts');
define('CUAR_ADMIN_SKIN', 'plugin%%default-wp38');
--- a/customer-area/src/php/core-addons/capabilities/capabilities-addon.class.php
+++ b/customer-area/src/php/core-addons/capabilities/capabilities-addon.class.php
@@ -147,6 +147,18 @@
*/
public function validate_options($validated, $cuar_settings, $input)
{
+
+ if (!current_user_can('manage_options')) {
+ return $validated;
+ }
+
+ $tab = isset($_POST['tab']) ? sanitize_text_field(wp_unslash($_POST['tab'])) : '';
+ if ($tab !== 'cuar_capabilities') {
+ return $validated;
+ }
+
+ check_admin_referer( CUAR_Settings::$OPTIONS_GROUP . '_' . $tab . '-options' );
+
global $wp_roles;
if ( !isset($wp_roles)) $wp_roles = new WP_Roles();
$roles = $wp_roles->role_objects;
--- a/customer-area/src/php/core-addons/installer/installer-addon.class.php
+++ b/customer-area/src/php/core-addons/installer/installer-addon.class.php
@@ -169,16 +169,16 @@
public function create_pages_and_navigation()
{
- // Anti CSRF
- check_ajax_referer('cuar_installer_create_pages_and_nav_nonce', 'security');
+ // Anti CSRF
+ check_ajax_referer('cuar_installer_create_pages_and_nav_nonce', 'security');
- // Vérification des capacités
- if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json( [
- 'success' => false,
- 'message' => __( 'You do not have the required capabilities to perform this action.', 'cuar' ),
- ]);
- }
+ // Vérification des capacités
+ if (!current_user_can('manage_options')) {
+ wp_send_json([
+ 'success' => false,
+ 'error' => __('You do not have the required capabilities to perform this action.', 'cuar'),
+ ]);
+ }
// Use the Customer Pages add-on to do the job
/** @var CUAR_CustomerPagesAddOn $cp_addon */
@@ -189,12 +189,10 @@
// Clear the notices added by the above functions
$this->plugin->clear_admin_notices();
- // No way to screw up currently, always success
- $response = new StdClass();
- $response->success = true;
- $response->message = __('The required pages and the navigation menu have been created', 'cuar');
-
- wp_send_json($response);
+ wp_send_json([
+ 'success' => true,
+ 'message' => __('The required pages and the navigation menu have been created', 'cuar'),
+ ]);
}
/**
--- a/customer-area/src/php/core-addons/installer/templates/installer-part-setup-wizard.template.php
+++ b/customer-area/src/php/core-addons/installer/templates/installer-part-setup-wizard.template.php
@@ -93,6 +93,8 @@
</p>
</div>
+<?php $cuar_installer_nonce = wp_create_nonce('cuar_installer_create_pages_and_nav_nonce'); ?>
+
<!--suppress JSUnresolvedVariable -->
<script type="text/javascript">
jQuery(document).ready(function($) {
@@ -109,7 +111,8 @@
// Ajax call to create pages and navigation menu
var data = {
- 'action': 'cuar_installer_create_pages_and_nav'
+ 'action': 'cuar_installer_create_pages_and_nav',
+ 'security': '<?php echo esc_js($cuar_installer_nonce); ?>',
};
$.post(ajaxurl, data, function(response) {
loadingIndicator.fadeOut();
@@ -138,7 +141,8 @@
// Button to configure permissions
$('.cuar-configure-permissions').click(function() {
var data = {
- 'action': 'cuar_mark_permissions_as_configured'
+ 'action': 'cuar_mark_permissions_as_configured',
+ 'security': '<?php echo esc_js($cuar_installer_nonce); ?>',
};
$.post(ajaxurl, data, function (response) {
});
--- a/customer-area/src/php/core-addons/private-file/private-file-default-handlers.class.php
+++ b/customer-area/src/php/core-addons/private-file/private-file-default-handlers.class.php
@@ -420,16 +420,44 @@
$po_addon = $this->plugin->get_addon('post-owner');
$src_folder = trailingslashit($pf_addon->get_ftp_path());
+ $src_folder_real = realpath($src_folder);
+
+ $initial_filename = (string) $initial_filename;
+ $is_invalid_filename = (
+ $initial_filename === ''
+ || strpos($initial_filename, "