Envira Gallery – Image Photo Gallery, Albums, Video Gallery, Slideshows & More <= 1.12.5 - Missing Authorization
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:NTechnical Details
<=1.12.5What Changed in the Fix
Changes introduced in v1.12.6
Source Code
WordPress.org SVN) `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
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
@@ -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.