CVE-2026-2127

SiteOrigin Widgets Bundle <= 1.70.4 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Shortcode Execution

mediumMissing Authorization
5.4
CVSS Score
5.4
CVSS Score
medium
Severity
1.71.0
Patched in
1d
Time to patch

Description

The SiteOrigin Widgets Bundle plugin for WordPress is vulnerable to unauthorized arbitrary shortcode execution in all versions up to, and including, 1.70.4. This is due to a missing capability check on the `siteorigin_widget_preview_widget_action()` function which is registered via the `wp_ajax_so_widgets_preview` AJAX action. The function only verifies a nonce (`widgets_action`) but does not check user capabilities. This makes it possible for authenticated attackers, with Subscriber-level access and above, to execute arbitrary shortcodes by invoking the `SiteOrigin_Widget_Editor_Widget` via the preview endpoint. The required nonce is exposed on the public frontend when the Post Carousel widget is present on a page, embedded in the `data-ajax-url` HTML attribute.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.70.4
PublishedFebruary 17, 2026
Last updatedFebruary 18, 2026
Affected pluginso-widgets-bundle

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: SiteOrigin Widgets Bundle Arbitrary Shortcode Execution (CVE-2026-2127) ## 1. Vulnerability Summary The **SiteOrigin Widgets Bundle** plugin (up to 1.70.4) contains a missing authorization vulnerability in its widget preview functionality. The function `siteorigin_widget_preview_wi…

Show full research plan

Research Plan: SiteOrigin Widgets Bundle Arbitrary Shortcode Execution (CVE-2026-2127)

1. Vulnerability Summary

The SiteOrigin Widgets Bundle plugin (up to 1.70.4) contains a missing authorization vulnerability in its widget preview functionality. The function siteorigin_widget_preview_widget_action(), registered to the wp_ajax_so_widgets_preview action, verifies a nonce named widgets_action but fails to perform any capability checks (e.g., current_user_can('edit_posts')).

This allows any authenticated user, including those with Subscriber privileges, to trigger the preview rendering of any SiteOrigin widget. By specifically invoking the SiteOrigin_Widget_Editor_Widget and providing a payload containing WordPress shortcodes, an attacker can achieve arbitrary shortcode execution.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: so_widgets_preview
  • Vulnerable Function: siteorigin_widget_preview_widget_action()
  • Authentication: Required (Subscriber level or higher).
  • Parameters:
    • action: so_widgets_preview
    • _widgets_nonce: The nonce value (extracted from frontend).
    • class: SiteOrigin_Widget_Editor_Widget
    • instance: A JSON-encoded string representing the widget configuration, containing the shortcode in the text or content field.
  • Precondition: The attacker must obtain a valid widgets_action nonce, which is leaked on the frontend by the Post Carousel widget.

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with action=so_widgets_preview.
  2. Hook Registration: The plugin registers the action:
    add_action( 'wp_ajax_so_widgets_preview', 'siteorigin_widget_preview_widget_action' );
  3. Nonce Verification: siteorigin_widget_preview_widget_action() calls check_ajax_referer( 'widgets_action', '_widgets_nonce' );.
  4. Missing Check: No current_user_can() call exists after the nonce check.
  5. Widget Invocation: The function instantiates the class provided in the class parameter (e.g., SiteOrigin_Widget_Editor_Widget).
  6. Shortcode Execution: The widget's widget() or preview() method is called with the instance data. For the Editor widget, this content is passed through do_shortcode(), executing the attacker's payload.

4. Nonce Acquisition Strategy

The vulnerability description notes that the widgets_action nonce is exposed in the data-ajax-url attribute of the Post Carousel widget.

  1. Create Trigger Content: Use WP-CLI to create a page containing the Post Carousel widget.
    wp post create --post_type=page --post_status=publish --post_title="Nonce Leak" --post_content='[so_post_carousel]'
    
  2. Navigate to Page: Use the browser_navigate tool to visit the newly created page.
  3. Extract Nonce: Use browser_eval to find the data-ajax-url attribute and parse the nonce.
    // Search for the Post Carousel container which holds the data-ajax-url
    (() => {
      const element = document.querySelector('[data-ajax-url*="_widgets_nonce"]');
      if (!element) return null;
      const url = element.getAttribute('data-ajax-url');
      const params = new URLSearchParams(url.split('?')[1]);
      return params.get('_widgets_nonce');
    })()
    

5. Exploitation Strategy

Once the nonce is acquired, perform the following steps:

  1. Log in as Subscriber: Ensure cookies are set for a user with the subscriber role.
  2. Craft Payload: Prepare an instance object for the SiteOrigin_Widget_Editor_Widget.
    • The instance should contain a text field with a shortcode that leaks info, such as [wp_version] or a shortcode from another installed plugin.
  3. Send AJAX Request: Use http_request to send the exploit.

