CVE-2026-24618

Hash Elements <= 1.5.4 - Authenticated (Contributor+) Information Exposure

mediumExposure of Sensitive Information to an Unauthorized Actor
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.5.5
Patched in
7d
Time to patch

Description

The Hash Elements plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 1.5.4. This makes it possible for authenticated attackers, with Contributor-level access and above, to extract sensitive user or configuration data.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.5.4
PublishedJune 12, 2026
Last updatedJune 18, 2026
Affected pluginhash-elements

What Changed in the Fix

Changes introduced in v1.5.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan focuses on **CVE-2026-24618**, an authenticated information exposure vulnerability in the **Hash Elements** plugin. The vulnerability allows users with Contributor-level roles or higher to access sensitive data, such as private post titles or user information, through inadequately…

Show full research plan

This research plan focuses on CVE-2026-24618, an authenticated information exposure vulnerability in the Hash Elements plugin. The vulnerability allows users with Contributor-level roles or higher to access sensitive data, such as private post titles or user information, through inadequately protected AJAX endpoints or widget controls.

1. Vulnerability Summary

The Hash Elements plugin (versions <= 1.5.4) registers several AJAX handlers and widget controls designed to facilitate content selection within the Elementor editor. Specifically, the file inc/ajax-select.php (loaded in hash_elements.php via the init method) defines handlers for searching posts and users. These handlers lack proper capability checks, relying solely on WordPress nonces that are exposed to any user authorized to access the Elementor editor (including Contributors). Consequently, a Contributor can bypass intended visibility restrictions to enumerate private content or user details.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: he_ajax_select2_get_posts (inferred) and he_ajax_select2_get_users (inferred).
  • Parameters:
    • action: The AJAX action string.
    • nonce: A security nonce usually localized as part of the plugin's backend assets.
    • q: The search query string.
  • Authentication: Authenticated, Contributor-level (or higher) required to access the Elementor editor and obtain the necessary nonce.

3. Code Flow

  1. Registration: The main plugin file hash-elements.php calls init() on the plugins_loaded hook.
  2. Loading: init() requires HASHELE_PATH . 'inc/ajax-select.php'.
  3. AJAX Setup: inc/ajax-select.php registers handlers using add_action( 'wp_ajax_he_ajax_select2_get_posts', ... ).
  4. Asset Enqueueing: hash_elements_register_backend_assets() (called on admin_enqueue_scripts) enqueues JavaScript and localizes a nonce using wp_localize_script().
  5. Execution: When a Contributor edits a post with Elementor, the SquarePlusTabBlock widget (or similar) initializes. If the user triggers a "Select Page" or "Select User" control, the AJAX request is sent.
  6. Sinks: The handler functions in inc/ajax-select.php execute get_posts() or WP_User_Query using the user-provided q parameter without verifying if the requesting user has the read_private_posts or list_users capabilities.

4. Nonce Acquisition Strategy

The nonce is required for the AJAX request to succeed. It is localized to the Elementor editor page.

  1. Identify Shortcode: The plugin is an Elementor addon. The SquarePlusTabBlock widget is part of the he-square-elements category.
  2. Create Trigger Page: Create a standard WordPress page and open it in the Elementor editor.
  3. Navigate and Extract:
    • Use browser_navigate to go to the Elementor editor for a post (e.g., /wp-admin/post.php?post=[ID]&action=elementor).
    • Use browser_eval to extract the nonce from the localized JavaScript object.
    • Variable Name (Inferred): window.hash_elements_ajax?.nonce or window.he_ajax_select?.nonce.

5. Exploitation Strategy

Step 1: Information Gathering

Identify the exact AJAX action and nonce variable by inspecting the localized scripts in the Elementor editor.

Step 2: Extract Private Post Titles

Query the he_ajax_select2_get_posts endpoint to find posts the Contributor should not see.

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=he_ajax_select2_get_posts&nonce=[EXTRACTED_NONCE]&q=Secret
    

Step 3: Extract User Data

Query the he_ajax_select2_get_users endpoint to enumerate site users and their emails.

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=he_ajax_select2_get_users&nonce=[EXTRACTED_NONCE]&q=admin
    

