From 76425ec0198697ca5cd7b2f4748aeceb1f8a8d6b Mon Sep 17 00:00:00 2001 From: Jitendra Banjara Date: Thu, 20 Aug 2026 18:25:26 +0530 Subject: [PATCH 1/5] PROD-10104 - Validate registration profile type against the offered set before assigning its role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a privilege-escalation path (CWE-269) where a self-registering visitor could assign themselves any Profile Type — and its mapped WP role — by injecting the member-type xprofile field into the signup POST. The membertypes field's is_valid() accepted any post ID and nothing re-checked, at activation, whether the submitted type is actually offered on the registration form, so a Profile Type hidden from the registration dropdown but mapped to a privileged role (editor/admin) could be self-assigned. bp_assign_default_member_type_to_activate_user() now ignores a self-submitted Profile Type that is not offered at registration (bb_is_member_type_allowed_on_registration(): active member-type post whose _bp_member_type_enable_profile_field is unset or '1', mirroring the registration dropdown's own gate), falling back to the admin-configured default type. This is the single chokepoint the three registrant branches read, and it fires on bp_core_activated_user, so it covers both the web form and REST /signup. Admin-driven paths (send-invite type, default registration type) are unaffected. BP_XProfile_Field_Type_Member_Types::is_valid() additionally restricts the value to member-type posts (was: any post), rejecting an arbitrary post ID crafted into the field. Context-free, so profile edits that keep a member's currently-assigned type are unaffected. --- src/bp-members/bp-members-functions.php | 54 +++++++++++++++++++ ...ss-bp-xprofile-field-type-member-types.php | 14 ++++- 2 files changed, 67 insertions(+), 1 deletion(-) 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-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..c33b50056ed 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,24 @@ 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 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 ) ); } /** From a1d257ac438f3d5e47de4ae286c8e2da4f08c257 Mon Sep 17 00:00:00 2001 From: Jitendra Banjara Date: Thu, 20 Aug 2026 22:22:17 +0530 Subject: [PATCH 2/5] PROD-10104 - Reject registration profile types that are not offered on the signup form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a privilege-escalation path (CWE-269): a self-registering visitor could inject the member-type xprofile field into the signup POST and assign themselves any Profile Type — and its mapped WP role. The membertypes field's is_valid() accepted any post ID, and nothing checked that the submitted type is actually offered on the registration form, so a Profile Type hidden from the registration dropdown but mapped to a privileged role (editor/admin) could be self-assigned at activation. register.php now rejects, with a "Please select a valid profile type" error, a submitted member type that is not offered at registration (bb_is_member_type_allowed_on_registration(): active member-type post whose _bp_member_type_enable_profile_field is unset or '1', mirroring the registration dropdown's own gate). As a backstop, bp_assign_default_member_type_to_activate_user() ignores a not-offered self-submitted type at activation and falls back to the admin-configured default; this runs on bp_core_activated_user so it also covers REST signup. Admin-driven paths (send-invite type, default registration type) are unaffected. BP_XProfile_Field_Type_Member_Types::is_valid() additionally restricts the value to member-type posts (was: any post). Context-free, so profile edits that keep a member's currently-assigned type are unaffected. --- src/bp-members/screens/register.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/bp-members/screens/register.php b/src/bp-members/screens/register.php index 825ea4b37ef..454b95cfceb 100644 --- a/src/bp-members/screens/register.php +++ b/src/bp-members/screens/register.php @@ -175,6 +175,33 @@ 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 ] ) ? absint( wp_unslash( $_POST[ 'field_' . $field_id ] ) ) : 0; + if ( + function_exists( 'bp_get_xprofile_member_type_field_id' ) + && (int) bp_get_xprofile_member_type_field_id() === (int) $field_id + && ! empty( $bb_submitted_member_type ) + && ! bp_current_user_can( 'bp_moderate' ) + && ! bb_is_member_type_allowed_on_registration( $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. From 0e5b37bea9e436ea8414ad76dc17a6ecd2579531 Mon Sep 17 00:00:00 2001 From: Jitendra Banjara Date: Thu, 20 Aug 2026 23:41:43 +0530 Subject: [PATCH 3/5] PROD-10104 - Harden member-type registration check against array input Guards the self-submitted member-type value against an array-crafted field_ (e.g. field_85[]=x) before absint(), avoiding a PHP 8.1+ deprecation notice in bp_core_screen_signup(). Also updates the BP_XProfile_Field_Type_Member_Types::is_valid() docblock to note that registration eligibility is enforced at the register.php input check and, as a backstop, at bp_assign_default_member_type_to_activate_user(). --- src/bp-members/screens/register.php | 2 +- .../classes/class-bp-xprofile-field-type-member-types.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bp-members/screens/register.php b/src/bp-members/screens/register.php index 454b95cfceb..fce4a1eed88 100644 --- a/src/bp-members/screens/register.php +++ b/src/bp-members/screens/register.php @@ -186,7 +186,7 @@ function bp_core_screen_signup() { * 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 ] ) ? absint( wp_unslash( $_POST[ 'field_' . $field_id ] ) ) : 0; + $bb_submitted_member_type = ( isset( $_POST[ 'field_' . $field_id ] ) && ! is_array( $_POST[ 'field_' . $field_id ] ) ) ? absint( wp_unslash( $_POST[ 'field_' . $field_id ] ) ) : 0; if ( function_exists( 'bp_get_xprofile_member_type_field_id' ) && (int) bp_get_xprofile_member_type_field_id() === (int) $field_id 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 c33b50056ed..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 @@ -224,7 +224,9 @@ public function admin_field_html( array $raw_properties = array() ) { * 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 activation + * 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. From a3730705411a1d63fccb4e291053b29835162ae5 Mon Sep 17 00:00:00 2001 From: Jitendra Banjara Date: Thu, 20 Aug 2026 23:51:15 +0530 Subject: [PATCH 4/5] [PROD-10104] Type casting --- src/bp-members/screens/register.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bp-members/screens/register.php b/src/bp-members/screens/register.php index fce4a1eed88..d87b6e2d75d 100644 --- a/src/bp-members/screens/register.php +++ b/src/bp-members/screens/register.php @@ -188,8 +188,7 @@ function bp_core_screen_signup() { // 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 ] ) && ! is_array( $_POST[ 'field_' . $field_id ] ) ) ? absint( wp_unslash( $_POST[ 'field_' . $field_id ] ) ) : 0; if ( - function_exists( 'bp_get_xprofile_member_type_field_id' ) - && (int) bp_get_xprofile_member_type_field_id() === (int) $field_id + (int) bp_get_xprofile_member_type_field_id() === (int) $field_id && ! empty( $bb_submitted_member_type ) && ! bp_current_user_can( 'bp_moderate' ) && ! bb_is_member_type_allowed_on_registration( $bb_submitted_member_type ) From 5abfd52ebe1d1c0b2c0afdbc0de17c7fce96ab3e Mon Sep 17 00:00:00 2001 From: Jitendra Banjara Date: Fri, 21 Aug 2026 11:56:52 +0530 Subject: [PATCH 5/5] PROD-10104 - Reject array input for the profile type field on the register screen --- src/bp-members/screens/register.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/bp-members/screens/register.php b/src/bp-members/screens/register.php index d87b6e2d75d..334d54fcf59 100644 --- a/src/bp-members/screens/register.php +++ b/src/bp-members/screens/register.php @@ -186,12 +186,18 @@ function bp_core_screen_signup() { * 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 ] ) && ! is_array( $_POST[ 'field_' . $field_id ] ) ) ? absint( wp_unslash( $_POST[ 'field_' . $field_id ] ) ) : 0; + $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' ) - && ! bb_is_member_type_allowed_on_registration( $bb_submitted_member_type ) + && ( + // 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( '