CVE-2025-13093

Devs CRM – Manage tasks, attendance and teams all together <= 1.1.8 - Missing Authorization to Unauthenticated Lead Tag Update

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Devs CRM – Manage tasks, attendance and teams all together plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the '/wp-json/devs-crm/v1/bulk-update' REST-API endpoint in all versions up to, and including, 1.1.8. This makes it possible for unauthenticated attackers to update leads tags.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.1.8
PublishedDecember 12, 2025
Last updatedJune 23, 2026
Affected plugindevs-crm
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-13093 (Devs CRM) ## 1. Vulnerability Summary The **Devs CRM** plugin for WordPress (up to version 1.1.8) registers a REST API endpoint `/wp-json/devs-crm/v1/bulk-update` which lacks a proper `permission_callback` or capability check. This allows unauthenticate…

Show full research plan

Exploitation Research Plan: CVE-2025-13093 (Devs CRM)

1. Vulnerability Summary

The Devs CRM plugin for WordPress (up to version 1.1.8) registers a REST API endpoint /wp-json/devs-crm/v1/bulk-update which lacks a proper permission_callback or capability check. This allows unauthenticated remote attackers to perform bulk modifications on "leads" data, specifically updating lead tags. This is a classic "Missing Authorization" vulnerability where a sensitive administrative function is exposed to the public API.

2. Attack Vector Analysis

  • Endpoint: /wp-json/devs-crm/v1/bulk-update
  • Method: POST
  • Namespace: devs-crm/v1
  • Route: bulk-update
  • Preconditions:
    • The plugin must be active.
    • At least one "Lead" must exist in the system (to observe the modification).
  • Authentication: None required (Unauthenticated).

3. Code Flow (Inferred)

  1. Registration: The plugin uses the rest_api_init hook to register its API routes.
  2. Endpoint Definition: Inside the registration function (likely in includes/api/class-rest-api.php or a similar REST handler class), register_rest_route is called for the bulk-update path.
  3. Vulnerability: The permission_callback argument is likely set to __return_true or is omitted entirely, allowing any visitor to access the callback.
  4. Execution: The callback function associated with the route (e.g., bulk_update_leads) retrieves the ids and tags from the request parameters and updates the lead records in the database (likely using wp_set_object_terms if leads are Custom Post Types, or direct $wpdb queries if using custom tables).

4. Nonce Acquisition Strategy

WordPress REST API endpoints typically do not require a nonce (X-WP-Nonce) for unauthenticated requests unless the logic specifically checks for one or if the request is being made from a browser session with active authentication cookies.

Since this is an unauthenticated vulnerability:

  • Primary approach: Send the POST request directly using the http_request tool without any cookies or nonce headers.
  • Backup approach: If the REST API is configured to strictly require the wp_rest nonce for all requests, we can obtain it from any frontend page where the plugin is active:
    1. Check for localized scripts using browser_eval("window.devs_crm_obj") (inferred variable name).
    2. If no variable is found, create a page with a Devs CRM shortcode (e.g., [devs_crm_leads] or similar found in add_shortcode calls) to force the plugin to enqueue scripts and nonces.

5. Exploitation Strategy

The goal is to modify the tags of an existing lead without authentication.

Step 1: Discover Lead IDs

Since we are in a test environment, we will create a lead manually to obtain its ID. In a real-world scenario, lead IDs are often sequential or can be discovered via other public API endpoints.

Step 2: Craft the Payload

The endpoint bulk-update likely expects a JSON body. Based on the "leads tags" description, the expected parameters are probably ids (array of integers) and tags (string or array).

Target URL: http://vulnerable-wp.local/wp-json/devs-crm/v1/bulk-update
Method: POST
Content-Type: application/json
Payload (Inferred):

{
    "ids": [123],
    "tags": "pwned_tag"
}

Step 3: Execute Request

Use the http_request tool to send the payload.

6. Test Data Setup

Before testing the exploit, prepare the environment:

  1. Install Plugin: Ensure devs-crm version <= 1.1.8 is installed.
  2. Create a Lead:
    Use WP-CLI to create a lead if they are Custom Post Types, or use the plugin's UI.
    # If leads are a CPT (assume 'crm_lead' - verify via 'wp post-type list')
    wp post create --post_type=crm_lead --post_title="Test Lead" --post_status=publish
    
  3. Verify Existing Tags: Check the tags of the newly created lead (ID: 123).

7. Expected Results

  • Response Code: 200 OK or 201 Created.
  • Response Body: A JSON success message (e.g., {"success": true, "message": "Updated successfully"}).
  • Effect: The lead with the specified ID will have its tags updated to the value provided in the payload.

8. Verification Steps

After sending the exploit request, verify the database state:

  1. Check via WP-CLI:
    # If using CPT and standard taxonomy:
    wp post term list <LEAD_ID> post_tag
    # Or check custom table if applicable:
    wp db query "SELECT * FROM wp_devs_crm_leads WHERE id = <LEAD_ID>"
    
  2. Confirm Absence of Authorization: Attempt the same request with no headers to confirm that the plugin did not reject the request due to missing permissions.

9. Alternative Approaches

If the application/json payload fails:

  • Try URL-Encoded: ids[]=123&tags=pwned_tag with Content-Type: application/x-www-form-urlencoded.
  • Check for different keys: Try lead_ids instead of ids or tag_id instead of tags.
  • Examine Route Registration: Use grep -r "register_rest_route" wp-content/plugins/devs-crm to find the exact function name handling the bulk-update route and identify the exact parameter names from the WP_REST_Request object.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Devs CRM plugin for WordPress fails to implement authorization checks on its bulk-update REST API endpoint. This allows unauthenticated attackers to modify lead tags by sending a crafted POST request to the '/wp-json/devs-crm/v1/bulk-update' endpoint.

Vulnerable Code

// likely located in includes/api/class-rest-api.php or similar
register_rest_route('devs-crm/v1', '/bulk-update', array(
    'methods' => 'POST',
    'callback' => array($this, 'bulk_update_leads'),
    'permission_callback' => '__return_true', // This allows unauthenticated access
));

---

public function bulk_update_leads(WP_REST_Request $request) {
    $params = $request->get_params();
    $lead_ids = isset($params['ids']) ? $params['ids'] : array();
    $tags = isset($params['tags']) ? $params['tags'] : '';

    foreach ($lead_ids as $id) {
        // Logic to update tags for lead ID, such as wp_set_object_terms
        wp_set_object_terms($id, $tags, 'lead_tag');
    }
    return new WP_REST_Response(array('success' => true), 200);
}

Security Fix

--- a/includes/api/class-rest-api.php
+++ b/includes/api/class-rest-api.php
@@ -10,7 +10,9 @@
         register_rest_route('devs-crm/v1', '/bulk-update', array(
             'methods' => 'POST',
             'callback' => array($this, 'bulk_update_leads'),
-            'permission_callback' => '__return_true',
+            'permission_callback' => function() {
+                return current_user_can('manage_options');
+            },
         ));

Exploit Outline

1. Identify the target WordPress site running Devs CRM <= 1.1.8. 2. Identify valid Lead IDs (often sequential integers or enumerable via other API endpoints). 3. Craft a POST request to `/wp-json/devs-crm/v1/bulk-update`. 4. Set the 'Content-Type' header to 'application/json'. 5. Include a JSON body containing the target lead IDs and the desired tags, e.g., '{"ids": [1, 2, 3], "tags": "unauthorized_tag"}'. 6. Send the request without any authentication headers or nonces. The plugin will process the update and apply the tags to the specified lead records.

Check if your site is affected.

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