CVE-2026-1883

Wicked Folders <= 4.1.0 - Insecure Direct Object Reference to Authenticated (Contributor+) Arbitrary Folder Deletion

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
4.1.1
Patched in
1d
Time to patch

Description

The Wicked Folders – Folder Organizer for Pages, Posts, and Custom Post Types plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 4.1.0 via the delete_folders() function due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Contributor-level access and above, to delete arbitrary folders created by other users.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=4.1.0
PublishedMarch 14, 2026
Last updatedMarch 15, 2026
Affected pluginwicked-folders

What Changed in the Fix

Changes introduced in v4.1.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-1883 - Wicked Folders IDOR Folder Deletion ## 1. Vulnerability Summary The **Wicked Folders** plugin (<= 4.1.0) is vulnerable to an **Insecure Direct Object Reference (IDOR)** in the `Folder_API::delete_folders` REST API endpoint. The vulnerability exists beca…

Show full research plan

Exploitation Research Plan: CVE-2026-1883 - Wicked Folders IDOR Folder Deletion

1. Vulnerability Summary

The Wicked Folders plugin (<= 4.1.0) is vulnerable to an Insecure Direct Object Reference (IDOR) in the Folder_API::delete_folders REST API endpoint. The vulnerability exists because the plugin fails to validate whether the authenticated user (Contributor or higher) has permission to delete the specific folder IDs provided in the request. While a permission check ensures the user can manage folders for a given post_type, it does not enforce ownership or access control at the individual folder level, allowing a low-privileged user to delete folders created by an Administrator.

2. Attack Vector Analysis

  • Endpoint: DELETE /wp-json/wicked-folders/v1/folders
  • Authentication: Authenticated (Contributor+)
  • Capability Required: Typically edit_posts (inherent to Contributor).
  • Vulnerable Parameter: folder_ids (an array of folder IDs to be deleted).
  • Required Parameters:
    • post_type: The post type the folders belong to (e.g., post, page).
    • folder_ids: An array of numeric IDs of the folders to delete.

3. Code Flow

  1. Entry Point: The REST API route is registered in classes/rest-api/v1/folder-api.php:
    register_rest_route( $this->base, '/folders', array(
        array(
            'methods'             => WP_REST_Server::DELETABLE,
            'callback'            => array( $this, 'delete_folders' ),
            'permission_callback' => array( $this, 'delete_folders_permissions_check' ),
            'args'                => array(
                'post_type' => array( 'required' => true ),
                'folder_ids' => array( 'required' => true, 'default' => array() ),
                // ...
            ),
        ),
    ));
    
  2. Permission Check: delete_folders_permissions_check (not fully shown in source, but inferred) checks if the user has general permission to edit folders for the specified post_type. Contributor-level users pass this check by default for post types they can edit.
  3. Vulnerable Sink: The callback delete_folders (in folder-api.php) instantiates a Folder_Collection for the given post_type and calls the delete method:
    // From classes/folder-collection.php
    public function delete( $ids ) {
        // ... (logic for parent reassignment)
        foreach ( $this->items as $folder ) {
            if ( in_array( $folder->id, $ids ) ) { // No check if $folder belongs to the current user
                $folder->delete();
                $this->remove( $folder );
            }
        }
        return $changed;
    }
    
  4. Vulnerability: The loop in Folder_Collection::delete iterates through all folders associated with the post_type. If an ID matches the user-provided $ids array, it calls $folder->delete(). There is no check to ensure the folder was created by the current user or that the user has specific rights to that folder.

4. Nonce Acquisition Strategy

The REST API endpoint requires a standard WordPress REST Nonce (wp_rest).

  1. Login: Log in as a Contributor user.
  2. Navigate: Use browser_navigate to go to the WordPress Admin dashboard (e.g., /wp-admin/edit.php).
  3. Extract Nonce: WordPress exposes the REST nonce in the wpApiSettings object.
    • Tool: browser_eval
    • Command: browser_eval("wpApiSettings.nonce")
  4. Fallback: If wpApiSettings is missing, check wickedFoldersSettings or similar localization objects enqueued by the plugin in the page source.

5. Exploitation Strategy

Step-by-Step Plan:

  1. Identify Target: As an Administrator, create a folder for the post post type and note its ID.
  2. Setup Attacker: Create/login as a Contributor user.
  3. Obtain Nonce: Extract the X-WP-Nonce as described in Section 4.
  4. Send Malicious Request:
    • Action: Send a DELETE request to the REST API.
    • Endpoint: /wp-json/wicked-folders/v1/folders
    • Headers:
      • Content-Type: application/json
      • X-WP-Nonce: [EXTRACTED_NONCE]
    • Payload:
      {
        "post_type": "post",
        "folder_ids": [TARGET_FOLDER_ID]
      }
      
  5. Tool: Use http_request (Playwright) to send the DELETE request.

6. Test Data Setup

  1. Target Folder (Admin):
    • Create a folder using the plugin's UI or via WP-CLI:
      wp term create wf_folder "Sensitive Admin Folder" --description="post"
    • Note the ID (e.g., 15).
  2. Attacker User:
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password123

7. Expected Results

  • Response: The server should return a 200 OK or 204 No Content response, potentially containing the updated folder collection.
  • Side Effect: The folder with ID TARGET_FOLDER_ID (created by Admin) will be deleted from the database.

8. Verification Steps

  1. Check Database: Use WP-CLI to check if the term still exists in the wf_folder taxonomy.
    • wp term list wf_folder --fields=term_id,name
  2. Verify Deletion: If the TARGET_FOLDER_ID is no longer in the list, the exploit was successful.

