Atomic Edge analysis of CVE-2024-13362: This is a reflected DOM-based cross-site scripting vulnerability affecting the Freemius SDK between versions 2.10.0 through 2.10.1, as integrated in multiple WordPress plugins and themes (including Share This Image 2.07). The vulnerability allows an unauthenticated attacker to inject arbitrary JavaScript into the admin notice template through the `url` parameter, which executes when a user clicks a crafted link. The CVSS score is 6.1 (Medium).
The root cause stems from insufficient sanitization and output escaping in the `_add_sticky_dismiss_javascript()` method within `share-this-image/freemius/includes/managers/class-fs-admin-notice-manager.php` (line 194). The vulnerable code directly includes a template file `sticky-admin-notice-js.php` without verifying file existence or sanitizing the request parameters that may be used within that template. Additionally, the `trial_promotion_message` filter in `share-this-image/freemius/includes/class-freemius.php` (line 24000) passes unsanitized user data (specifically the `url` parameter originally from `$trial_url`) directly into the HTML response. The `apply_filters` call at line 24010 allows plugin/theme developers to modify the message, but the default filter does not escape output, creating an injection point.
Exploitation occurs when an attacker tricks a logged-in administrator into clicking a specially crafted URL that contains a malicious `url` parameter. The vulnerable code passes this parameter through the `trial_promotion_message` filter and embeds it into the admin notice HTML. A payload such as `javascript:alert(1)` placed in the `url` parameter would be reflected in the rendered admin notice anchor tag. The attack does not require authentication; it only requires a user with admin panel access to click the malicious link. The request is typically a GET to the WordPress admin area with the crafted parameter.
The patch applies two key changes. First, in `class-fs-admin-notice-manager.php` (line 197), it adds a `file_exists()` check before including the sticky admin notice JavaScript template, preventing inclusion of non-existent or malicious template files. Second, in `class-freemius.php` (lines 24003-24011), the patch restructures the trial promotion message by separating the message text (`$message_text`) from the button HTML (`$button`). It no longer directly appends `$button` (which contains the unsanitized URL) to the message. Instead, it wraps the message text in a container `
{$message_text}
{$button}
`, although the actual escaping of the URL within the anchor tag is handled by WordPress’s `esc_url()` function in the `sprintf` format string (the `%s` placeholder in the updated version uses the output of `$trial_url`, but the patch does not introduce `esc_url()` explicitly). However, the real fix lies in removing the dangerous filter application pattern.
Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of the WordPress admin dashboard. This can lead to session token theft, forced administrative actions (e.g., creating new admin users), plugin/theme deactivation, exfiltration of sensitive site data (user lists, configuration), and full site compromise. Since the attacker can only trigger this via a social engineering attack (clicking a link), the impact is elevated by the fact that no authentication check occurs before the vulnerable code runs.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/share-this-image/freemius/includes/class-freemius.php
+++ b/share-this-image/freemius/includes/class-freemius.php
@@ -24000,13 +24000,15 @@
// Start trial button.
$button = ' ' . sprintf(
- '<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s ➜</button></a>',
+ '<div><a class="button button-primary" href="%s">%s ➜</a></div>',
$trial_url,
$this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' )
);
+ $message_text = $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string}" );
+
$this->_admin_notices->add_sticky(
- $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ),
+ "<div class="fs-trial-message-container"><div>{$message_text}</div> {$button}</div>",
'trial_promotion',
'',
'promotion'
@@ -25476,7 +25478,7 @@
$img_dir = WP_FS__DIR_IMG;
// Locate the main assets folder.
- if ( 1 < count( $fs_active_plugins->plugins ) ) {
+ if ( ! empty( $fs_active_plugins->plugins ) ) {
$plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) );
foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
--- a/share-this-image/freemius/includes/class-fs-plugin-updater.php
+++ b/share-this-image/freemius/includes/class-fs-plugin-updater.php
@@ -542,24 +542,8 @@
global $wp_current_filter;
- $current_plugin_version = $this->_fs->get_plugin_version();
-
- if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
- if (
- is_null( $this->_update_details ) ||
- ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
- ) {
- /**
- * After an update, clear the stored update details and reparse the plugin's main file in order to get
- * the updated version's information and prevent the previous update information from showing up on the
- * updates page.
- *
- * @author Leo Fajardo (@leorw)
- * @since 2.3.1
- */
- $this->_update_details = null;
- $current_plugin_version = $this->_fs->get_plugin_version( true );
- }
+ if ( ! empty( $wp_current_filter ) && in_array( 'upgrader_process_complete', $wp_current_filter ) ) {
+ return $transient_data;
}
if ( ! isset( $this->_update_details ) ) {
@@ -568,7 +552,7 @@
false,
fs_request_get_bool( 'force-check' ),
FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
- $current_plugin_version
+ $this->_fs->get_plugin_version()
);
$this->_update_details = false;
--- a/share-this-image/freemius/includes/entities/class-fs-plugin-plan.php
+++ b/share-this-image/freemius/includes/entities/class-fs-plugin-plan.php
@@ -13,7 +13,6 @@
/**
* Class FS_Plugin_Plan
*
- * @property FS_Pricing[] $pricing
*/
class FS_Plugin_Plan extends FS_Entity {
--- a/share-this-image/freemius/includes/entities/class-fs-site.php
+++ b/share-this-image/freemius/includes/entities/class-fs-site.php
@@ -10,16 +10,16 @@
exit;
}
- /**
- * @property int $blog_id
- */
- #[AllowDynamicProperties]
class FS_Site extends FS_Scope_Entity {
/**
* @var number
*/
public $site_id;
/**
+ * @var int
+ */
+ public $blog_id;
+ /**
* @var number
*/
public $plugin_id;
--- a/share-this-image/freemius/includes/entities/class-fs-user.php
+++ b/share-this-image/freemius/includes/entities/class-fs-user.php
@@ -48,6 +48,19 @@
parent::__construct( $user );
}
+ /**
+ * This method removes the deprecated 'is_beta' property from the serialized data.
+ * Should clean up the serialized data to avoid PHP 8.2 warning on next execution.
+ *
+ * @return void
+ */
+ function __wakeup() {
+ if ( property_exists( $this, 'is_beta' ) ) {
+ // If we enter here, and we are running PHP 8.2, we already had the warning. But we sanitize data for next execution.
+ unset( $this->is_beta );
+ }
+ }
+
function get_name() {
return trim( ucfirst( trim( is_string( $this->first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) );
}
--- a/share-this-image/freemius/includes/managers/class-fs-admin-menu-manager.php
+++ b/share-this-image/freemius/includes/managers/class-fs-admin-menu-manager.php
@@ -699,16 +699,36 @@
$menu = $this->find_main_submenu();
}
+ $menu_slug = $menu['menu'][2];
$parent_slug = isset( $menu['parent_slug'] ) ?
- $menu['parent_slug'] :
- 'admin.php';
+ $menu['parent_slug'] :
+ 'admin.php';
- return admin_url(
- $parent_slug .
- ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) .
- 'page=' .
- $menu['menu'][2]
- );
+ if ( fs_apply_filter( $this->_module_unique_affix, 'enable_cpt_advanced_menu_logic', false ) ) {
+ $parent_slug = 'admin.php';
+
+ /**
+ * This line and the `if` block below it are based on the `menu_page_url()` function of WordPress.
+ *
+ * @author Leo Fajardo (@leorw)
+ * @since 2.10.2
+ */
+ global $_parent_pages;
+
+ if ( ! empty( $_parent_pages[ $menu_slug ] ) ) {
+ $_parent_slug = $_parent_pages[ $menu_slug ];
+ $parent_slug = isset( $_parent_pages[ $_parent_slug ] ) ?
+ $parent_slug :
+ $menu['parent_slug'];
+ }
+ }
+
+ return admin_url(
+ $parent_slug .
+ ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) .
+ 'page=' .
+ $menu_slug
+ );
}
/**
--- a/share-this-image/freemius/includes/managers/class-fs-admin-notice-manager.php
+++ b/share-this-image/freemius/includes/managers/class-fs-admin-notice-manager.php
@@ -194,8 +194,14 @@
* @since 1.0.7
*/
static function _add_sticky_dismiss_javascript() {
+ $sticky_admin_notice_js_template_name = 'sticky-admin-notice-js.php';
+
+ if ( ! file_exists( fs_get_template_path( $sticky_admin_notice_js_template_name ) ) ) {
+ return;
+ }
+
$params = array();
- fs_require_once_template( 'sticky-admin-notice-js.php', $params );
+ fs_require_once_template( $sticky_admin_notice_js_template_name, $params );
}
private static $_added_sticky_javascript = false;
--- a/share-this-image/freemius/start.php
+++ b/share-this-image/freemius/start.php
@@ -15,7 +15,7 @@
*
* @var string
*/
- $this_sdk_version = '2.10.1';
+ $this_sdk_version = '2.11.0';
#region SDK Selection Logic --------------------------------------------------------------------
--- a/share-this-image/share-this-image.php
+++ b/share-this-image/share-this-image.php
@@ -3,7 +3,7 @@
/*
Plugin Name: Share This Image
Description: Allows you to share in social networks any of your images
-Version: 2.07
+Version: 2.08
Author: ILLID
Author URI: https://share-this-image.com/
Text Domain: share-this-image
@@ -14,7 +14,7 @@
exit;
}
-define( 'STI_VER', '2.07' );
+define( 'STI_VER', '2.08' );
define( 'STI_DIR', dirname( __FILE__ ) );