The Exploit
An authenticated user with Subscriber role or higher can inject arbitrary PHP code into the plugin's customFunction.php file and trigger remote code execution.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded
Cookie: wordpress_logged_in=<subscriber_session>
action=wp_all_import_function&csv_value=system($_GET['cmd']);%20?>&nonce=<valid_nonce>
When this request lands, the server appends the PHP code directly to customFunction.php and executes it via require_once. The attacker observes a 200 response, then visits wp-content/uploads/wp_all_import_data/customFunction.php?cmd=id to execute shell commands with the web server's privilege level.
What the Patch Did
Before
public function write_to_customfile($csv_value, $header_array=null, $value_array=null){
$customfn_file_path = WP_CONTENT_DIR . '/uploads/wp_all_import_data/customFunction.php';
if(!is_dir(dirname($customfn_file_path))){
mkdir(dirname($customfn_file_path), 0777, true);
}
$openFile = fopen($customfn_file_path, "a+");
fwrite($openFile, "\n".$csv_value);
fclose($openFile);
chmod($customfn_file_path , 0777);
require_once $customfn_file_path;
}
After
public function write_to_customfile($csv_value, $header_array = null, $value_array = null) {
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.'));
}
$custom_file_path = WP_CONTENT_DIR . '/uploads/wp_all_import_data/customFunction.json';
$existing_data = [];
if (file_exists($custom_file_path)) {
$existing_data = json_decode(file_get_contents($custom_file_path), true) ?: [];
}
if(!is_dir(dirname($custom_file_path))){
mkdir(dirname($custom_file_path), 0755, true);
}
$existing_data[] = $csv_value;
file_put_contents($custom_file_path, json_encode($existing_data, JSON_PRETTY_PRINT));
chmod($custom_file_path, 0644);
}
The patch implements three critical security controls. First, it adds current_user_can('manage_options') capability checking at the function entry point, restricting write access to administrators only. Second, it changes the storage format from PHP code appended to a .php file to JSON-encoded data in a .json file, eliminating the code injection surface entirely since json_encode() escapes all special characters. Third, it removes the require_once statement, ensuring no code from user-supplied data is ever executed. The patch also tightens file permissions from 0777 (world-writable executable) to 0644 (read-only for non-owners) and directory permissions from 0777 to 0755.
Root Cause
CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code) and CWE-862 (Missing Authorization).
The vulnerability follows a classic dataflow: the csv_value parameter, supplied by any authenticated user in the wp_all_import_function AJAX action, enters the function without validation or sanitization. The parameter flows directly into a file append operation (fwrite($openFile, "\n".$csv_value)) that constructs PHP code. The created file is then executed via require_once, crossing a critical trust boundary — user input becomes executable code. No authorization check guards the entry point, so even Subscriber-level users can invoke the vulnerable function. The plugin falsely assumes that all CSV data is benign or pre-validated; it is not.
Why It Works
The load-bearing line in the patch is the capability check: if (!current_user_can('manage_options')) { wp_die(...); }. Without this single line, every other mitigation fails; an authenticated non-admin attacker can still invoke the function and reach the dangerous code path. However, the fix is not a mere authorization wrapper — the engineer correctly implemented defence-in-depth. The format change to JSON (json_encode()) ensures that even if an admin-level attacker attempts injection, the data cannot execute. Removing require_once eliminates the execution sink entirely, making the stored format irrelevant to code safety. Lowering file permissions to 0644 prevents the web server from modifying the file once written. Each layer stands independently; removing any one weakens the defence, but the capability check alone stops the unauthenticated threat.
Hardening Checklist
-
Implement capability checks at all sensitive function entry points. Use
current_user_can('manage_options')or more granular custom capabilities registered viaregister_cap_type()for CSV import actions; do not rely onis_user_logged_in()alone. -
Never pass user-supplied data to
require_once(),eval(), orassert(). If dynamic code execution is needed, use a whitelist-based approach (e.g., array of permitted function names) and invoke functions by name, not by code string. -
Store user data in serialization formats that do not conflate structure with executable syntax. Use
json_encode()for JSON storage ormaybe_serialize()for PHP-native data, never string concatenation into code files. -
Set restrictive default file permissions on sensitive plugin directories. Use
mkdir(..., 0755, true)for directories andchmod($file, 0644)for data files; audit existing permissions withstat()and tighten as part of the upgrade process. -
Validate and nonce-protect all AJAX actions. Use
check_admin_referer()orwp_verify_nonce()on everywp_ajax_*hook to prevent CSRF; combine with capability checks to enforce authentication and authorization.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-10057