CVE-2026-1053

Ivory Search <= 5.5.13 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'menu_gcse' and 'nothing_found_text' Parameters

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
5.5.14
Patched in
1d
Time to patch

Description

The Ivory Search – WordPress Search Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via admin settings in all versions up to, and including, 5.5.13 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level permissions and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
High
Privileges Required
High
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=5.5.13
PublishedJanuary 27, 2026
Last updatedJanuary 28, 2026
Affected pluginadd-search-to-menu

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-1053 (Ivory Search Stored XSS) ## 1. Vulnerability Summary The Ivory Search plugin (<= 5.5.13) contains a stored Cross-Site Scripting (XSS) vulnerability in its administrative settings. Specifically, the parameters `menu_gcse` and `nothing_found_text` are proc…

Show full research plan

Exploitation Research Plan: CVE-2026-1053 (Ivory Search Stored XSS)

1. Vulnerability Summary

The Ivory Search plugin (<= 5.5.13) contains a stored Cross-Site Scripting (XSS) vulnerability in its administrative settings. Specifically, the parameters menu_gcse and nothing_found_text are processed without sufficient sanitization before being saved to the database and are subsequently rendered on the frontend or admin dashboard without adequate escaping.

While the vulnerability requires Administrator-level privileges, it is significant in multi-site environments or single-site installations where unfiltered_html has been disabled (e.g., via define( 'DISALLOW_UNFILTERED_HTML', true );). In these hardened configurations, even Administrators should not be able to inject arbitrary scripts; this plugin's failure to sanitize input provides a bypass for that security control.

2. Attack Vector Analysis

  • Endpoint: Admin settings page, typically wp-admin/admin.php?page=ivory-search-settings or wp-admin/admin-ajax.php.
  • Vulnerable Parameters: menu_gcse and nothing_found_text.
  • Payload Location: Stored in the wp_options table (often within an array under ivory_search_settings or similar) and rendered on the frontend search results page or in the site menu.
  • Authentication: Required (Administrator or higher).
  • Preconditions: DISALLOW_UNFILTERED_HTML should be set to true in wp-config.php to demonstrate the vulnerability's impact as a bypass of intended security restrictions.

3. Code Flow (Inferred)

  1. Input: An Administrator submits a settings update via the Ivory Search dashboard.
  2. Handling: The plugin catches the POST request. Based on typical plugin architecture, this is likely handled in a class like Ivory_Search_Admin within a method responsible for saving settings (e.g., save_settings() or update_options()).
  3. Storage: The raw value of nothing_found_text or menu_gcse is saved using update_option(). The plugin fails to use wp_kses_post() or sanitize_text_field() on these specific fields.
  4. Output: On the frontend, when a search returns no results, the plugin retrieves the nothing_found_text option and echoes it directly to the page without using esc_html() or wp_kses(). Similarly, menu_gcse is rendered when the GCSE search integration is active.

4. Nonce Acquisition Strategy

Since this is an authenticated Stored XSS, the agent must log in as an administrator. The settings page uses WordPress nonces for CSRF protection.

  1. Login: Log in to /wp-login.php as an Administrator.
  2. Navigate: Use browser_navigate to go to the Ivory Search settings page: /wp-admin/admin.php?page=ivory-search.
  3. Identify Form: The settings are likely updated via a form.
  4. Extract Nonce:
    • Ivory Search often uses a nonce named is_settings_nonce or the standard _wpnonce.
    • Use browser_eval to find the nonce:
      browser_eval("document.querySelector('input[name=\"_wpnonce\"]')?.value || document.querySelector('input[name=\"is_settings_nonce\"]')?.value")
  5. Verify Localized Data: If the plugin uses AJAX, check the localized script object:
    • browser_eval("window.ivory_search_vars?.nonce") (inferred JS variable name).

5. Exploitation Strategy

Step 1: Inject Stored XSS

The primary target is nothing_found_text because it is easy to trigger.

HTTP Request:

  • Method: POST
  • URL: /wp-admin/admin.php?page=ivory-search (or the admin-post.php/admin-ajax.php endpoint identified during discovery)
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters (Inferred structure):
    is_settings_nonce=[NONCE]&
    action=is_save_settings&
    is_search_form[nothing_found_text]=<script>alert(document.domain)</script>&
    submit=Save
    
    Note: Ivory Search often nests settings within an is_search_form array.

Step 2: Trigger the Payload

  1. Navigate to the frontend homepage.
  2. Perform a search for a string that definitely does not exist (e.g., s=nonexistent_random_string_12345).
  3. The page should render the "Nothing Found" text, executing the script.

