CVE-2026-54190

Envira Gallery – Image Photo Gallery, Albums, Video Gallery, Slideshows & More <= 1.12.5 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
1.12.6
Patched in
9d
Time to patch

Description

The Envira Gallery – Image Photo Gallery, Albums, Video Gallery, Slideshows & More plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.12.5. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.12.5
PublishedJune 15, 2026
Last updatedJune 23, 2026
Affected pluginenvira-gallery-lite

What Changed in the Fix

Changes introduced in v1.12.6

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

) `envira_convert_process_items` (>) What if the `permission_callback` returns `true` if the user is unauthenticated? No. Let's look at `OnboardingWizard` again. Many Envira vulnerabilities are in the AJAX handlers. But the description says "Missing Authorization". …

Show full research plan

)
envira_convert_process_items (>)

What if the `permission_callback` returns `true` if the user is unauthenticated?
No.

Let's look at `OnboardingWizard` again.
Many Envira vulnerabilities are in the AJAX handlers.
But the description says "Missing Authorization".

Let's try to find if `Convert_Gallery_REST` has a vulnerability.
What if unauthenticated users can call `envira_convert_process_items`?
It has a `permission_callback` called `verify_process_gallery_permission`.
If `$this->can_edit_post( $post_id )` returns `true` for unauthenticated users...
Why would it?
Maybe if `$post_id` is a post that is "public"?
In WordPress, `current_user_can('edit_post', $post_id)` returns false for unauthenticated users regardless of the post status.

*Aha!* I see something in the `envira-gallery-lite.php` file.
```php
	// Load global components.
	$this->require_global();

	// Load admin only components.
	if ( is_admin() ) {
		$this->require_admin();
	}
```

Wait, look at the `require_global()` call. It loads `rest.php`.
Inside
Research Findings
Static analysis — not yet PoC-verified

Summary

The Envira Gallery plugin for WordPress is vulnerable to unauthorized access because its REST API permission callbacks return a WP_REST_Response object instead of a boolean or WP_Error upon failure. This causes the WordPress REST API to treat the result as truthy, allowing unauthenticated attackers to bypass authorization and trigger gallery conversion or processing actions.

Vulnerable Code

// includes/global/convert_gallery/Convert_Gallery_REST.php:83
public function verify_convert_gallery_permission( $request ) {

    // Get post ID from request - support both postId and post_id for backward compatibility.
    $post_id = absint( $request->get_param( 'post_id' ) );
    if ( $post_id <= 0 ) {
        $post_id = absint( $request->get_param( 'postId' ) );
    }

    if ( $post_id <= 0 ) {
        return new WP_REST_Response( [ 'message' => __( 'A valid post ID is required.', 'envira-gallery-lite' ) ], 400 );
    }

    // Check if user can edit the post.
    if ( ! $this->can_edit_post( $post_id ) ) {
        return new WP_REST_Response( [ 'message' => __( 'You do not have permission to edit this post.', 'envira-gallery-lite' ) ], 403 );
    }

    return true;
}

---

