Sky Addons <= 3.3.2 - Authenticated (Author+) Stored Cross-Site Scripting via Custom Script
Description
The Sky Addons plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the `sky-custom-scripts` custom post type in all versions up to, and including, 3.3.2. This is due to the custom post type being registered with `capability_type => 'post'` and `show_in_rest => true`, combined with insufficient input sanitization on the `sky_script_content` meta field and lack of output escaping when rendering scripts on the frontend. This makes it possible for authenticated attackers, with Author-level access and above, to inject arbitrary web scripts via the REST API that execute on every frontend page for all site visitors.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=3.3.2What Changed in the Fix
Changes introduced in v3.3.3
Source Code
WordPress.org SVN# Research Plan: CVE-2026-7475 - Sky Addons Stored XSS via Custom Scripts ## 1. Vulnerability Summary The **Sky Addons** plugin (<= 3.3.2) introduces a custom post type `sky-custom-scripts` designed to allow users to inject custom JS/CSS into the site. The vulnerability exists because this post typ…
Show full research plan
Research Plan: CVE-2026-7475 - Sky Addons Stored XSS via Custom Scripts
1. Vulnerability Summary
The Sky Addons plugin (<= 3.3.2) introduces a custom post type sky-custom-scripts designed to allow users to inject custom JS/CSS into the site. The vulnerability exists because this post type is registered with 'show_in_rest' => true and 'capability_type' => 'post', allowing users with the **Author** role (who possess the edit_posts capability) to create and modify these scripts via the WordPress REST API.
The plugin fails to sanitize the sky_script_content meta field during input via the REST API and fails to escape the content when rendering it on the frontend. Since these scripts are intended to be global, a malicious Author can inject arbitrary JavaScript that executes for all site visitors, including administrators.
2. Attack Vector Analysis
- REST API Endpoint:
/wp-json/wp/v2/sky-custom-scripts - HTTP Method:
POST - Vulnerable Parameter:
meta[sky_script_content] - Required Authentication: Author-level (or higher) authenticated user.
- Preconditions: The plugin must be active. No specific widget needs to be placed, as the "Custom Scripts" feature is a global "Theme Builder" style feature.
3. Code Flow
- Registration: In
includes/custom-scripts/class-custom-scripts-data.php, the functionregister_post_type()registerssky-custom-scripts. Crucially, it sets'capability_type' => 'post'and'show_in_rest' => true. - Meta Registration: In the same file,
register_meta_fields()registerssky_script_contentwith'show_in_rest' => true. - Data Insertion: The REST API handles the request. The plugin hooks
rest_pre_insert_sky-custom-scriptsvia therest_pre_insert()method. - Insecure Update: Inside
rest_pre_insert(), the code iterates through themetaarray from the request:
There is noforeach ( $params['meta'] as $meta_key => $meta_value ) { update_post_meta( $post_id, $meta_key, $meta_value ); }sanitize_text_field()orwp_kses()applied to$meta_value. - Rendering (Sink): While the rendering code is likely in a frontend-specific file (not fully provided), the description confirms the content of
sky_script_contentis echoed directly onto the frontend (likely hooked towp_headorwp_footer) without escaping.
4. Nonce Acquisition Strategy
To interact with the WordPress REST API using cookie authentication, a _wpnonce (specifically the wp_rest nonce) is required in the X-WP-Nonce header.
- Authentication: Log in as an Author user.
- Extraction: Navigate to the WordPress Dashboard (
/wp-admin/). - Execution: Use
browser_evalto extract the REST nonce from the standard WordPress global variable:// WordPress typically localizes the REST nonce in the 'wpApiSettings' object window.wpApiSettings?.nonce - Alternative: If
wpApiSettingsis not found, the nonce is often present in thewp-adminsource code inside a script tag:var wpApiSettings = {"root":"...","nonce":"[NONCE_VALUE]"}.
5. Exploitation Strategy
- Login: Authenticate as an Author user.
- Get Nonce: Extract the
wp_restnonce using the strategy in Section 4. - Create Malicious Script: Send a
POSTrequest to the REST API to create a newsky-custom-scriptsentry.- Tool:
http_request - URL:
http://[TARGET]/wp-json/wp/v2/sky-custom-scripts - Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Body:
{ "title": "Exploit Script", "status": "publish", "meta": { "sky_script_type": "js", "sky_script_content": "</script><script>alert('XSS_SUCCESS_CVE_2026_7475')</script>", "sky_script_position": "header", "sky_script_status": "enabled" } } - Note: The
</script>prefix ensures we break out of any potential existing script tags if the plugin wraps the content.
- Tool:
- Trigger: Visit the site homepage (unauthenticated or as Admin).
6. Test Data Setup
- Plugin Installation: Ensure
sky-elementor-addons(v3.3.2) is installed and active. - User Creation: Create a user with the Author role.
wp user create attacker attacker@example.com --role=author --user_pass=password123
7. Expected Results
- The REST API should return a
201 Createdstatus code with the JSON representation of the new post. - The response should contain the injected payload in the
meta.sky_script_contentfield (verified via therest_preparefilter in the source). - When navigating to the site frontend, the browser should execute the JavaScript and display an alert box.
8. Verification Steps
- Check Database: Use WP-CLI to verify the meta value was stored raw.
wp post list --post_type=sky-custom-scripts --format=ids | xargs -I % wp post meta get % sky_script_content - Frontend Inspection: Use
http_requestto fetch the homepage and grep for the payload.# Look for the payload in the HTML source curl -s http://localhost:8080/ | grep "XSS_SUCCESS_CVE_2026_7475"
9. Alternative Approaches
- Payload Variation: If the plugin wraps the input in
<script>tags, the payload should be:alert('XSS'). If it doesn't, use<script>alert('XSS')</script>. The provided source suggestssky_script_typeis a choice, so it likely determines whether the plugin adds tags. - Update Existing: If creation is restricted for some reason, use
GET /wp-json/wp/v2/sky-custom-scriptsto find an existing ID, thenPOST /wp-json/wp/v2/sky-custom-scripts/[ID]to update it. - Bypassing meta registration: If the REST API
metaparameter is blocked, therest_pre_inserthook in the plugin's code specifically manually extracts and updates meta, which might bypass standard REST API meta registration logic.
Summary
The Sky Addons plugin for WordPress (versions <= 3.3.2) contains a Stored Cross-Site Scripting (XSS) vulnerability. Authenticated users with Author-level permissions or higher can inject malicious JavaScript into the 'sky_script_content' meta field via the REST API, which is then rendered unsanitized on every frontend page of the site.
Vulnerable Code
/** * includes/custom-scripts/class-custom-scripts-data.php * Line 38: Metadata is updated from the request object without any sanitization or authorization checks. */ public function rest_pre_insert( $prepared_post, $request ) { // Get the meta data from request $params = $request->get_params(); if ( isset( $params['meta'] ) && is_array( $params['meta'] ) ) { // If we have a post ID (update), save meta immediately if ( isset( $params['id'] ) ) { $post_id = $params['id']; foreach ( $params['meta'] as $meta_key => $meta_value ) { update_post_meta( $post_id, $meta_key, $meta_value ); } } else { // For new posts, we'll handle this in rest_insert action add_action( 'rest_insert_sky-custom-scripts', function ( $post, $request ) use ( $params ) { foreach ( $params['meta'] as $meta_key => $meta_value ) { update_post_meta( $post->ID, $meta_key, $meta_value ); } }, 10, 2 ); } } return $prepared_post; } --- /** * includes/custom-scripts/class-custom-scripts-data.php * Line 128: The post type is registered using 'capability_type' => 'post', making it accessible to Authors via show_in_rest. */ 'capability_type' => 'post', 'show_in_rest' => true, --- /** * includes/custom-scripts/class-custom-scripts-data.php * Line 149: Meta fields are exposed to the REST API without an 'auth_callback' restricting access to administrators. */ register_post_meta( 'sky-custom-scripts', 'sky_script_content', [ 'show_in_rest' => true, 'single' => true, 'type' => 'string', 'default' => '', ]);
Security Fix
@@ -35,6 +35,14 @@ * Filter data before inserting via REST API */ public function rest_pre_insert( $prepared_post, $request ) { + if ( ! current_user_can( 'manage_options' ) ) { + return new \WP_Error( + 'rest_forbidden', + __( 'You do not have permission to manage custom scripts.', 'sky-elementor-addons' ), + [ 'status' => 403 ] + ); + } + // Get the meta data from request $params = $request->get_params(); @@ -126,6 +134,20 @@ 'rewrite' => false, 'show_in_nav_menus' => false, 'capability_type' => 'post', + 'map_meta_cap' => true, + 'capabilities' => [ + 'create_posts' => 'manage_options', + 'edit_posts' => 'manage_options', + 'edit_others_posts' => 'manage_options', + 'publish_posts' => 'manage_options', + 'read_private_posts' => 'manage_options', + 'delete_posts' => 'manage_options', + 'delete_private_posts' => 'manage_options', + 'delete_published_posts' => 'manage_options', + 'delete_others_posts' => 'manage_options', + 'edit_private_posts' => 'manage_options', + 'edit_published_posts' => 'manage_options', + ], 'show_in_rest' => true, ]; @@ -139,52 +161,62 @@ * Register all meta fields for custom scripts */ private function register_meta_fields() { + $admin_only = function () { + return current_user_can( 'manage_options' ); + }; + // Script type (css or js) register_post_meta( 'sky-custom-scripts', 'sky_script_type', [ - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', - 'default' => 'js', + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'default' => 'js', + 'auth_callback' => $admin_only, ]); // Script content register_post_meta( 'sky-custom-scripts', 'sky_script_content', [ - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', - 'default' => '', + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'default' => '', + 'auth_callback' => $admin_only, ]);
Exploit Outline
An attacker authenticates as an Author-level user and retrieves a WordPress REST API nonce from the dashboard. The attacker then submits a POST request to the '/wp-json/wp/v2/sky-custom-scripts' endpoint, using the nonce in the 'X-WP-Nonce' header. The request payload includes a 'meta' object where 'sky_script_content' contains a malicious JavaScript string. Because the plugin manually updates post metadata via the 'rest_pre_insert' hook without sanitizing the input or verifying that the user has administrative privileges, the script is saved and automatically executed in the browser of any user visiting the site's frontend.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.