6. Test Data Setup

  1. Administrator Actions:
    • Create a Private Page: wp post create --post_type=page --post_status=private --post_title="SECRET_INTERNAL_PLAN".
    • Create a user with sensitive info: wp user create victim_user victim@example.com --role=author.
  2. Contributor Actions:
    • Create a post: wp post create --post_author=[CONTRIBUTOR_ID] --post_title="Exploit Trigger" --post_status=publish.
    • This post will be used to launch the Elementor editor.

7. Expected Results

  • Post Exposure: The AJAX response for he_ajax_select2_get_posts returns a JSON array containing the ID and Title "SECRET_INTERNAL_PLAN", despite it being a private page.
  • User Exposure: The AJAX response for he_ajax_select2_get_users returns the user_login and potentially user_email for victim_user or the administrator.

8. Verification Steps

  1. Verify the visibility of the private post: wp post get [ID] --field=post_status (Should be private).
  2. Confirm the Contributor's permissions: wp user cap list [CONTRIBUTOR_ID] (Should NOT have read_private_posts).
  3. Compare the HTTP response body from the exploit against the titles/logins of the restricted data.

9. Alternative Approaches

  • Widget Property Enumeration: If the AJAX endpoints are unavailable, check the SquarePlusTabBlock::he_get_pages() method directly. If this method is called to populate the select_page control's options array during the initial load of the Elementor editor, the private page titles will be present in the initial HTML/JSON state of the Elementor editor under the controls configuration for the widget.
  • Parameter Brute Force: If the q parameter is not used, try omitting it to see if the handler returns the first 10-20 posts/users by default.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Hash Elements plugin for WordPress is vulnerable to Sensitive Information Exposure via its AJAX handlers and Elementor widget controls. Authenticated attackers with Contributor-level access can enumerate private post titles and user details (including logins and emails) because the plugin fails to perform capability checks or verify post visibility status before returning data.

Vulnerable Code

// modules/square-plus-tab-block/widgets/square-plus-tab-block.php (v1.5.4)
// The widget fetches page content based on a selected ID without checking if the post is published or private.

} else if ($tab['content_from'] == 'page') {
    $square_tab_page = $tab['select_page'];
    if ($square_tab_page) {
        // ...
        $get_id = $square_tab_page;
        // ...
        // Get template content
        $template_id = get_post($get_id);

        if ($template_id && !is_wp_error($template_id)) {
            $content = $template_id->post_content;
            if (\Elementor\Plugin::$instance->editor->is_edit_mode()) {
                echo $content;
            } else {
                echo apply_filters('the_content', $content);
            }
        }
    }
}

---

// inc/ajax-select.php (Inferred from research plan)
// The plugin registers AJAX handlers that lack capability checks and return post/user data.

add_action( 'wp_ajax_he_ajax_select2_get_posts', 'he_ajax_select2_get_posts_handler' );
add_action( 'wp_ajax_he_ajax_select2_get_users', 'he_ajax_select2_get_users_handler' );

// These handlers use get_posts() or WP_User_Query without checking read_private_posts or list_users capabilities.

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/hash-elements/1.5.4/modules/square-plus-tab-block/widgets/square-plus-tab-block.php	2025-12-07 16:36:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/hash-elements/1.5.5/modules/square-plus-tab-block/widgets/square-plus-tab-block.php	2026-04-08 10:00:28.000000000 +0000
@@ -354,55 +354,42 @@
                     $tab_id = 'he-' . $id . $tab_count;
                     ?>
                     <div class="he-tab-pane animated zoomIn" id="<?php echo esc_attr($tab_id); ?>">
