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 unauthenticated attackers to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/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 SVNThis research plan outlines the steps to exploit a Missing Authorization vulnerability in the WPAdverts plugin. ### 1. Vulnerability Summary The **WPAdverts – Classifieds Plugin** (versions <= 2.3.0) contains a missing authorization check in the `adext_payments_ajax_render` function. This function …
Show full research plan
This research plan outlines the steps to exploit a Missing Authorization vulnerability in the WPAdverts plugin.
1. Vulnerability Summary
The WPAdverts – Classifieds Plugin (versions <= 2.3.0) contains a missing authorization check in the adext_payments_ajax_render function. This function is registered as an AJAX action for both authenticated and unauthenticated users via wp_ajax_adext_payments_render and wp_ajax_nopriv_adext_payments_render.
Because the function fails to verify if the requester has permission to modify the specified payment_id (Insecure Direct Object Reference - IDOR) and lacks capability checks, an unauthenticated attacker can update existing payment records. Specifically, an attacker can change the post_status of a payment to pending, update the post_title, and modify sensitive metadata including the payer's name (adverts_person) and email (adverts_email).
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
adext_payments_render - Authentication: None (unauthenticated)
- Vulnerable Parameter:
payment_id - Payload Parameters:
gateway: The name of an active payment gateway (e.g.,bank).form: An array of serialized form data containingadverts_personandadverts_email.
- Preconditions:
- The "Payments" module must be enabled (default in many configurations).
- An existing
adverts-paymentpost ID must be known or enumerated.
3. Code Flow
- Entry Point:
addons/payments/includes/ajax.phpregisters the hooks:add_action('wp_ajax_nopriv_adext_payments_render', 'adext_payments_ajax_render'); - Sink:
adext_payments_ajax_render()is called. - Input Handling: The function retrieves
payment_idandgatewayfrom the request.$gateway_name = adverts_request('gateway'); $payment_id = absint( adverts_request( "payment_id" ) ); - Processing: It loads a form based on the gateway and binds the user-provided
formdata. - Vulnerable Sink: If the form validates (usually requires just a name and email), it calls
wp_update_postandupdate_post_metaon the providedpayment_idwithout any ownership or capability checks: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') );
4. Nonce Acquisition Strategy
No nonce is required.
Analysis of addons/payments/includes/ajax.php confirms that neither check_ajax_referer nor wp_verify_nonce are used within the adext_payments_ajax_render function. Furthermore, the frontend JS (addons/payments/assets/js/payments.js) does not include a nonce in the data object sent to the server.
5. Exploitation Strategy
- Target Identification: Identify a valid
payment_idof typeadverts-payment. This can be done via enumeration or if the attacker previously initiated a legitimate payment. - Determine Gateway: Use
bankas the default gateway, as it is the most common built-in gateway for WPAdverts. - Craft Payload:
action:adext_payments_rendergateway:bankpayment_id: [TARGET_ID]form[0][name]:adverts_personform[0][value]:Unauthorized Modifierform[1][name]:adverts_emailform[1][value]:pwned@example.com
- Execution: Send a POST request to
admin-ajax.php.
6. Test Data Setup
- Activate WPAdverts: Ensure the plugin and its Payments module are active.
- Create Pricing:
wp post create --post_type=adverts-pricing --post_title="Premium" --post_status=publish - Create a "Completed" Payment: Create a payment record that we will "downgrade" to pending and modify.
PAYMENT_ID=$(wp post create --post_type=adverts-payment --post_title="Original Payer" --post_status=completed --porcelain) wp post meta add $PAYMENT_ID adverts_person "Original Payer" wp post meta add $PAYMENT_ID adverts_email "original@example.com" wp post meta add $PAYMENT_ID _adverts_payment_gateway "bank"
7. Expected Results
- The AJAX response should return a JSON object. If successful, it might execute a "success" callback or render HTML depending on the gateway's response.
- The
adverts-paymentpost withID=PAYMENT_IDwill have its:post_statuschanged fromcompletedtopending.post_titlechanged toUnauthorized Modifier.- Meta
adverts_personchanged toUnauthorized Modifier. - Meta
adverts_emailchanged topwned@example.com.
8. Verification Steps
- Check Post Status and Title:
wp post get [PAYMENT_ID] --fields=ID,post_title,post_status - Check Metadata:
wp post meta get [PAYMENT_ID] adverts_person wp post meta get [PAYMENT_ID] adverts_email
9. Alternative Approaches
- Status Reset: If the gateway callback (
render) behaves differently, the primary impact is thewp_update_postcall which forces the status topending. This can be used to disrupt business logic where "completed" payments are expected. - Gateway Enumeration: If the
bankgateway is disabled, an attacker can guess other common gateways likepaypalorstripeto satisfy the$gatewaylookup. - Information Leakage: Note that if the form validation fails, the function returns
$html_form, which might contain rendered form templates, potentially leaking configuration details or available fields.
Summary
The WPAdverts plugin for WordPress is vulnerable to unauthorized data modification due to a missing capability check and lack of ownership verification in the `adext_payments_ajax_render` AJAX function. An unauthenticated attacker can exploit this to reset the status of existing payment records to 'pending' and update sensitive metadata such as the payer's name and email address.
Vulnerable Code
// addons/payments/includes/ajax.php lines 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 lines 19-70 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 ); $response = null; $data = array(); // ... (truncated) foreach(adverts_request( 'form', array() ) as $item) { $data["bind"][$item["name"]] = $item["value"]; } $form = new Adverts_Form(); $form->load( $gateway["form"]["payment_form"] ); $form->bind( $data["bind"] ); 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"] );
Security Fix
@@ -20,6 +20,7 @@ var $ = jQuery; var data = { action: "adext_payments_render", + nonce: $(".adverts-payment-data").data("nonce"), gateway: $(WPADVERTS.Payments.Tab.Link + ".current").data("tab"), page_id: $(".adverts-payment-data").data("page-id"), listing_id: $(".adverts-payment-data").data("listing-id"), @@ -116,6 +117,7 @@ var data = { action: "adext_payments_render", + nonce: $(".adverts-payment-data").data("nonce"), gateway: $(this).data("tab"), page_id: $(".adverts-payment-data").data("page-id"), listing_id: $(".adverts-payment-data").data("listing-id"), @@ -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; $data = array();
Exploit Outline
The vulnerability can be exploited by an unauthenticated attacker by sending a POST request to the WordPress AJAX endpoint (admin-ajax.php) with the action 'adext_payments_render'. The attacker needs to identify or enumerate a valid 'adverts-payment' post ID. The payload must include the 'payment_id', a valid 'gateway' name (such as 'bank'), and a 'form' array containing 'adverts_person' and 'adverts_email' fields. Because there is no nonce or capability check, the plugin will validate the form and proceed to call wp_update_post and update_post_meta on the specified ID, resulting in an unauthorized status reset and metadata update.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.