| @@ -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§ion=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§ion=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§ion=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§ion=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 ); |
| @@ -203,9 +166,9 @@ | ||
| 203 | 166 | $response['msg'] = ''; |
| 204 | 167 | |
| 205 | 168 | if ( $done >= 100 ) { |
| 206 | 169 | $this->step = 'done'; |
| 207 | - $new_filename = 'uwp-users-export-' . date( 'y-m-d-H-i' ) . '-' . wp_generate_password(12, false ) . '.csv'; | |
| 170 | + $new_filename = 'uwp-users-export-' . date( 'y-m-d-H-i' ) . '.csv'; | |
| 208 | 171 | $new_file = $this->export_dir . $new_filename; |
| 209 | 172 | |
| 210 | 173 | if ( file_exists( $this->file ) ) { |
| 211 | 174 | $this->wp_filesystem->move( $this->file, $new_file, true ); |
| @@ -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. |