-                        <?php
-                        if ($tab['content_from'] == 'wisiwyg') {
-                            ?>
-                            <div class="he-content"><?php echo do_shortcode($tab['content']); ?></div>
+                        <div class="he-content">
                             <?php
-                        } else if ($tab['content_from'] == 'page') {
-                            $square_tab_page = $tab['select_page'];
-                            if ($square_tab_page) {
-                                ?>
-                                    <div class="he-content">
-                                        <?php
-                                        // Get ID
-                                        $get_id = $square_tab_page;
-                                        // Check if page is Elementor page
-                                        $elementor = get_post_meta($get_id, '_elementor_edit_mode', true);
-                                        $siteorigin = get_post_meta($get_id, 'panels_data', true);
+                            if (isset($tab['content_from']) && $tab['content_from'] === 'page' && !empty($tab['content_from'])) {
+                                $page_id = $tab['select_page'];
+                                $post_obj = get_post($page_id);
+                                $siteorigin = get_post_meta($page_id, 'panels_data', true);
 
-                                        // If Elementor
-                                        if (class_exists('Elementor\Plugin') && $elementor) {
-                                            echo \Elementor\Plugin::instance()->frontend->get_builder_content_for_display($get_id);
-                                        }
+                                if ($post_obj && $post_obj->post_status === 'publish' && !post_password_required($post_obj)) {
+                                    if (\Elementor\Plugin::$instance->db->is_built_with_elementor($page_id)) {
+                                        echo 'yes';
+                                        echo \Elementor\Plugin::$instance->frontend->get_builder_content_for_display($page_id);
+                                    }
 
-                                        // If Beaver Builder
-                                        else if (class_exists('FLBuilder') && !empty($get_id)) {
-                                            echo do_shortcode('[fl_builder_insert_layout id="' . $get_id . '"]');
-                                        }
+                                    // If Beaver Builder
+                                    elseif (class_exists('FLBuilder') && !empty($page_id)) {
+                                        echo do_shortcode('[fl_builder_insert_layout id="' . $page_id . '"]');
+                                    }
 
-                                        // If Site Origin
-                                        else if (class_exists('SiteOrigin_Panels') && $siteorigin) {
-                                            echo SiteOrigin_Panels::renderer()->render($get_id);
-                                        } else {
-                                            // Get template content
-                                            $template_id = get_post($get_id);
-
-                                            if ($template_id && !is_wp_error($template_id)) {
-                                                $content = $template_id->post_content;
-                                                if (\Elementor\Plugin::$instance->editor->is_edit_mode()) {
-                                                    echo $content;
-                                                } else {
-                                                    echo apply_filters('the_content', $content);
-                                                }
+                                    // If Site Origin
+                                    elseif (class_exists('SiteOrigin_Panels') && $siteorigin) {
+                                        echo SiteOrigin_Panels::renderer()->render($page_id);
+                                    } else {
+                                        if ($post_obj && !is_wp_error($post_obj)) {
+                                            if (\Elementor\Plugin::$instance->editor->is_edit_mode()) {
+                                                echo do_shortcode($post_obj->post_content);
+                                            } else {
+                                                echo apply_filters('the_content', $post_obj->post_content);
                                             }
                                         }
-                                        ?>
-                                    </div>
-                                <?php
+                                    }
+                                }
+                            } elseif (isset($tab['content_from']) && $tab['content_from'] === 'wisiwyg' && !empty($tab['content_from'])) {
+                                echo wp_kses_post(do_shortcode($tab['content']));
                             }
-                        }
-                        ?>
+                            ?>
+                        </div>
                     </div>
                     <?php

Exploit Outline

1. Login to the WordPress site as a user with Contributor-level privileges (or higher). 2. Open any post or page for editing using the Elementor editor to ensure the plugin's backend assets are loaded. 3. Inspect the page source or use the browser console to extract the security nonce used by the Hash Elements plugin (localized under an object like `window.hash_elements_ajax` or similar). 4. Using a tool like CURL or Postman, send a POST request to `/wp-admin/admin-ajax.php` with the parameter `action=he_ajax_select2_get_posts`, the extracted nonce, and a search query `q` (e.g., 'Secret'). 5. The server will respond with a JSON object containing the titles and IDs of posts, including those with 'private' status that the Contributor is not authorized to see. 6. Repeat the process using `action=he_ajax_select2_get_users` to enumerate user logins and potentially sensitive metadata like email addresses.

Check if your site is affected.

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