6. Test Data Setup

  1. Plugin Configuration:
    • Install Ivory Search v5.5.13.
    • Go to Ivory Search > Search Forms. Note the ID of the default search form (usually default).
  2. Hardening Check:
    • Ensure define( 'DISALLOW_UNFILTERED_HTML', true ); is added to wp-config.php to prove the vulnerability exists even when WordPress core protections are active.
  3. Content:
    • Ensure at least one page/post exists so the search functionality is active.

7. Expected Results

  • After the POST request, the server should return a 302 Redirect back to the settings page with a success message.
  • When viewing the frontend search results for a query with no results, the HTML source should contain the raw payload: <script>alert(document.domain)</script>.
  • A browser alert should trigger if viewed in a headed browser.

8. Verification Steps

  • WP-CLI Check:
    wp option get ivory_search_settings --format=json
    • Verify that the nothing_found_text key contains the unescaped <script> tag.
  • Source Verification:
    Use the http_request tool to GET the search results page:
    GET /?s=nonexistent_string
    • Search the response body for the exact string <script>alert(document.domain)</script>.

9. Alternative Approaches

If nothing_found_text is successfully sanitized in the specific test environment, target menu_gcse:

  1. Enable GCSE: Find the setting to enable "Google Custom Search" in the Ivory Search settings.
  2. Inject Payload: Send the payload in the menu_gcse parameter.
  3. Trigger: View any page where the search is integrated into the menu.

If the settings are updated via AJAX:

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: is_save_search_form (inferred)
  • Parameters: action=is_save_search_form&nonce=[NONCE]&...
Research Findings
Static analysis — not yet PoC-verified

Summary

Ivory Search (<= 5.5.13) fails to sanitize the 'menu_gcse' and 'nothing_found_text' administrative settings, allowing administrators to inject arbitrary JavaScript. The vulnerability is exploitable even when the 'unfiltered_html' capability is disabled, as the plugin stores and renders the raw input on the frontend and admin pages without sufficient escaping.

Vulnerable Code

// Inferred from Research Plan: Handling settings update
// admin/class-ivory-search-admin.php (approximate)
if ( isset( $_POST['is_search_form'] ) ) {
    $settings = $_POST['is_search_form'];
    // Fails to sanitize nothing_found_text or menu_gcse
    update_option( 'ivory_search_settings', $settings );
}

---

// Inferred from Research Plan: Rendering on frontend
// includes/class-ivory-search-frontend.php (approximate)
$settings = get_option( 'ivory_search_settings' );
if ( empty( $results ) ) {
    // Fails to escape output
    echo $settings['nothing_found_text'];
}

Security Fix

--- a/admin/class-ivory-search-admin.php
+++ b/admin/class-ivory-search-admin.php
@@ -102,7 +102,11 @@
 if ( isset( $_POST['is_search_form'] ) ) {
-    update_option( 'ivory_search_settings', $_POST['is_search_form'] );
+    $settings = $_POST['is_search_form'];
+    if ( isset( $settings['nothing_found_text'] ) ) {
+        $settings['nothing_found_text'] = wp_kses_post( $settings['nothing_found_text'] );
+    }
+    if ( isset( $settings['menu_gcse'] ) ) {
+        $settings['menu_gcse'] = wp_kses_post( $settings['menu_gcse'] );
+    }
+    update_option( 'ivory_search_settings', $settings );
 }

--- a/includes/class-ivory-search-frontend.php
+++ b/includes/class-ivory-search-frontend.php
@@ -250,7 +250,7 @@
 if ( empty( $results ) ) {
-    echo $settings['nothing_found_text'];
+    echo wp_kses_post( $settings['nothing_found_text'] );
 }

Exploit Outline

To exploit this vulnerability, an attacker with Administrator privileges must bypass security restrictions (like DISALLOW_UNFILTERED_HTML) by following these steps: 1. Authenticate to the WordPress dashboard. 2. Navigate to the Ivory Search settings page (typically `wp-admin/admin.php?page=ivory-search`). 3. Locate the field for 'nothing_found_text' (Search Form -> Settings -> Results -> 'Nothing found' message). 4. Inject a payload such as `<script>alert(document.domain)</script>` into that field. 5. Save the settings, ensuring the CSRF nonce (`is_settings_nonce`) is valid. 6. Trigger the execution by performing a search on the site frontend that returns no results (e.g., `/?s=nonexistent_string_12345`), causing the unsanitized script to execute in the victim's browser.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.