Skip to content

Commit c9291d9

Browse files
authored
UR-4593 Fix - Patch unauthenticated privilege escalation and user deletion vulnerabilities (#1308)
* fix: read membership tier allow-list from stored form definition (UR-4593) The tier allow-list check in process_membership_after_registration() was reading general_setting from the runtime $valid_form_data objects, which do not carry general_setting at that point — it is always null. This caused the listing_option to fall back to "all", accepting every active membership tier including off-form admin-role tiers. Fix: load the membership field's general_setting from the form's stored post_content via $form_id instead of from the submission object. * fix: guard wp_delete_user() against unauthenticated callers in Stripe failure paths (UR-4593)
1 parent 020fe57 commit c9291d9

3 files changed

Lines changed: 60 additions & 34 deletions

File tree

modules/membership/includes/AJAX.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1129,7 +1129,9 @@ public static function create_stripe_subscription() {
11291129
wp_send_json_success( $stripe_subscription );
11301130
} else {
11311131
if ( ! $is_upgrading && ! $is_renewing && ! $is_purchasing_multiple ) {
1132-
wp_delete_user( absint( $member_id ) );
1132+
if ( absint( $member_id ) === get_current_user_id() || current_user_can( 'edit_users' ) ) {
1133+
wp_delete_user( absint( $member_id ) );
1134+
}
11331135
}
11341136

11351137
wp_send_json_error(

modules/membership/includes/Admin.php

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -309,48 +309,68 @@ public function process_membership_after_registration( $success_params, $valid_f
309309
}
310310

311311
// Validate that the submitted membership ID is one the form is actually configured to offer.
312-
// This prevents an attacker from supplying an off-form tier (e.g. Administrator-role) in members_data.
313-
if ( $membership_field_data ) {
314-
$listing_option = isset( $membership_field_data->general_setting->membership_listing_option )
315-
? $membership_field_data->general_setting->membership_listing_option
316-
: 'all';
317-
$allowed_membership_ids = array();
318-
319-
if ( 'group' === $listing_option ) {
320-
$group_id = isset( $membership_field_data->general_setting->membership_group )
321-
? absint( $membership_field_data->general_setting->membership_group )
322-
: 0;
323-
if ( $group_id ) {
324-
$group_service = new MembershipGroupService();
325-
$group_memberships = $group_service->get_group_memberships( $group_id );
326-
foreach ( $group_memberships as $m ) {
327-
$id = isset( $m['ID'] ) ? (int) $m['ID'] : ( isset( $m['id'] ) ? (int) $m['id'] : 0 );
328-
if ( $id ) {
329-
$allowed_membership_ids[] = $id;
312+
// Read field config from the stored form definition (post_content), not the runtime
313+
// submission object — submission objects do not carry general_setting at this point.
314+
$stored_membership_config = null;
315+
$form_post = get_post( absint( $form_id ) );
316+
if ( $form_post && ! empty( $form_post->post_content ) ) {
317+
$form_rows = json_decode( $form_post->post_content );
318+
foreach ( (array) $form_rows as $row ) {
319+
foreach ( (array) $row as $grid ) {
320+
foreach ( (array) $grid as $field ) {
321+
if ( isset( $field->field_key ) && 'membership' === $field->field_key ) {
322+
$stored_membership_config = $field;
323+
break 3;
330324
}
331325
}
332326
}
333-
} elseif ( 'selected' === $listing_option ) {
334-
$selected_ids = isset( $membership_field_data->general_setting->membership_active_memberships )
335-
? $membership_field_data->general_setting->membership_active_memberships
336-
: array();
337-
$selected_ids = is_array( $selected_ids ) ? $selected_ids : (array) maybe_unserialize( $selected_ids );
338-
$allowed_membership_ids = array_values( array_filter( array_map( 'absint', $selected_ids ) ) );
339-
} else {
340-
$membership_service_temp = new MembershipService();
341-
foreach ( $membership_service_temp->list_active_memberships() as $m ) {
327+
}
328+
}
329+
330+
if ( ! $stored_membership_config ) {
331+
wp_delete_user( absint( $member_id ) );
332+
wp_send_json_error( array( 'message' => esc_html__( 'Invalid membership selection.', 'user-registration' ) ) );
333+
}
334+
335+
$listing_option = isset( $stored_membership_config->general_setting->membership_listing_option )
336+
? $stored_membership_config->general_setting->membership_listing_option
337+
: 'all';
338+
$allowed_membership_ids = array();
339+
340+
if ( 'group' === $listing_option ) {
341+
$group_id = isset( $stored_membership_config->general_setting->membership_group )
342+
? absint( $stored_membership_config->general_setting->membership_group )
343+
: 0;
344+
if ( $group_id ) {
345+
$group_service = new MembershipGroupService();
346+
$group_memberships = $group_service->get_group_memberships( $group_id );
347+
foreach ( $group_memberships as $m ) {
342348
$id = isset( $m['ID'] ) ? (int) $m['ID'] : ( isset( $m['id'] ) ? (int) $m['id'] : 0 );
343349
if ( $id ) {
344350
$allowed_membership_ids[] = $id;
345351
}
346352
}
347353
}
348-
349-
if ( empty( $allowed_membership_ids ) || ! in_array( absint( $data['membership'] ), $allowed_membership_ids, true ) ) {
350-
wp_delete_user( absint( $member_id ) );
351-
wp_send_json_error( array( 'message' => esc_html__( 'Invalid membership selection.', 'user-registration' ) ) );
354+
} elseif ( 'selected' === $listing_option ) {
355+
$selected_ids = isset( $stored_membership_config->general_setting->membership_active_memberships )
356+
? $stored_membership_config->general_setting->membership_active_memberships
357+
: array();
358+
$selected_ids = is_array( $selected_ids ) ? $selected_ids : (array) maybe_unserialize( $selected_ids );
359+
$allowed_membership_ids = array_values( array_filter( array_map( 'absint', $selected_ids ) ) );
360+
} else {
361+
$membership_service_temp = new MembershipService();
362+
foreach ( $membership_service_temp->list_active_memberships() as $m ) {
363+
$id = isset( $m['ID'] ) ? (int) $m['ID'] : ( isset( $m['id'] ) ? (int) $m['id'] : 0 );
364+
if ( $id ) {
365+
$allowed_membership_ids[] = $id;
366+
}
352367
}
353368
}
369+
370+
if ( empty( $allowed_membership_ids ) || ! in_array( absint( $data['membership'] ), $allowed_membership_ids, true ) ) {
371+
wp_delete_user( absint( $member_id ) );
372+
wp_send_json_error( array( 'message' => esc_html__( 'Invalid membership selection.', 'user-registration' ) ) );
373+
}
354374
$data['username'] = $member->user_login;
355375
$data['email'] = $member->user_email;
356376

modules/membership/includes/Admin/Services/Stripe/StripeService.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,9 @@ public function update_order( $data ) {
941941
do_action( 'ur_membership_order_status_failed', $latest_order['ID'], $latest_order, 'failed' );
942942

943943
if ( ! $is_upgrading && ! $is_renewing && ! $is_purchasing_multiple ) {
944-
wp_delete_user( absint( $member_id ) );
944+
if ( absint( $member_id ) === get_current_user_id() || current_user_can( 'edit_users' ) ) {
945+
wp_delete_user( absint( $member_id ) );
946+
}
945947
$this->members_orders_repository->delete_member_order( $member_id );
946948
}
947949
if ( $is_renewing ) {
@@ -1623,7 +1625,9 @@ public function create_subscription( $customer_id, $payment_method_id, $member_i
16231625
);
16241626

16251627
if ( ! $is_upgrading && ! $is_renewing ) {
1626-
wp_delete_user( absint( $member_id ) );
1628+
if ( absint( $member_id ) === get_current_user_id() || current_user_can( 'edit_users' ) ) {
1629+
wp_delete_user( absint( $member_id ) );
1630+
}
16271631
$this->members_orders_repository->delete_member_order( $member_id );
16281632
$customer = \Stripe\Customer::retrieve( $customer_id );
16291633
$customer->delete();

0 commit comments

Comments
 (0)