// includes/global/convert_gallery/Convert_Gallery_REST.php:144
public function verify_process_gallery_permission( $request ) {

    // Get post ID from request - support both postId and post_id for backward compatibility.
    $post_id = absint( $request->get_param( 'post_id' ) );
    // ... (truncated)
    // Check if user can edit the post.
    if ( ! $this->can_edit_post( $post_id ) ) {
        return new WP_REST_Response( [ 'error' => __( 'You do not have permission to edit this post.', 'envira-gallery-lite' ) ], 403 );
    }

    return true;
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/envira-gallery-lite/1.12.5/includes/global/convert_gallery/Convert_Gallery_REST.php /home/deploy/wp-safety.org/data/plugin-versions/envira-gallery-lite/1.12.6/includes/global/convert_gallery/Convert_Gallery_REST.php
--- /home/deploy/wp-safety.org/data/plugin-versions/envira-gallery-lite/1.12.5/includes/global/convert_gallery/Convert_Gallery_REST.php	2026-04-23 16:54:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/envira-gallery-lite/1.12.6/includes/global/convert_gallery/Convert_Gallery_REST.php	2026-05-27 14:58:50.000000000 +0000
@@ -61,7 +61,7 @@
 	 * Verify REST request nonce.
 	 *
 	 * @param WP_REST_Request $request Request object.
-	 * @return bool|WP_REST_Response
+	 * @return bool|WP_Error
 	 */
 	public function verify_rest_nonce( $request ) {
 		// Get the nonce from the request header.
@@ -69,7 +69,7 @@
 
 		// Verify the nonce.
 		if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
-			return new WP_REST_Response( [ 'message' => __( 'Security check failed. You are not authorized. Please refresh and try again.', 'envira-gallery-lite' ) ], 403 );
+			return new WP_Error( 'rest_forbidden', __( 'Security check failed. You are not authorized. Please refresh and try again.', 'envira-gallery-lite' ), [ 'status' => 403 ] );
 		}
 
 		// Return true if valid.
@@ -80,10 +80,14 @@
 	 * Permission callback for single gallery conversion.
 	 *
 	 * @param WP_REST_Request $request Request object.
-	 * @return bool|WP_REST_Response
+	 * @return bool|WP_Error
 	 */
 	public function verify_convert_gallery_permission( $request ) {
 
+		if ( ! is_user_logged_in() ) {
+			return new WP_Error( 'rest_not_logged_in', __( 'You must be logged in to perform this action.', 'envira-gallery-lite' ), [ 'status' => 401 ] );
+		}
+
 		// Get post ID from request - support both postId and post_id for backward compatibility.
 		$post_id = absint( $request->get_param( 'post_id' ) );
 		if ( $post_id <= 0 ) {
@@ -91,12 +95,12 @@
 		}
 
 		if ( $post_id <= 0 ) {
-			return new WP_REST_Response( [ 'message' => __( 'A valid post ID is required.', 'envira-gallery-lite' ) ], 400 );
+			return new WP_Error( 'rest_bad_request', __( 'A valid post ID is required.', 'envira-gallery-lite' ), [ 'status' => 400 ] );
 		}
 
 		// Check if user can edit the post.
 		if ( ! $this->can_edit_post( $post_id ) ) {
-			return new WP_REST_Response( [ 'message' => __( 'You do not have permission to edit this post.', 'envira-gallery-lite' ) ], 403 );
+			return new WP_Error( 'rest_forbidden', __( 'You do not have permission to edit this post.', 'envira-gallery-lite' ), [ 'status' => 403 ] );
 		}
 
 		return true;
@@ -106,32 +110,36 @@
 	 * Permission callback for bulk gallery conversion.
 	 *
 	 * @param WP_REST_Request $request Request object.
-	 * @return bool|WP_REST_Response
+	 * @return bool|WP_Error
 	 */
 	public function verify_bulk_convert_permission( $request ) {
 
+		if ( ! is_user_logged_in() ) {
+			return new WP_Error( 'rest_not_logged_in', __( 'You must be logged in to perform this action.', 'envira-gallery-lite' ), [ 'status' => 401 ] );
+		}
+
 		// Check bulk conversion capability.
 		$capability = apply_filters( 'envira_convert_bulk_galleries_cap', 'manage_options' );
 		if ( ! current_user_can( $capability ) ) {
-			return new WP_REST_Response( [ 'message' => __( 'You do not have permission to access this feature.', 'envira-gallery-lite' ) ], 403 );
+			return new WP_Error( 'rest_forbidden', __( 'You do not have permission to access this feature.', 'envira-gallery-lite' ), [ 'status' => 403 ] );
 		}
 
 		// Get post type from request.
 		$selected_posttype = sanitize_text_field( $request->get_param( 'selected_posttype' ) );
 
 		if ( empty( $selected_posttype ) ) {
-			return new WP_REST_Response( [ 'message' => __( 'A post type is required for conversion. Please make a selection.', 'envira-gallery-lite' ) ], 400 );
+			return new WP_Error( 'rest_bad_request', __( 'A post type is required for conversion. Please make a selection.', 'envira-gallery-lite' ), [ 'status' => 400 ] );
 		}
 
 		if ( ! post_type_exists( $selected_posttype ) ) {
-			return new WP_REST_Response( [ 'message' => __( 'Post type not recognized. Please check your selection and try again.', 'envira-gallery-lite' ) ], 404 );
+			return new WP_Error( 'rest_not_found', __( 'Post type not recognized. Please check your selection and try again.', 'envira-gallery-lite' ), [ 'status' => 404 ] );
 		}
 
 		// Check if the current user can edit the selected post type.
 		$post_type_object = get_post_type_object( $selected_posttype );
 		if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) {
 			// translators: %s is the post type singular name.
-			return new WP_REST_Response( [ 'message' => sprintf( __( 'You do not have permission to edit %s item(s).', 'envira-gallery-lite' ), $post_type_object->labels->singular_name ) ], 403 );
+			return new WP_Error( 'rest_forbidden', sprintf( __( 'You do not have permission to edit %s item(s).', 'envira-gallery-lite' ), $post_type_object->labels->singular_name ), [ 'status' => 403 ] );
 		}
 
 		return true;
@@ -141,10 +149,14 @@
 	 * Permission callback for processing gallery items.
 	 *
 	 * @param WP_REST_Request $request Request object.
-	 * @return bool|WP_REST_Response
+	 * @return bool|WP_Error
 	 */
 	public function verify_process_gallery_permission( $request ) {
 
+		if ( ! is_user_logged_in() ) {
+			return new WP_Error( 'rest_not_logged_in', __( 'You must be logged in to perform this action.', 'envira-gallery-lite' ), [ 'status' => 401 ] );
+		}
+
 		// Get post ID from request - support both postId and post_id for backward compatibility.
 		$post_id = absint( $request->get_param( 'post_id' ) );
 		if ( $post_id <= 0 ) {
@@ -152,19 +164,19 @@
 		}
 
 		if ( $post_id <= 0 ) {
-			return new WP_REST_Response( [ 'error' => __( 'A valid post ID is required.', 'envira-gallery-lite' ) ], 400 );
+			return new WP_Error( 'rest_bad_request', __( 'A valid post ID is required.', 'envira-gallery-lite' ), [ 'status' => 400 ] );
 		}
 
 		// Get the post.
 		$post = get_post( $post_id );
 
 		if ( ! $post ) {
-			return new WP_REST_Response( [ 'error' => __( 'Post not found.', 'envira-gallery-lite' ) ], 400 );
+			return new WP_Error( 'rest_not_found', __( 'Post not found.', 'envira-gallery-lite' ), [ 'status' => 404 ] );
 		}
 
 		// Check if user can edit the post.
 		if ( ! $this->can_edit_post( $post_id ) ) {
-			return new WP_REST_Response( [ 'error' => __( 'You do not have permission to edit this post.', 'envira-gallery-lite' ) ], 403 );
+			return new WP_Error( 'rest_forbidden', __( 'You do not have permission to edit this post.', 'envira-gallery-lite' ), [ 'status' => 403 ] );
 		}
 
 		return true;

Exploit Outline

The exploit involves sending an unauthenticated POST request to the `/wp-json/envira-convert/v1/convert-gallery` or `/wp-json/envira-convert/v1/process-gallery` endpoints. Because the `permission_callback` returns a `WP_REST_Response` object when authorization fails (e.g., when `can_edit_post` is false), the WordPress REST API infrastructure incorrectly evaluates this object as `true`. This bypasses the intended capability check, allowing the unauthenticated request to reach the core callback logic, where an attacker can supply a `post_id` and image data to manipulate gallery content or trigger conversion processes.

Check if your site is affected.

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