diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php index 2c00329d6dd..3c21b55c6eb 100644 --- a/src/bp-members/bp-members-functions.php +++ b/src/bp-members/bp-members-functions.php @@ -4099,6 +4099,24 @@ function bp_assign_default_member_type_to_activate_user( $user_id, $key, $user ) $get_selected_member_type_on_register = ''; } + /* + * Harden the self-submitted Profile Type before it is trusted below. + * + * The registration form fully controls this value, so a visitor can post + * the ID of a Profile Type that is not offered on the registration form + * (its "_bp_member_type_enable_profile_field" is off) and have it — and + * its mapped WP role — assigned at activation. Ignore it, falling back + * to the admin-configured default type, whenever it is not actually + * offered at registration. Admin-driven paths (send-invite type, + * default registration type) are unaffected. + */ + if ( + '' !== $get_selected_member_type_on_register + && ! bb_is_member_type_allowed_on_registration( $get_selected_member_type_on_register ) + ) { + $get_selected_member_type_on_register = ''; + } + // return to user if default member type is not set. $existing_selected = bp_member_type_default_on_registration(); @@ -5580,3 +5598,39 @@ function bb_remove_orphaned_profile_slug( $user_id ) { bb_remove_orphaned_profile_slug( $user_id ); } } + +/** + * Check whether a profile type may be self-selected on the registration form. + * + * Mirrors the gate the registration Profile Type dropdown itself uses + * (see BP_XProfile_Field_Type_Member_Types::edit_field_options_html()): the + * type must be an active member-type post whose "_bp_member_type_enable_profile_field" + * meta is unset or '1'. Used to reject a Profile Type that was submitted at + * signup but is not actually offered on the registration form, closing the + * mass-assignment path where a hidden, role-mapped Profile Type could be + * self-assigned during registration. + * + * @since BuddyBoss [BBVERSION] + * + * @param int $member_type_id Member type post ID submitted at registration. + * + * @return bool True when the type is offered on the registration form. + */ +function bb_is_member_type_allowed_on_registration( $member_type_id ) { + $member_type_id = absint( $member_type_id ); + + if ( empty( $member_type_id ) ) { + return false; + } + + // Must be one of the active member-type posts (same source the dropdown iterates). + $active_member_types = array_map( 'absint', (array) bp_get_active_member_types() ); + if ( ! in_array( $member_type_id, $active_member_types, true ) ) { + return false; + } + + // Must be enabled for the registration profile field (same gate as the dropdown). + $enabled = get_post_meta( $member_type_id, '_bp_member_type_enable_profile_field', true ); + + return ( '' === $enabled || '1' === $enabled ); +} diff --git a/src/bp-members/screens/register.php b/src/bp-members/screens/register.php index 825ea4b37ef..334d54fcf59 100644 --- a/src/bp-members/screens/register.php +++ b/src/bp-members/screens/register.php @@ -175,6 +175,38 @@ function bp_core_screen_signup() { ); } } + + /* + * Reject a Profile Type that is not actually offered on the registration + * form. The hidden signup_profile_field_ids and field_ values are + * attacker-controllable, so a Profile Type not shown in the registration + * dropdown could otherwise be submitted and, via its WP role mapping, + * assigned at activation. The activation-time gate in + * bp_assign_default_member_type_to_activate_user() remains as a backstop + * for any path that bypasses this validation. + */ + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Registration is a public form processed without a nonce by design; matches the surrounding field handling. + $bb_submitted_member_type = isset( $_POST[ 'field_' . $field_id ] ) ? wp_unslash( $_POST[ 'field_' . $field_id ] ) : ''; + if ( + (int) bp_get_xprofile_member_type_field_id() === (int) $field_id + && ! empty( $bb_submitted_member_type ) + && ! bp_current_user_can( 'bp_moderate' ) + && ( + // An array is never a valid single-select submission; reject it + // (rather than skip validation) and short-circuit absint() so it + // is not called on an array. + is_array( $bb_submitted_member_type ) + || ! bb_is_member_type_allowed_on_registration( absint( $bb_submitted_member_type ) ) + ) + ) { + $bp->signup->errors[ 'field_' . $field_id ] = sprintf( + '
+ +

%s

+
', + __( 'Please select a valid profile type.', 'buddyboss' ) + ); + } } // This situation doesn't naturally occur so bounce to website root. diff --git a/src/bp-xprofile/classes/class-bp-xprofile-field-type-member-types.php b/src/bp-xprofile/classes/class-bp-xprofile-field-type-member-types.php index 8f614f77f1a..81642ec602f 100644 --- a/src/bp-xprofile/classes/class-bp-xprofile-field-type-member-types.php +++ b/src/bp-xprofile/classes/class-bp-xprofile-field-type-member-types.php @@ -220,12 +220,26 @@ public function admin_field_html( array $raw_properties = array() ) { /** * Check if valid. * + * A submitted value must reference an actual member-type post, not any + * arbitrary post ID. The previous check accepted any existing post, which + * let a crafted signup/profile payload store an unrelated post ID in this + * field. Whether that member type may be self-selected at registration is + * enforced separately at registration input (see the member-type check in + * bp_core_screen_signup(), src/bp-members/screens/register.php) and, as a + * backstop, at activation + * (see bp_assign_default_member_type_to_activate_user()), so this check + * stays context-free and does not break profile edits that keep a member's + * currently-assigned type. + * + * @since BuddyBoss 1.0.0 + * @since BuddyBoss [BBVERSION] Restrict to member-type posts only. + * * @param int $values post id. * * @return bool */ public function is_valid( $values ) { - return empty( $values ) || get_post( $values ); + return empty( $values ) || ( bp_get_member_type_post_type() === get_post_type( $values ) ); } /**