PluginProbe
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP / 1.2.48
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP v1.2.48
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 1.0.22 All 173 releases
← All changes | includes/class-import-export.php +14 -148 trunk1.2.48 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 }
@@ -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'],
@@ -630,14 +591,14 @@
630 591 $return['msg'] = sprintf(__('Row - %s Error: '. 'Skipped due to invalid/no data.','userswp'), $this->imp_step);
631 592 continue;
632 593 }
633 594
634 - $username = isset($row['username']) ? sanitize_user($row['username']) : '';
635 - $email = isset($row['email']) ? sanitize_email($row['email']) : '';
636 - $first_name = isset($row['first_name']) ? sanitize_text_field($row['first_name']) : '';
637 - $last_name = isset($row['last_name']) ? sanitize_text_field($row['last_name']) : '';
638 - $bio = isset($row['bio']) ? sanitize_textarea_field($row['bio']) : '';
639 - $display_name = isset($row['display_name']) ? sanitize_text_field($row['display_name']) : '';
595 + $username = isset($row['username']) ? $row['username'] : '';
596 + $email = isset($row['email']) ? $row['email'] : '';
597 + $first_name = isset($row['first_name']) ? $row['first_name'] : '';
598 + $last_name = isset($row['last_name']) ? $row['last_name'] : '';
599 + $bio = isset($row['bio']) ? $row['bio'] : '';
600 + $display_name = isset($row['display_name']) ? $row['display_name'] : '';
640 601 $password = wp_generate_password();
641 602 $exclude = array('user_id');
642 603 $exclude = apply_filters('uwp_import_exclude_columns', $exclude, $row);
643 604
@@ -643,8 +604,9 @@
643 604
644 605 if(isset($row['username']) && username_exists($row['username'])){
645 606 $user = get_user_by('login', $row['username']);
646 607 $user_id = $user->ID;
608 + $email = $row['email'];
647 609 if( !empty( $email ) && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id) ) {
648 610 $args = array(
649 611 'ID' => $user_id,
650 612 'user_email' => $email,
@@ -661,9 +623,9 @@
661 623 } elseif((int)$row['user_id'] > 0){
662 624 $user = get_user_by('ID', $row['user_id']);
663 625 if(false === $user){
664 626 $userdata = array(
665 - 'user_login' => $username,
627 + 'user_login' => $row['username'],
666 628 'user_email' => $email,
667 629 'user_pass' => $password,
668 630 'first_name' => $first_name,
669 631 'last_name' => $last_name,
@@ -707,15 +669,12 @@
707 669 }
708 670
709 671 if( !is_wp_error( $user_id ) ){
710 672 foreach ($row as $key => $value){
711 - //Only write columns on the allowlist; denylist always wins.
712 - if ( ! $this->is_importable_column( $key ) ) {
713 - continue;
673 + if(!in_array($key, $exclude)){
674 + $value = maybe_unserialize($value);
675 + uwp_update_usermeta($user_id, $key, $value);
714 676 }
715 - //Never deserialize CSV input. Cast to safe scalar string.
716 - $value = $this->sanitize_import_value( $key, $value );
717 - uwp_update_usermeta($user_id, $key, $value);
718 677 }
719 678 } else {
720 679 $return['msg'] = sprintf(__('Row - %s Error: %s','userswp'), $this->imp_step, $user_id->get_error_message());
721 680 continue;
@@ -840,101 +799,8 @@
840 799
841 800 public function allowed_upload_mimes($mimes = array()) {
842 801 $mimes['csv'] = "text/csv";
843 802 return $mimes;
844 - }
845 -
846 - /**
847 - * Sanitize a single CSV import value.
848 - *
849 - * CSV data is always a plain string. There is no legitimate reason for it
850 - * to contain serialized PHP. We detect the serialization type-prefix
851 - * signatures, reject them with a log entry, and return an empty string.
852 - * All other values are cast to string and sanitized with sanitize_text_field().
853 - *
854 - * @param string $key The CSV column / meta key name.
855 - * @param mixed $value The raw value from the CSV row.
856 - * @return string A safe scalar string ready for DB insertion.
857 - */
858 - private function sanitize_import_value( $key, $value ) {
859 - // Reject serialized payloads
860 - if ( is_string( $value ) && preg_match( '/^[aAbBdDiIoOsScCnN][:;]/', ltrim( $value ) ) ) {
861 - if ( function_exists( 'uwp_log' ) ) {
862 - uwp_log( sprintf( 'Import security: serialized payload in column "%s" — discarded.', esc_attr( $key ) ) );
863 - }
864 - return '';
865 - }
866 -
867 - // File path columns: validate as a URL pointing inside the uploads directory only
868 - if ( in_array( $key, array( 'avatar_thumb', 'banner_thumb' ), true ) ) {
869 - return $this->sanitize_import_thumb( $value );
870 - }
871 -
872 - return sanitize_text_field( (string) $value );
873 - }
874 -
875 - /**
876 - * Sanitizes a thumbnail path value from CSV import.
877 - *
878 - * Validates that the given path is a real, existing file located within
879 - * the WordPress uploads directory, preventing path traversal attacks and
880 - * references to arbitrary files outside the uploads directory.
881 - *
882 - * @param string $value Raw thumbnail path value from the CSV row.
883 - * @return string Resolved absolute path if valid, empty string otherwise.
884 - */
885 - private function sanitize_import_thumb( $value ) {
886 - $value = trim( (string) $value );
887 -
888 - if ( empty( $value ) ) {
889 - return '';
890 - }
891 -
892 - // Resolve any ../ traversal attempts before comparison
893 - $real = realpath( $value );
894 -
895 - if ( $real === false ) {
896 - return ''; // Path doesn't exist on disk — reject
897 - }
898 -
899 - // Must stay within the uploads directory
900 - $uploads = wp_upload_dir();
901 - $base_dir = trailingslashit( realpath( $uploads['basedir'] ) );
902 -
903 - if ( strpos( $real . DIRECTORY_SEPARATOR, $base_dir ) !== 0 ) {
904 - if ( function_exists( 'uwp_log' ) ) {
905 - uwp_log( sprintf(
906 - 'Import security: thumb path "%s" is outside uploads directory — discarded.',
907 - esc_attr( $value )
908 - ) );
909 - }
910 - return '';
911 - }
912 -
913 - // Must be an allowed image extension
914 - $ext = strtolower( pathinfo( $real, PATHINFO_EXTENSION ) );
915 - if ( ! in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ), true ) ) {
916 - return '';
917 - }
918 -
919 - return $real; // Return the resolved, canonical path
920 - }
921 -
922 - /**
923 - * Return true only when the given column name is permitted for CSV import.
924 - *
925 - * @param string $column The CSV column / meta key name.
926 - * @return bool
927 - */
928 - private function is_importable_column( $column ) {
929 - $column = strtolower( trim( (string) $column ) );
930 -
931 - // Denylist is checked first — it unconditionally blocks.
932 - if ( in_array( $column, self::$import_meta_denylist, true ) ) {
933 - return false;
934 - }
935 -
936 - return in_array( $column, self::$import_meta_allowlist, true );
937 803 }
938 804
939 805 /**
940 806 * Escape a string to be used in a CSV export.