Splide Carousel Block <= 1.7.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'url' Block Attribute
Description
The Splide Carousel Block plugin for WordPress is vulnerable to Stored Cross-Site Scripting via 'url' Block Attribute in all versions up to, and including, 1.7.1 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. The injected payload must be published before it executes for site visitors, which requires an editor or administrator to approve and publish the contributor's post.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.7.2
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-9022 ## 1. Vulnerability Summary The **Splide Carousel Block** plugin (versions <= 1.7.1) is vulnerable to **Authenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists in the `cloudcatch/splide-carousel-item` block, specifically within the `u…
Show full research plan
Exploitation Research Plan - CVE-2026-9022
1. Vulnerability Summary
The Splide Carousel Block plugin (versions <= 1.7.1) is vulnerable to Authenticated Stored Cross-Site Scripting (XSS). The vulnerability exists in the cloudcatch/splide-carousel-item block, specifically within the url attribute. While the editor UI attempts to prepend protocols, it does not sanitize or validate against the javascript: protocol. Furthermore, the block's save function stores this attribute in a JSON string within a data-link attribute on the frontend. When a user clicks the carousel item, the frontend JavaScript parses this JSON and executes the URL, leading to script execution if a javascript: URI is used.
2. Attack Vector Analysis
- Endpoint: WordPress REST API Post creation/update (
/wp-json/wp/v2/posts) or the Gutenberg Editor (/wp-admin/post.php). - Vulnerable Block:
cloudcatch/splide-carousel-item. - Vulnerable Attribute:
url. - Payload Type:
javascript:protocol URI. - Authentication Level: Contributor or higher. Contributors can create and save posts (stored XSS), which then execute when viewed by an Editor or Administrator (e.g., during post review).
- Preconditions: The plugin must be active, and the attacker must have credentials for a Contributor-level account.
3. Code Flow
- Input: A Contributor user creates or edits a post and inserts a
cloudcatch/splide-carousel-itemblock with a maliciousurlattribute (e.g.,javascript:alert(window.origin)). - Editor Processing (
build/carousel-item/index.js):- The
editfunction uses(0, g.prependHTTP)(e)to process the URL. prependHTTP(fromwp-url) allows any string starting with a protocol (matching[a-z]+:), thus permittingjavascript:.
- The
- Storage (
build/carousel-item/index.js):- The
savefunction generates the frontend HTML:"data-link": c ? JSON.stringify({ url: i, target: o, rel: a }) : null - The attribute
i(the rawurl) is placed into a JSON object and stringified into thedata-linkattribute.
- The
- Frontend Execution (
build/carousel/view.js):- The frontend script initializes the Splide carousel.
- It retrieves the
data-linkattribute from the slide element. - Upon a click event, it parses the JSON and performs a navigation/redirection using the
urlproperty (e.g.,window.location.href = data.urlorwindow.open(data.url)).
- Sink: The browser executes the
javascript:URI, resulting in XSS.
4. Nonce Acquisition Strategy
To interact with the REST API for post creation, a REST Nonce (_wpnonce) is required.
- Login: Authenticate as a Contributor using the
browser_navigatetool. - Navigation: Navigate to the "New Post" screen:
/wp-admin/post-new.php. - Extraction: Use
browser_evalto extract the nonce from thewpApiSettingsobject which is globally available in the Gutenberg editor context.- JavaScript:
window.wpApiSettings.nonce
- JavaScript:
5. Exploitation Strategy
- Setup Account: Ensure a Contributor user exists.
- Get Nonce: Login as Contributor and extract the REST API nonce from
/wp-admin/post-new.php. - Create Malicious Post: Use the
http_requesttool to send aPOSTrequest to/wp-json/wp/v2/posts.- Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Payload:
{ "title": "XSS Carousel Test", "content": "<!-- wp:cloudcatch/splide-carousel -->\n<div class=\"wp-block-cloudcatch-splide-carousel\">\n<!-- wp:cloudcatch/splide-carousel-item {\"url\":\"javascript:alert(document.domain)\"} -->\n<div class=\"wp-block-cloudcatch-splide-carousel-item\"></div>\n<!-- /wp:cloudcatch/splide-carousel-item -->\n</div>\n<!-- /wp:cloudcatch/splide-carousel -->", "status": "draft" }
- Headers:
- Obtain Post URL: Extract the
linkfrom the REST API response. - Trigger XSS:
- Navigate to the post's preview URL or the published URL (if an admin publishes it).
- Click on the carousel slide item.
6. Test Data Setup
- Plugin: Install and activate
splide-carouselversion 1.7.1. - User: Create a user with the
contributorrole. - Environment: Ensure the WordPress instance is configured to allow blocks (standard in WP 5.0+).
7. Expected Results
- The REST API call should return
201 Created. - The post content in the database (verified via
wp post get [ID] --field=post_content) should contain:<!-- wp:cloudcatch/splide-carousel-item {"url":"javascript:alert(document.domain)"} -->. - The frontend HTML for the slide should contain:
data-link='{"url":"javascript:alert(document.domain)", ...}'. - Upon clicking the slide on the frontend, an alert box showing the document domain should appear.
8. Verification Steps
- Database Check:
wp post list --post_type=post --post_status=draftwp post get [POST_ID] --field=post_content
- Frontend Render Check:
- Navigate to the post URL.
- Inspect the HTML for the element with class
splide__slide. - Confirm the presence of
data-linkcontaining thejavascript:payload.
9. Alternative Approaches
- Bypassing
prependHTTP: IfprependHTTPwas more restrictive, an attacker could trydata-linkattribute breakout:- Payload for
url:"}><img src=x onerror=alert(1)> - This targets the way
JSON.stringifyoutput is placed into the HTML attribute if the plugin failed to useesc_attron the block'ssaveoutput (though Gutenberg usually handles this).
- Payload for
- Editor-Side XSS: Check if the
urlis rendered unsanitized within the Gutenberg Editor (editfunction inindex.js). If the editor displays the link in an<a>tag or an<iframe>preview, the XSS might trigger for the Contributor immediately upon saving.
Summary
The Splide Carousel Block plugin is vulnerable to Stored Cross-Site Scripting (XSS) via the 'url' attribute of the Splide Carousel Item block. Authenticated attackers with contributor-level access can inject 'javascript:' URIs into the block, which are stored in a data attribute and executed in the browser context when a user clicks on the carousel slide.
Vulnerable Code
// build/carousel-item/index.js line 101 (edit function processing) onChange:({url:e="",opensInNewTab:t})=>{x({url:(0,g.prependHTTP)(e)}),A!==t&&(e=>{const t=e?"_blank":void 0;let r=_;t&&!_?r=w:t||_!==w||(r=void 0),x({target:t,rel:r})})(t)} --- // build/carousel-item/index.js line 109 (save function rendering) save:function(e){const{attributes:t}=e,{urlHash:r,verticalAlignment:n,url:i,target:o,rel:a}=t,c=!!i,u=p.useBlockProps.save({className:l()("wp-block-splide-carousel__slide","splide__slide",{[`is-vertically-aligned-${n}`]:n}),"data-link":c?JSON.stringify({url:i,target:o,rel:a}):null,...r&&{"data-splide-hash":r}}),d=p.useInnerBlocksProps.save(u);return(0,s.jsx)("div",{...d})}
Security Fix
@@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "cloudcatch/splide-carousel", - "version": "1.7.1", + "version": "1.7.2", "title": "Splide Carousel", "category": "media", "attributes": { @@ -1 +1 @@ -<?php return array('dependencies' => array('lodash', 'react-jsx-runtime', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => '51deb1d6947788b25af6'); +<?php return array('dependencies' => array('lodash', 'react-jsx-runtime', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => 'e8fd1be94de72804e90b');
Exploit Outline
To exploit this vulnerability, an attacker with Contributor-level credentials (or higher) navigates to the WordPress block editor and inserts a 'Splide Carousel Item' block. The attacker then modifies the 'url' attribute of this block to include a malicious JavaScript payload (e.g., 'javascript:alert(document.domain)'). Because the plugin uses the 'prependHTTP' helper without further validation, it allows the 'javascript:' protocol. When the post is saved and subsequently viewed (either in preview mode or after being published by an editor), the frontend JavaScript retrieves the malicious URL from the 'data-link' attribute and executes it via browser navigation when the carousel slide is clicked.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.