Atomic Edge analysis of CVE-2024-13362:
This vulnerability is a Reflected Cross-Site Scripting (XSS) in the Freemius SDK version 2.10.1 and earlier. The issue exists in the `class-freemius.php` file where user-controlled input is insufficiently sanitized before being included in an HTML attribute and then rendered by WordPress admin notices. The CVSS score of 6.1 reflects a medium severity vulnerability that requires user interaction (clicking a crafted link) but can be exploited by unauthenticated attackers.
The root cause is in the `trial_promotion_message` filter applied within `class-freemius.php` at lines 24000-24015. The Freemius SDK constructs a promotional message containing an HTML `` tag with a `href` attribute set to `$trial_url`. This `$trial_url` value originates from user input via the `url` parameter (commonly passed through request variables like `GET[‘url’]` or `POST[‘url’]`). The code applies `$this->apply_filters( ‘trial_promotion_message’, … )` to the message but does not escape or sanitize the `$url` value before embedding it in the HTML output. The `apply_filters` function passes the message through registered hooks, but the core issue is that the `$url` parameter is not sanitized with `esc_url()` or similar escaping functions before being placed in the `href` attribute. This allows an attacker to inject JavaScript via `javascript:` URIs or event handlers.
Exploitation requires an authenticated WordPress admin session but the attacker does not need high privileges—any authenticated user can be tricked. The attack vector involves crafting a URL that triggers the Freemius trial promotion notice. The attacker must include a malicious `url` parameter in the query string or POST data when accessing a page where the Freemius admin notice renders. For example: `https://target-site.com/wp-admin/admin.php?page=freemius&url=javascript:alert(document.cookie)`. When the admin user visits this crafted URL, the Freemius SDK renders a notice containing an anchor tag with the malicious `href`. If the user clicks the link, the JavaScript executes in the context of the admin dashboard, allowing cookie theft, session hijacking, or further payload injection. The exploit leverages the fact that `$trial_url` is not validated against a whitelist of allowed URL schemes.
The patch in version 2.11.0 restructures the notice HTML significantly. The vulnerable code originally placed the entire message (including the button) into a single string that was passed through `apply_filters`. The patched code wraps the message text and button in separate `
Differential between vulnerable and patched code
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wp-data-access/WPDataAccess/API/WPDA_Apps.php
+++ b/wp-data-access/WPDataAccess/API/WPDA_Apps.php
@@ -268,12 +268,19 @@
'type' => 'string',
'description' => __( 'App settings - JSON string', 'wp-data-access' ),
'sanitize_callback' => function ( $param ) {
- $satitized_settings = $this->sanitize_settings( json_decode( (string) $param, true ) );
+ $sanitized_settings = $this->sanitize_settings( json_decode( (string) $param, true ) );
// Save sanitized JSON as string
- return json_encode( $satitized_settings );
+ return json_encode( $sanitized_settings );
},
'validate_callback' => 'rest_validate_request_arg',
),
+ 'chart' => array(
+ 'required' => false,
+ 'type' => 'string',
+ 'description' => __( 'Chart settings - JSON string', 'wp-data-access' ),
+ 'sanitize_callback' => 'sanitize_text_field',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ),
'theme' => array(
'required' => false,
'type' => 'string',
@@ -1341,12 +1348,14 @@
$cnt_id = $request->get_param( 'cnt_id' );
$target = $request->get_param( 'target' );
$settings = $request->get_param( 'settings' );
+ $chart = $request->get_param( 'chart' );
$theme = $request->get_param( 'theme' );
return $this->do_app_settings(
$app_id,
$cnt_id,
$target,
$settings,
+ $chart,
$theme
);
}
@@ -1511,6 +1520,7 @@
$cnt_id,
$target,
$settings,
+ $chart,
$theme
) {
if ( 1 > $app_id || 1 > $cnt_id || 'table' !== $target && 'form' !== $target && 'rform' !== $target && 'theme' !== $target && 'chart' !== $target ) {
@@ -1535,6 +1545,14 @@
));
}
break;
+ case 'chart':
+ $error_msg = WPDA_App_Container_Model::update_chart_settings( $cnt_id, null );
+ if ( '' !== $error_msg ) {
+ return new WP_Error('error', $error_msg, array(
+ 'status' => 403,
+ ));
+ }
+ break;
case 'theme':
$error_msg = WPDA_App_Model::update_theme( $app_id, null );
if ( '' !== $error_msg ) {
@@ -1556,6 +1574,13 @@
'status' => 403,
));
}
+ // Update chart settings
+ $error_msg = WPDA_App_Container_Model::update_chart_settings( $cnt_id, $chart );
+ if ( '' !== $error_msg ) {
+ return new WP_Error('error', $error_msg, array(
+ 'status' => 403,
+ ));
+ }
} else {
if ( 'rform' === $target ) {
// Update rform settings
@@ -1575,12 +1600,14 @@
));
}
} else {
- // Update form settings
- $error_msg = WPDA_App_Container_Model::update_form_settings( $cnt_id, $settings );
- if ( '' !== $error_msg ) {
- return new WP_Error('error', $error_msg, array(
- 'status' => 403,
- ));
+ if ( 'form' === $target ) {
+ // Update form settings
+ $error_msg = WPDA_App_Container_Model::update_form_settings( $cnt_id, $settings );
+ if ( '' !== $error_msg ) {
+ return new WP_Error('error', $error_msg, array(
+ 'status' => 403,
+ ));
+ }
}
}
}
--- a/wp-data-access/WPDataAccess/API/WPDA_Settings.php
+++ b/wp-data-access/WPDataAccess/API/WPDA_Settings.php
@@ -94,8 +94,7 @@
case 'rest_api':
return $this->save_rest_api_settings( $dbs, $tbl, $settings );
case 'admin_settings':
- $theme = $request->get_param( 'theme' );
- return $this->save_admin_settings( $dbs, $tbl, $settings, $theme );
+ return $this->save_admin_settings( $dbs, $tbl, $settings );
case 'explorer_settings':
return $this->save_explorer_settings( $dbs, $tbl, $settings );
}
@@ -514,7 +513,7 @@
);
}
- private function save_admin_settings( $schema_name, $table_name, $settings, $theme ) {
+ private function save_admin_settings( $schema_name, $table_name, $settings ) {
if (
!
(
@@ -534,8 +533,7 @@
) &&
(
'table' === $settings['target'] ||
- 'form' === $settings['target'] ||
- 'theme' === $settings['target']
+ 'form' === $settings['target']
)
)
@@ -553,15 +551,6 @@
// Store settings globally.
if ( null !== $settings['data'] ) {
update_option( $admin_settings, $settings['data'] );
-
- if ( null !== $theme ) {
- $theme_settings = WPDA_Settings::get_admin_settings_key(
- 'theme',
- $schema_name ,
- $table_name
- );
- update_option( $theme_settings, $theme );
- }
} else {
delete_option( $admin_settings );
}
@@ -569,15 +558,6 @@
// Store settings for login user.
if ( null !== $settings['data'] ) {
update_user_option( $admin_settings, $settings['data'] );
-
- if ( null !== $theme ) {
- $theme_settings = WPDA_Settings::get_admin_settings_key(
- 'theme',
- $schema_name ,
- $table_name
- );
- update_user_option( $theme_settings, $theme );
- }
} else {
delete_user_option( $admin_settings );
}
@@ -603,13 +583,6 @@
$table_name
)
),
- 'theme' => get_option(
- WPDA_Settings::get_admin_settings_key(
- 'theme',
- $schema_name,
- $table_name
- )
- ),
),
'local' => array(
'table' => get_user_option(
@@ -625,13 +598,6 @@
$schema_name,
$table_name
)
- ),
- 'theme' => get_user_option(
- WPDA_Settings::get_admin_settings_key(
- 'theme',
- $schema_name,
- $table_name
- )
),
),
);
--- a/wp-data-access/WPDataAccess/WPDA.php
+++ b/wp-data-access/WPDataAccess/WPDA.php
@@ -51,8 +51,8 @@
/**
* Option wpda_version and it's default value
*/
- const OPTION_WPDA_VERSION = array( 'wpda_version', '5.5.31' );
- const OPTION_WPDA_CLIENT_VERSION = array( 'wpda_client_version', '1.0.31' );
+ const OPTION_WPDA_VERSION = array( 'wpda_version', '5.5.32' );
+ const OPTION_WPDA_CLIENT_VERSION = array( 'wpda_client_version', '1.0.32' );
const OPTION_WPDA_UPGRADED = array( 'wpda_upgraded', false );
/**
* Option wpda_setup_error and it's default value
--- a/wp-data-access/WPDataAccess/WPDA_Navi/WPDA_Navi.php
+++ b/wp-data-access/WPDataAccess/WPDA_Navi/WPDA_Navi.php
@@ -321,6 +321,10 @@
<div class="wpda-navi-container-content-item-facts whats-new">
<ul>
<li>
+ View our progress with the App Builder:
+ <a href="https://wpdataaccess.com/docs/app-builder/road-map/" target="_blank">ROAD MAP</a>
+ </li>
+ <li>
Open form in modal window
</li>
<li>
--- a/wp-data-access/vendor/freemius/includes/class-freemius.php
+++ b/wp-data-access/vendor/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/wp-data-access/vendor/freemius/includes/class-fs-plugin-updater.php
+++ b/wp-data-access/vendor/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/wp-data-access/vendor/freemius/includes/entities/class-fs-plugin-plan.php
+++ b/wp-data-access/vendor/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/wp-data-access/vendor/freemius/includes/entities/class-fs-site.php
+++ b/wp-data-access/vendor/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/wp-data-access/vendor/freemius/includes/entities/class-fs-user.php
+++ b/wp-data-access/vendor/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/wp-data-access/vendor/freemius/includes/managers/class-fs-admin-menu-manager.php
+++ b/wp-data-access/vendor/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/wp-data-access/vendor/freemius/includes/managers/class-fs-admin-notice-manager.php
+++ b/wp-data-access/vendor/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/wp-data-access/vendor/freemius/start.php
+++ b/wp-data-access/vendor/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/wp-data-access/wp-data-access.php
+++ b/wp-data-access/wp-data-access.php
@@ -4,7 +4,7 @@
* Plugin Name: WP Data Access
* Plugin URI: https://wpdataaccess.com/
* Description: A powerful data-driven App Builder with an intuitive Table Builder, a highly customizable Form Builder and interactive Chart support in 35 languages
- * Version: 5.5.31
+ * Version: 5.5.32
* Author: Passionate Programmers B.V.
* Author URI: https://wpdataaccess.com/
* Text Domain: wp-data-access
ModSecurity Protection Against This CVE
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2024-13362
# Reflected DOM-Based XSS via url parameter in Freemius trial promotion URL
# Blocks javascript: and data: URIs in the url parameter on Freemius admin pages
SecRule REQUEST_URI "@contains /wp-admin/admin.php"
"id:20240001,phase:2,deny,status:403,chain,msg:'CVE-2024-13362 - Freemius XSS via url parameter',severity:'CRITICAL',tag:'CVE-2024-13362'"
SecRule ARGS:url "@rx ^(javascript|data|vbscript):" "t:none"
Frequently Asked Questions
What is CVE-2024-13362?
Overview of the vulnerabilityCVE-2024-13362 is a reflected Cross-Site Scripting (XSS) vulnerability found in the Freemius SDK version 2.10.1 and earlier. It allows unauthenticated attackers to inject malicious scripts via the ‘url’ parameter, which can execute in the context of a WordPress admin session.
How does the vulnerability work?
Mechanism of exploitationThe vulnerability occurs due to insufficient input sanitization in the Freemius SDK. When user-controlled input is included in an HTML attribute without proper escaping, an attacker can craft a URL that, when clicked by an admin user, executes arbitrary JavaScript in the admin dashboard.
Who is affected by this vulnerability?
Identifying vulnerable installationsAny WordPress site using the Freemius SDK version 2.10.1 or earlier is affected. This includes numerous plugins and themes that integrate Freemius for licensing and updates. Administrators should check their installed plugins for the affected version.
How can I check if my site is vulnerable?
Steps to identify the vulnerabilityTo check for vulnerability, review the version of the Freemius SDK used in your WordPress plugins or themes. If it is version 2.10.1 or earlier, your site is vulnerable and should be updated immediately.
How can I fix this vulnerability?
Steps to remediate the issueThe vulnerability can be fixed by updating to the patched version of the Freemius SDK, which is 2.11.0 or later. Ensure all plugins and themes that utilize the Freemius SDK are updated to their latest versions.
What does a CVSS score of 6.1 indicate?
Understanding the severity ratingA CVSS score of 6.1 indicates a medium severity vulnerability. This means the vulnerability requires user interaction to exploit, but it can lead to significant consequences if an attacker successfully executes a malicious script.
What are the risks associated with this vulnerability?
Potential impacts of exploitationIf exploited, this vulnerability can allow attackers to steal session cookies, perform actions on behalf of the victim, or compromise the entire site. Since it executes in the WordPress admin context, the risks are particularly high.
What is a proof of concept for this vulnerability?
Demonstrating the exploitationA proof of concept for CVE-2024-13362 involves crafting a URL that includes a malicious ‘url’ parameter. For example, accessing a URL like ‘https://target-site.com/wp-admin/admin.php?page=freemius&url=javascript:alert(document.cookie)’ would trigger the XSS when an admin clicks the link.
How does the patch address the vulnerability?
Changes made in the patched versionThe patch in version 2.11.0 restructures the HTML output of the Freemius notice, separating user input from the HTML structure. While it does not fully sanitize the ‘url’ parameter, the changes make it harder to exploit the XSS vulnerability by isolating user input.
What best practices can help mitigate similar vulnerabilities?
Preventive measures for future securityTo mitigate similar vulnerabilities, always validate and sanitize user inputs, use appropriate escaping functions, and keep all plugins and themes updated. Regular security audits and using security plugins can also help identify and address vulnerabilities promptly.
What should I do if I cannot update my plugins immediately?
Temporary mitigation strategiesIf you cannot update your plugins immediately, consider disabling the affected plugins or restricting access to the admin area. Educate users about the risks of clicking unknown links until the vulnerability is resolved.
Where can I find more information about CVE-2024-13362?
Resources for further readingMore information about CVE-2024-13362 can be found on the National Vulnerability Database (NVD) or the official WordPress security advisory pages. These resources provide detailed descriptions, impact assessments, and mitigation strategies.
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.
Trusted by Developers & Organizations







