PluginProbe
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP / 1.2.32
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP v1.2.32
1.2.74 1.2.73 1.2.72 1.2.71 1.2.70 1.2.69 1.2.68 1.2.67 1.2.66 1.2.65 1.2.64 1.2.63 trunk 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.20 1.0.21 All 174 releases
← All changes | includes/class-import-export.php +8 -143 1.2.681.2.32 View file →
@@ -21,47 +21,9 @@
21 21 private $step;
22 22 private $file;
23 23 private $filename;
24 24
25 - /**
26 - * Columns that are NEVER writable via CSV import.
27 - * Denylist wins over allowlist — a column here can never be accidentally
28 - * re-enabled by adding it to $import_meta_allowlist.
29 - *
30 - * @var array
31 - */
32 - private static $import_meta_denylist = array(
33 - 'user_id', // Primary key — never importable.
34 - 'old_password', // Credential-adjacent — must not be set via import.
35 - );
36 25
37 - /**
38 - * Columns that ARE permitted in a CSV import (positive / allowlist).
39 - * Everything not listed here is silently skipped — default-deny.
40 - * Add new safe meta keys here deliberately; never use a wildcard.
41 - *
42 - * @var array
43 - */
44 - private static $import_meta_allowlist = array(
45 - // Core WP user fields
46 - 'username',
47 - 'email',
48 - 'display_name',
49 - 'first_name',
50 - 'last_name',
51 - 'description',
52 - 'user_url',
53 - 'user_registered',
54 - 'role',
55 - // uwp_usermeta safe fields
56 - 'bio',
57 - 'phone',
58 - 'user_privacy',
59 - 'avatar_thumb',
60 - 'banner_thumb',
61 - );
62 -
63 -
64 26 public function __construct() {
65 27 global $wp_filesystem;
66 28
67 29 if ( empty( $wp_filesystem ) ) {
@@ -81,8 +43,9 @@
81 43 add_action( 'admin_init', array($this, 'process_settings_import') );
82 44 add_action( 'wp_ajax_uwp_ajax_export_users', array( $this, 'process_users_export' ) );
83 45 add_action( 'wp_ajax_uwp_ajax_import_users', array( $this, 'process_users_import' ) );
84 46 add_action( 'wp_ajax_uwp_ie_upload_file', array( $this, 'ie_upload_file' ) );
47 + add_action( 'wp_ajax_nopriv_uwp_ie_upload_file', array( $this, 'ie_upload_file' ) );
85 48 add_action( 'admin_notices', array($this, 'ie_admin_notice') );
86 49 add_filter( 'uwp_get_export_users_status', array( $this, 'get_export_users_status' ) );
87 50 add_filter( 'uwp_get_import_users_status', array( $this, 'get_import_users_status' ) );
88 51 }
@@ -150,13 +113,13 @@
150 113 return;
151 114 $extension = explode( '.', $_FILES['import_file']['name'] );
152 115 $extension = end( $extension );
153 116 if( $extension != 'json' ) {
154 - wp_die( esc_html( wp_sprintf( __( 'Please upload a valid .json file. %sGo Back%s', 'userswp' ), '<a href="' . esc_url( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings' ) ) . '">', '</a>' ) ) );
117 + wp_die( esc_html( wp_sprintf( __( 'Please upload a valid .json file. %sGo Back%s' ), '<a href="' . esc_url( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings' ) ) . '">', '</a>' ) ) );
155 118 }
156 119 $import_file = $_FILES['import_file']['tmp_name'];
157 120 if( empty( $import_file ) ) {
158 - wp_die( esc_html( wp_sprintf( __( 'Please upload a file to import. %sGo Back%s', 'userswp' ), '<a href="' . esc_url( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings' ) ) . '">', '</a>' ) ) );
121 + wp_die( esc_html( wp_sprintf( __( 'Please upload a file to import. %sGo Back%s' ), '<a href="' . esc_url( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings' ) ) . '">', '</a>' ) ) );
159 122 }
160 123 // Retrieve the settings from the file and convert the json object to an array.
161 124 $settings = (array) json_decode( file_get_contents( $import_file ), true );
162 125 update_option( 'uwp_settings', $settings );
@@ -494,14 +457,12 @@
494 457 *
495 458 */
496 459 public function ie_upload_file(){
497 460
498 - if ( ! current_user_can( 'manage_options' ) ) {
499 - wp_send_json_error( array( 'message' => __( 'Permission denied.', 'userswp' ) ), 403 );
461 + if ( !(!empty($_REQUEST['nonce']) && wp_verify_nonce( $_REQUEST['nonce'], 'uwp-ie-file-upload-nonce' )) ) {
462 + echo 'error';return;
500 463 }
501 464
502 - check_ajax_referer( 'uwp-ie-file-upload-nonce', 'nonce' );
503 -
504 465 $upload_data = array(
505 466 'name' => $_FILES['import_file']['name'],
506 467 'type' => $_FILES['import_file']['type'],
507 468 'tmp_name' => $_FILES['import_file']['tmp_name'],
@@ -708,15 +669,12 @@
708 669 }
709 670
710 671 if( !is_wp_error( $user_id ) ){
711 672 foreach ($row as $key => $value){
712 - //Only write columns on the allowlist; denylist always wins.
713 - if ( ! $this->is_importable_column( $key ) ) {
714 - continue;
673 + if(!in_array($key, $exclude)){
674 + $value = maybe_unserialize($value);
675 + uwp_update_usermeta($user_id, $key, $value);
715 676 }
716 - //Never deserialize CSV input. Cast to safe scalar string.
717 - $value = $this->sanitize_import_value( $key, $value );
718 - uwp_update_usermeta($user_id, $key, $value);
719 677 }
720 678 } else {
721 679 $return['msg'] = sprintf(__('Row - %s Error: %s','userswp'), $this->imp_step, $user_id->get_error_message());
722 680 continue;
@@ -841,101 +799,8 @@
841 799
842 800 public function allowed_upload_mimes($mimes = array()) {
843 801 $mimes['csv'] = "text/csv";
844 802 return $mimes;
845 - }
846 -
847 - /**
848 - * Sanitize a single CSV import value.
849 - *
850 - * CSV data is always a plain string. There is no legitimate reason for it
851 - * to contain serialized PHP. We detect the serialization type-prefix
852 - * signatures, reject them with a log entry, and return an empty string.
853 - * All other values are cast to string and sanitized with sanitize_text_field().
854 - *
855 - * @param string $key The CSV column / meta key name.
856 - * @param mixed $value The raw value from the CSV row.
857 - * @return string A safe scalar string ready for DB insertion.
858 - */
859 - private function sanitize_import_value( $key, $value ) {
860 - // Reject serialized payloads
861 - if ( is_string( $value ) && preg_match( '/^[aAbBdDiIoOsScCnN][:;]/', ltrim( $value ) ) ) {
862 - if ( function_exists( 'uwp_log' ) ) {
863 - uwp_log( sprintf( 'Import security: serialized payload in column "%s" — discarded.', esc_attr( $key ) ) );
864 - }
865 - return '';
866 - }
867 -
868 - // File path columns: validate as a URL pointing inside the uploads directory only
869 - if ( in_array( $key, array( 'avatar_thumb', 'banner_thumb' ), true ) ) {
870 - return $this->sanitize_import_thumb( $value );
871 - }
872 -
873 - return sanitize_text_field( (string) $value );
874 - }
875 -
876 - /**
877 - * Sanitizes a thumbnail path value from CSV import.
878 - *
879 - * Validates that the given path is a real, existing file located within
880 - * the WordPress uploads directory, preventing path traversal attacks and
881 - * references to arbitrary files outside the uploads directory.
882 - *
883 - * @param string $value Raw thumbnail path value from the CSV row.
884 - * @return string Resolved absolute path if valid, empty string otherwise.
885 - */
886 - private function sanitize_import_thumb( $value ) {
887 - $value = trim( (string) $value );
888 -
889 - if ( empty( $value ) ) {
890 - return '';
891 - }
892 -
893 - // Resolve any ../ traversal attempts before comparison
894 - $real = realpath( $value );
895 -
896 - if ( $real === false ) {
897 - return ''; // Path doesn't exist on disk — reject
898 - }
899 -
900 - // Must stay within the uploads directory
901 - $uploads = wp_upload_dir();
902 - $base_dir = trailingslashit( realpath( $uploads['basedir'] ) );
903 -
904 - if ( strpos( $real . DIRECTORY_SEPARATOR, $base_dir ) !== 0 ) {
905 - if ( function_exists( 'uwp_log' ) ) {
906 - uwp_log( sprintf(
907 - 'Import security: thumb path "%s" is outside uploads directory — discarded.',
908 - esc_attr( $value )
909 - ) );
910 - }
911 - return '';
912 - }
913 -
914 - // Must be an allowed image extension
915 - $ext = strtolower( pathinfo( $real, PATHINFO_EXTENSION ) );
916 - if ( ! in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ), true ) ) {
917 - return '';
918 - }
919 -
920 - return $real; // Return the resolved, canonical path
921 - }
922 -
923 - /**
924 - * Return true only when the given column name is permitted for CSV import.
925 - *
926 - * @param string $column The CSV column / meta key name.
927 - * @return bool
928 - */
929 - private function is_importable_column( $column ) {
930 - $column = strtolower( trim( (string) $column ) );
931 -
932 - // Denylist is checked first — it unconditionally blocks.
933 - if ( in_array( $column, self::$import_meta_denylist, true ) ) {
934 - return false;
935 - }
936 -
937 - return in_array( $column, self::$import_meta_allowlist, true );
938 803 }
939 804
940 805 /**
941 806 * Escape a string to be used in a CSV export.