HTTP Request Details:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=so_widgets_preview&_widgets_nonce=[NONCE]&class=SiteOrigin_Widget_Editor_Widget&instance={"text":"[wp_version]"}
    
    (Note: The instance might need to be double-encoded or passed as an array depending on how siteorigin_widget_preview_widget_action parses it. If JSON fails, try instance[text]=[wp_version].)

6. Test Data Setup

  1. Plugin Installation: Ensure so-widgets-bundle version 1.70.4 is installed and active.
  2. Target User:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
    
  3. Leak Page:
    wp post create --post_type=page --post_status=publish --post_content='[so_post_carousel]'
    

7. Expected Results

  • The response from admin-ajax.php should be a 200 OK.
  • The response body should contain the rendered output of the shortcode (e.g., the WordPress version number string).
  • If the exploit fails due to authorization, the response would typically be 403 Forbidden or -1.

8. Verification Steps

  1. Log Output: Check if the returned string matches the expected output of the shortcode.
  2. Access Control Confirmation: Attempt the same request without a valid login cookie to verify it returns 403 or -1, confirming it's an authenticated-only vulnerability.
  3. Shortcode confirmation: Use a shortcode like [random_non_existent_shortcode] and verify the output contains the literal string, then use [wp_version] and verify it contains a version number.

9. Alternative Approaches

If the SiteOrigin_Widget_Editor_Widget is not available or its field names differ:

  1. Discover Fields: Use wp_ajax_so_widgets_preview with a known widget class and observe how it handles parameters.
  2. Alternative Widget: Try SiteOrigin_Widget_Headline_Widget or SiteOrigin_Widget_Text_Widget, as most SiteOrigin widgets process their text fields for shortcodes.
  3. Encoding: If the instance parameter is ignored, check if the plugin expects it to be base64 encoded, which is a common pattern in SiteOrigin's legacy preview system. Use browser_eval to see how the editor sends preview requests.
Research Findings
Static analysis — not yet PoC-verified

Summary

The SiteOrigin Widgets Bundle plugin is vulnerable to unauthorized arbitrary shortcode execution because the `siteorigin_widget_preview_widget_action` function, hooked to the `so_widgets_preview` AJAX action, lacks a capability check. Authenticated attackers, starting from the Subscriber role, can execute arbitrary shortcodes by leveraging a leaked nonce found in the Post Carousel widget's frontend rendering to trigger the preview rendering of a text-based widget containing a shortcode payload.

Vulnerable Code

// In the plugin's base AJAX handler
// add_action( 'wp_ajax_so_widgets_preview', 'siteorigin_widget_preview_widget_action' );

function siteorigin_widget_preview_widget_action() {
    // Nonce check is present, but no capability check follows
    check_ajax_referer( 'widgets_action', '_widgets_nonce' );

    // Missing: if ( ! current_user_can( 'edit_posts' ) ) wp_die();

    $class = $_POST['class'];
    $instance = $_POST['instance']; // This is often a JSON or array of widget settings

    if ( class_exists( $class ) ) {
        $widget = new $class();
        // The widget processes the instance and renders it, often executing do_shortcode()
        // on specific text fields within the provided instance data.
        $widget->preview( $instance );
    }
    wp_die();
}

Security Fix

--- a/base/siteorigin-widget.class.php
+++ b/base/siteorigin-widget.class.php
@@ -1024,6 +1024,10 @@
 	public static function siteorigin_widget_preview_widget_action() {
 		check_ajax_referer( 'widgets_action', '_widgets_nonce' );
 
+		if ( ! current_user_can( 'edit_posts' ) ) {
+			wp_die( -1 );
+		}
+
 		if ( empty( $_POST['class'] ) ) {
 			wp_die();
 		}

Exploit Outline

1. Authentication: Log in to the target WordPress site as a Subscriber or higher-privileged user. 2. Nonce Acquisition: Navigate to any public page containing the Post Carousel widget. Locate the HTML element with the `data-ajax-url` attribute and extract the value of the `_widgets_nonce` parameter (the nonce is labeled `widgets_action`). 3. Payload Preparation: Craft a POST request to `/wp-admin/admin-ajax.php` with the following parameters: - `action`: `so_widgets_preview` - `_widgets_nonce`: [Leaked Nonce] - `class`: `SiteOrigin_Widget_Editor_Widget` (or another text-rendering widget class) - `instance`: A JSON string containing the payload, e.g., `{"text": "[wp_version]"}` or any other shortcode like `[contact-form-7 ...]` or `[gravityform ...]`. 4. Execution: Send the request. The server will process the widget's preview logic, executing any shortcodes within the `instance` data and returning the rendered output in the response body.

Check if your site is affected.

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