9. Alternative Approaches

  • Single Folder Endpoint: If the bulk delete fails, try the single folder delete endpoint:
    DELETE /wp-json/wicked-folders/v1/folders/(?P<id>[\d]+)
    • Payload: {"post_type": "post"}
  • Post Type Variation: Test with post_type=page or any other custom post type supported by the plugin, as the vulnerability resides in the core Folder_Collection logic used across types.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Wicked Folders plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) due to missing validation on user-controlled folder IDs in the REST API. This allows authenticated attackers with Contributor-level access or higher to delete arbitrary folders created by other users, including administrators.

Vulnerable Code

// classes/rest-api/v1/folder-api.php

    public function delete_folder_permissions_check( $request ) {
        $allowed 	= current_user_can( 'edit_posts' );
        $user_id 	= get_current_user_id();
        $term_id    = $request->get_param( 'id' );
        $post_type  = $request->get_param( 'postType' );
        $taxonomy   = Wicked_Folders::get_tax_name( $post_type );

        return apply_filters( 'wicked_folders_can_delete_folder', $allowed, $user_id, $term_id, $taxonomy );
    }

    public function delete_folders_permissions_check( $request ) {
        return current_user_can( 'edit_posts' );
    }

---

// classes/folder-collection.php

    public function delete( $ids ) {
        $changed = new Folder_Collection();

        // ... (truncated reassignment logic)

        // Now we can delete
        foreach ( $this->items as $folder ) {
            if ( in_array( $folder->id, $ids ) ) {
                $folder->delete();

                $this->remove( $folder );
            }
        }
        
        return $changed;
    }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.0/classes/folder-collection.php /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.1/classes/folder-collection.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.0/classes/folder-collection.php	2025-11-10 16:27:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.1/classes/folder-collection.php	2026-03-03 16:39:56.000000000 +0000
@@ -68,6 +68,17 @@
             if ( $folder->id === $item->id ) {
                 unset( $this->items[ $index ] );
 
+                // Re-index. I don't understand why this is needed but leaving it out
+                // can prevent looping over the collection sometimes
+                $this->items = array_values( $this->items );
+
+                // Adjust index if the removed item is the same or before the current index.
+                // Setting to -1 is okay because the next() method will increment it (although
+                // then current() will be wrong so perhaps this needs to be implemented differently?)
+                if ( $index <= $this->index ) {
+                    $this->index--;
+                }
+
                 break;
             }
         }

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.0/classes/rest-api/v1/folder-api.php /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.1/classes/rest-api/v1/folder-api.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.0/classes/rest-api/v1/folder-api.php	2025-11-10 16:27:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wicked-folders/4.1.1/classes/rest-api/v1/folder-api.php	2026-03-03 16:39:56.000000000 +0000
@@ -277,27 +277,41 @@
     }
 
     public function update_folder_permissions_check( $request ) {
-        $allowed 	= current_user_can( 'edit_posts' );
         $user_id 	= get_current_user_id();
         $term_id    = $request->get_param( 'id' );
         $post_type  = $request->get_param( 'postType' );
         $taxonomy   = Wicked_Folders::get_tax_name( $post_type );
+        $allowed 	= current_user_can( 'edit_term', $term_id );
 
         return apply_filters( 'wicked_folders_can_edit_folder', $allowed, $user_id, $term_id, $taxonomy );
     }
 
     public function delete_folder_permissions_check( $request ) {
-        $allowed 	= current_user_can( 'edit_posts' );
         $user_id 	= get_current_user_id();
         $term_id    = $request->get_param( 'id' );
         $post_type  = $request->get_param( 'postType' );
         $taxonomy   = Wicked_Folders::get_tax_name( $post_type );
+        $allowed 	= current_user_can( 'delete_term', $term_id );
 
         return apply_filters( 'wicked_folders_can_delete_folder', $allowed, $user_id, $term_id, $taxonomy );
     }
 
     public function delete_folders_permissions_check( $request ) {
-        return current_user_can( 'edit_posts' );
+        $user_id 	= get_current_user_id();
+        $post_type  = $request->get_param( 'post_type' );
+        $folder_ids = $request->get_param( 'folder_ids' );
+        $taxonomy   = Wicked_Folders::get_tax_name( $post_type );
+
+        foreach ( $folder_ids as $term_id ) {
+            $allowed = current_user_can( 'delete_term', $term_id );
+            $allowed = apply_filters( 'wicked_folders_can_delete_folder', $allowed, $user_id, $term_id, $taxonomy );
+
+            if ( ! $allowed ) {
+                return false;
+            }
+        }
+
+        return true;
     }

Exploit Outline

1. Login to the WordPress site as an authenticated user with at least Contributor-level permissions. 2. Identify the target folder IDs (numeric values corresponding to the custom taxonomy terms used by the plugin) that were created by a different user (e.g., an Administrator). 3. Extract a valid WordPress REST API nonce (`X-WP-Nonce`) from the administrative dashboard source code (e.g., from the `wpApiSettings` JavaScript object). 4. Send a `DELETE` request to the `/wp-json/wicked-folders/v1/folders` endpoint. 5. Include a JSON payload containing the `post_type` (e.g., 'post' or 'page') and a `folder_ids` array containing the target IDs to be deleted. 6. Because the plugin only checks for the general `edit_posts` capability and not specific ownership or `delete_term` permissions for the provided IDs, the folders will be deleted successfully.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.