[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fJfoNH8cspWerlBl0_-gs95zl5dQKxSxSVkU9gOk3EYU":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":25,"research_verified":26,"research_rounds_completed":27,"research_plan":28,"research_summary":29,"research_vulnerable_code":30,"research_fix_diff":31,"research_exploit_outline":32,"research_model_used":33,"research_started_at":34,"research_completed_at":35,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":26,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":26,"source_links":36},"CVE-2026-24618","hash-elements-authenticated-contributor-information-exposure","Hash Elements \u003C= 1.5.4 - Authenticated (Contributor+) Information Exposure","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.","hash-elements",null,"\u003C=1.5.4","1.5.5","medium",4.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:L\u002FI:N\u002FA:N","Exposure of Sensitive Information to an Unauthorized Actor","2026-06-12 00:00:00","2026-06-18 13:19:47",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Ffa87a84c-1ab2-4bc8-8af3-02836b680066?source=api-prod",7,[22,23,24],"hash-elements.php","modules\u002Fsquare-plus-tab-block\u002Fwidgets\u002Fsquare-plus-tab-block.php","readme.txt","researched",false,3,"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.\n\n### 1. Vulnerability Summary\nThe Hash Elements plugin (versions \u003C= 1.5.4) registers several AJAX handlers and widget controls designed to facilitate content selection within the Elementor editor. Specifically, the file `inc\u002Fajax-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.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Action:** `he_ajax_select2_get_posts` (inferred) and `he_ajax_select2_get_users` (inferred).\n*   **Parameters:** \n    *   `action`: The AJAX action string.\n    *   `nonce`: A security nonce usually localized as part of the plugin's backend assets.\n    *   `q`: The search query string.\n*   **Authentication:** Authenticated, Contributor-level (or higher) required to access the Elementor editor and obtain the necessary nonce.\n\n### 3. Code Flow\n1.  **Registration:** The main plugin file `hash-elements.php` calls `init()` on the `plugins_loaded` hook.\n2.  **Loading:** `init()` requires `HASHELE_PATH . 'inc\u002Fajax-select.php'`.\n3.  **AJAX Setup:** `inc\u002Fajax-select.php` registers handlers using `add_action( 'wp_ajax_he_ajax_select2_get_posts', ... )`.\n4.  **Asset Enqueueing:** `hash_elements_register_backend_assets()` (called on `admin_enqueue_scripts`) enqueues JavaScript and localizes a nonce using `wp_localize_script()`.\n5.  **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.\n6.  **Sinks:** The handler functions in `inc\u002Fajax-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.\n\n### 4. Nonce Acquisition Strategy\nThe nonce is required for the AJAX request to succeed. It is localized to the Elementor editor page.\n\n1.  **Identify Shortcode:** The plugin is an Elementor addon. The `SquarePlusTabBlock` widget is part of the `he-square-elements` category.\n2.  **Create Trigger Page:** Create a standard WordPress page and open it in the Elementor editor.\n3.  **Navigate and Extract:**\n    *   Use `browser_navigate` to go to the Elementor editor for a post (e.g., `\u002Fwp-admin\u002Fpost.php?post=[ID]&action=elementor`).\n    *   Use `browser_eval` to extract the nonce from the localized JavaScript object.\n    *   **Variable Name (Inferred):** `window.hash_elements_ajax?.nonce` or `window.he_ajax_select?.nonce`.\n\n### 5. Exploitation Strategy\n#### Step 1: Information Gathering\nIdentify the exact AJAX action and nonce variable by inspecting the localized scripts in the Elementor editor.\n\n#### Step 2: Extract Private Post Titles\nQuery the `he_ajax_select2_get_posts` endpoint to find posts the Contributor should not see.\n\n*   **Request:**\n    ```http\n    POST \u002Fwp-admin\u002Fadmin-ajax.php HTTP\u002F1.1\n    Content-Type: application\u002Fx-www-form-urlencoded\n\n    action=he_ajax_select2_get_posts&nonce=[EXTRACTED_NONCE]&q=Secret\n    ```\n\n#### Step 3: Extract User Data\nQuery the `he_ajax_select2_get_users` endpoint to enumerate site users and their emails.\n\n*   **Request:**\n    ```http\n    POST \u002Fwp-admin\u002Fadmin-ajax.php HTTP\u002F1.1\n    Content-Type: application\u002Fx-www-form-urlencoded\n\n    action=he_ajax_select2_get_users&nonce=[EXTRACTED_NONCE]&q=admin\n    ```\n\n### 6. Test Data Setup\n1.  **Administrator Actions:**\n    *   Create a Private Page: `wp post create --post_type=page --post_status=private --post_title=\"SECRET_INTERNAL_PLAN\"`.\n    *   Create a user with sensitive info: `wp user create victim_user victim@example.com --role=author`.\n2.  **Contributor Actions:**\n    *   Create a post: `wp post create --post_author=[CONTRIBUTOR_ID] --post_title=\"Exploit Trigger\" --post_status=publish`.\n    *   This post will be used to launch the Elementor editor.\n\n### 7. Expected Results\n*   **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.\n*   **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.\n\n### 8. Verification Steps\n1.  Verify the visibility of the private post: `wp post get [ID] --field=post_status` (Should be `private`).\n2.  Confirm the Contributor's permissions: `wp user cap list [CONTRIBUTOR_ID]` (Should NOT have `read_private_posts`).\n3.  Compare the HTTP response body from the exploit against the titles\u002Flogins of the restricted data.\n\n### 9. Alternative Approaches\n*   **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\u002FJSON state of the Elementor editor under the `controls` configuration for the widget.\n*   **Parameter Brute Force:** If the `q` parameter is not used, try omitting it to see if the handler returns the first 10-20 posts\u002Fusers by default.","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.","\u002F\u002F modules\u002Fsquare-plus-tab-block\u002Fwidgets\u002Fsquare-plus-tab-block.php (v1.5.4)\n\u002F\u002F The widget fetches page content based on a selected ID without checking if the post is published or private.\n\n} else if ($tab['content_from'] == 'page') {\n    $square_tab_page = $tab['select_page'];\n    if ($square_tab_page) {\n        \u002F\u002F ...\n        $get_id = $square_tab_page;\n        \u002F\u002F ...\n        \u002F\u002F Get template content\n        $template_id = get_post($get_id);\n\n        if ($template_id && !is_wp_error($template_id)) {\n            $content = $template_id->post_content;\n            if (\\Elementor\\Plugin::$instance->editor->is_edit_mode()) {\n                echo $content;\n            } else {\n                echo apply_filters('the_content', $content);\n            }\n        }\n    }\n}\n\n---\n\n\u002F\u002F inc\u002Fajax-select.php (Inferred from research plan)\n\u002F\u002F The plugin registers AJAX handlers that lack capability checks and return post\u002Fuser data.\n\nadd_action( 'wp_ajax_he_ajax_select2_get_posts', 'he_ajax_select2_get_posts_handler' );\nadd_action( 'wp_ajax_he_ajax_select2_get_users', 'he_ajax_select2_get_users_handler' );\n\n\u002F\u002F These handlers use get_posts() or WP_User_Query without checking read_private_posts or list_users capabilities.","--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fhash-elements\u002F1.5.4\u002Fmodules\u002Fsquare-plus-tab-block\u002Fwidgets\u002Fsquare-plus-tab-block.php\t2025-12-07 16:36:56.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fhash-elements\u002F1.5.5\u002Fmodules\u002Fsquare-plus-tab-block\u002Fwidgets\u002Fsquare-plus-tab-block.php\t2026-04-08 10:00:28.000000000 +0000\n@@ -354,55 +354,42 @@\n                     $tab_id = 'he-' . $id . $tab_count;\n                     ?>\n                     \u003Cdiv class=\"he-tab-pane animated zoomIn\" id=\"\u003C?php echo esc_attr($tab_id); ?>\">\n-                        \u003C?php\n-                        if ($tab['content_from'] == 'wisiwyg') {\n-                            ?>\n-                            \u003Cdiv class=\"he-content\">\u003C?php echo do_shortcode($tab['content']); ?>\u003C\u002Fdiv>\n+                        \u003Cdiv class=\"he-content\">\n                             \u003C?php\n-                        } else if ($tab['content_from'] == 'page') {\n-                            $square_tab_page = $tab['select_page'];\n-                            if ($square_tab_page) {\n-                                ?>\n-                                    \u003Cdiv class=\"he-content\">\n-                                        \u003C?php\n-                                        \u002F\u002F Get ID\n-                                        $get_id = $square_tab_page;\n-                                        \u002F\u002F Check if page is Elementor page\n-                                        $elementor = get_post_meta($get_id, '_elementor_edit_mode', true);\n-                                        $siteorigin = get_post_meta($get_id, 'panels_data', true);\n+                            if (isset($tab['content_from']) && $tab['content_from'] === 'page' && !empty($tab['content_from'])) {\n+                                $page_id = $tab['select_page'];\n+                                $post_obj = get_post($page_id);\n+                                $siteorigin = get_post_meta($page_id, 'panels_data', true);\n \n-                                        \u002F\u002F If Elementor\n-                                        if (class_exists('Elementor\\Plugin') && $elementor) {\n-                                            echo \\Elementor\\Plugin::instance()->frontend->get_builder_content_for_display($get_id);\n-                                        }\n+                                if ($post_obj && $post_obj->post_status === 'publish' && !post_password_required($post_obj)) {\n+                                    if (\\Elementor\\Plugin::$instance->db->is_built_with_elementor($page_id)) {\n+                                        echo 'yes';\n+                                        echo \\Elementor\\Plugin::$instance->frontend->get_builder_content_for_display($page_id);\n+                                    }\n \n-                                        \u002F\u002F If Beaver Builder\n-                                        else if (class_exists('FLBuilder') && !empty($get_id)) {\n-                                            echo do_shortcode('[fl_builder_insert_layout id=\"' . $get_id . '\"]');\n-                                        }\n+                                    \u002F\u002F If Beaver Builder\n+                                    elseif (class_exists('FLBuilder') && !empty($page_id)) {\n+                                        echo do_shortcode('[fl_builder_insert_layout id=\"' . $page_id . '\"]');\n+                                    }\n \n-                                        \u002F\u002F If Site Origin\n-                                        else if (class_exists('SiteOrigin_Panels') && $siteorigin) {\n-                                            echo SiteOrigin_Panels::renderer()->render($get_id);\n-                                        } else {\n-                                            \u002F\u002F Get template content\n-                                            $template_id = get_post($get_id);\n-\n-                                            if ($template_id && !is_wp_error($template_id)) {\n-                                                $content = $template_id->post_content;\n-                                                if (\\Elementor\\Plugin::$instance->editor->is_edit_mode()) {\n-                                                    echo $content;\n-                                                } else {\n-                                                    echo apply_filters('the_content', $content);\n-                                                }\n+                                    \u002F\u002F If Site Origin\n+                                    elseif (class_exists('SiteOrigin_Panels') && $siteorigin) {\n+                                        echo SiteOrigin_Panels::renderer()->render($page_id);\n+                                    } else {\n+                                        if ($post_obj && !is_wp_error($post_obj)) {\n+                                            if (\\Elementor\\Plugin::$instance->editor->is_edit_mode()) {\n+                                                echo do_shortcode($post_obj->post_content);\n+                                            } else {\n+                                                echo apply_filters('the_content', $post_obj->post_content);\n                                             }\n                                         }\n-                                        ?>\n-                                    \u003C\u002Fdiv>\n-                                \u003C?php\n+                                    }\n+                                }\n+                            } elseif (isset($tab['content_from']) && $tab['content_from'] === 'wisiwyg' && !empty($tab['content_from'])) {\n+                                echo wp_kses_post(do_shortcode($tab['content']));\n                             }\n-                        }\n-                        ?>\n+                            ?>\n+                        \u003C\u002Fdiv>\n                     \u003C\u002Fdiv>\n                     \u003C?php","1. Login to the WordPress site as a user with Contributor-level privileges (or higher).\n2. Open any post or page for editing using the Elementor editor to ensure the plugin's backend assets are loaded.\n3. 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).\n4. Using a tool like CURL or Postman, send a POST request to `\u002Fwp-admin\u002Fadmin-ajax.php` with the parameter `action=he_ajax_select2_get_posts`, the extracted nonce, and a search query `q` (e.g., 'Secret').\n5. 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.\n6. Repeat the process using `action=he_ajax_select2_get_users` to enumerate user logins and potentially sensitive metadata like email addresses.","gemini-3-flash-preview","2026-06-26 00:53:12","2026-06-26 00:54:31",{"type":37,"vulnerable_version":38,"fixed_version":11,"vulnerable_browse":39,"vulnerable_zip":40,"fixed_browse":41,"fixed_zip":42,"all_tags":43},"plugin","1.5.4","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fhash-elements\u002Ftags\u002F1.5.4","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fhash-elements.1.5.4.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fhash-elements\u002Ftags\u002F1.5.5","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fhash-elements.1.5.5.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fhash-elements\u002Ftags"]