From 2004aa7ddecfe84c91b75ad29a19579d2d5bcf0d Mon Sep 17 00:00:00 2001 From: Nikita Sinelnikov Date: Wed, 9 Nov 2022 03:17:23 +0200 Subject: [PATCH] Custom dropdown callback functions security enhancements: - avoid using different letter case for bypass the blacklist e.g. phpInfo - avoid using root namespace for bypass the blacklist e.g. \phpinfo --- includes/admin/core/class-admin-builder.php | 2 +- includes/core/class-fields.php | 33 ++++++++++++++++----- includes/core/class-form.php | 2 +- includes/core/class-validation.php | 2 +- includes/core/um-actions-profile.php | 2 +- includes/core/um-filters-fields.php | 2 +- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/includes/admin/core/class-admin-builder.php b/includes/admin/core/class-admin-builder.php index b655d341..ebfc1aa6 100644 --- a/includes/admin/core/class-admin-builder.php +++ b/includes/admin/core/class-admin-builder.php @@ -1220,7 +1220,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Builder' ) ) { $arr_options['function_exists'] = function_exists( $um_callback_func ); } - if ( in_array( $um_callback_func, UM()->fields()->dropdown_options_source_blacklist(), true ) ) { + if ( UM()->fields()->is_source_blacklisted( $um_callback_func ) ) { wp_send_json_error( __( 'This is not possible for security reasons. Don\'t use internal PHP functions.', 'ultimate-member' ) ); } diff --git a/includes/core/class-fields.php b/includes/core/class-fields.php index 970cfff6..704228fa 100644 --- a/includes/core/class-fields.php +++ b/includes/core/class-fields.php @@ -112,7 +112,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { $output = null; foreach ( $fields as $key => $data ) { - $output .= UM()->fields()->edit_field( $key, $data ); + $output .= $this->edit_field( $key, $data ); } echo $output; @@ -147,7 +147,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { if ( array_key_exists( 'custom_dropdown_options_source', $args ) ) { if ( function_exists( wp_unslash( $args['custom_dropdown_options_source'] ) ) ) { - if ( ! in_array( $args['custom_dropdown_options_source'], UM()->fields()->dropdown_options_source_blacklist(), true ) ) { + if ( ! $this->is_source_blacklisted( $args['custom_dropdown_options_source'] ) ) { $allowed_callbacks = UM()->options()->get( 'allowed_choice_callbacks' ); if ( ! empty( $allowed_callbacks ) ) { $allowed_callbacks = array_map( 'rtrim', explode( "\n", $allowed_callbacks ) ); @@ -204,7 +204,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { if ( array_key_exists( 'custom_dropdown_options_source', $args ) ) { if ( function_exists( wp_unslash( $args['custom_dropdown_options_source'] ) ) ) { - if ( ! in_array( $args['custom_dropdown_options_source'], UM()->fields()->dropdown_options_source_blacklist(), true ) ) { + if ( ! $this->is_source_blacklisted( $args['custom_dropdown_options_source'] ) ) { $allowed_callbacks = UM()->options()->get( 'allowed_choice_callbacks' ); if ( ! empty( $allowed_callbacks ) ) { $allowed_callbacks = array_map( 'rtrim', explode( "\n", $allowed_callbacks ) ); @@ -1309,6 +1309,25 @@ if ( ! class_exists( 'um\core\Fields' ) ) { return $blacklist; } + /** + * Is the dropdown source callback function blacklisted? + * + * @param string $source Function name + * + * @return bool + */ + public function is_source_blacklisted( $source ) { + // avoid using different letter case for bypass the blacklist e.g. phpInfo + // avoid using root namespace for bypass the blacklist e.g. \phpinfo + $source = trim( strtolower( $source ), '\\' ); + + if ( in_array( $source, $this->dropdown_options_source_blacklist(), true ) ) { + return true; + } + + return false; + } + /** * Gets selected option value from a callback function * @@ -1322,7 +1341,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { if ( in_array( $type, array( 'select', 'multiselect' ) ) && ! empty( $data['custom_dropdown_options_source'] ) ) { - if ( in_array( $data['custom_dropdown_options_source'], $this->dropdown_options_source_blacklist(), true ) ) { + if ( $this->is_source_blacklisted( $data['custom_dropdown_options_source'] ) ) { return $value; } @@ -1393,7 +1412,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { if ( in_array( $type, array( 'select', 'multiselect' ) ) && ! empty( $data['custom_dropdown_options_source'] ) ) { - if ( in_array( $data['custom_dropdown_options_source'], $this->dropdown_options_source_blacklist(), true ) ) { + if ( $this->is_source_blacklisted( $data['custom_dropdown_options_source'] ) ) { return $arr_options; } @@ -3062,7 +3081,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { if ( ! empty( $data['custom_dropdown_options_source'] ) && $has_parent_option && function_exists( $data['custom_dropdown_options_source'] ) && um_user( $data['parent_dropdown_relationship'] ) ) { - if ( ! in_array( $data['custom_dropdown_options_source'], $this->dropdown_options_source_blacklist(), true ) ) { + if ( ! $this->is_source_blacklisted( $data['custom_dropdown_options_source'] ) ) { $options = call_user_func( $data['custom_dropdown_options_source'], $data['parent_dropdown_relationship'] ); } @@ -3082,7 +3101,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { if ( $has_parent_option ) { if ( ! empty( $data['custom_dropdown_options_source'] ) && $has_parent_option && function_exists( $data['custom_dropdown_options_source'] ) && isset( UM()->form()->post_form[ $form_key ] ) ) { - if ( ! in_array( $data['custom_dropdown_options_source'], $this->dropdown_options_source_blacklist(), true ) ) { + if ( ! $this->is_source_blacklisted( $data['custom_dropdown_options_source'] ) ) { $options = call_user_func( $data['custom_dropdown_options_source'], $data['parent_dropdown_relationship'] ); } } diff --git a/includes/core/class-form.php b/includes/core/class-form.php index ab12b1ca..eeab460b 100644 --- a/includes/core/class-form.php +++ b/includes/core/class-form.php @@ -148,7 +148,7 @@ if ( ! class_exists( 'um\core\Form' ) ) { wp_send_json( $arr_options ); } - if ( in_array( $ajax_source_func, UM()->fields()->dropdown_options_source_blacklist(), true ) ) { + if ( UM()->fields()->is_source_blacklisted( $ajax_source_func ) ) { $arr_options['status'] = 'error'; $arr_options['message'] = __( 'This is not possible for security reasons.', 'ultimate-member' ); diff --git a/includes/core/class-validation.php b/includes/core/class-validation.php index 6ef84de6..4f98a5e7 100644 --- a/includes/core/class-validation.php +++ b/includes/core/class-validation.php @@ -95,7 +95,7 @@ if ( ! class_exists( 'um\core\Validation' ) ) { isset( $fields[ $key ]['custom_dropdown_options_source'] ) && ! empty( $fields[ $key ]['custom_dropdown_options_source'] ) && function_exists( $fields[ $key ]['custom_dropdown_options_source'] ) ) { - if ( ! in_array( $fields[ $key ]['custom_dropdown_options_source'], UM()->fields()->dropdown_options_source_blacklist(), true ) ) { + if ( ! UM()->fields()->is_source_blacklisted( $fields[ $key ]['custom_dropdown_options_source'] ) ) { $arr_options = call_user_func( $fields[ $key ]['custom_dropdown_options_source'] ); $fields[ $key ]['options'] = array_keys( $arr_options ); } diff --git a/includes/core/um-actions-profile.php b/includes/core/um-actions-profile.php index 49f01f7d..7f7e9b37 100644 --- a/includes/core/um-actions-profile.php +++ b/includes/core/um-actions-profile.php @@ -295,7 +295,7 @@ function um_user_edit_profile( $args ) { $options = array(); if ( ! empty( $array['custom_dropdown_options_source'] ) && function_exists( $array['custom_dropdown_options_source'] ) && ! $has_custom_source ) { - if ( ! in_array( $array['custom_dropdown_options_source'], UM()->fields()->dropdown_options_source_blacklist(), true ) ) { + if ( ! UM()->fields()->is_source_blacklisted( $array['custom_dropdown_options_source'] ) ) { $callback_result = call_user_func( $array['custom_dropdown_options_source'], $array['options'] ); if ( is_array( $callback_result ) ) { $options = array_keys( $callback_result ); diff --git a/includes/core/um-filters-fields.php b/includes/core/um-filters-fields.php index d431a434..29527944 100644 --- a/includes/core/um-filters-fields.php +++ b/includes/core/um-filters-fields.php @@ -694,7 +694,7 @@ add_filter( 'um_field_non_utf8_value', 'um_field_non_utf8_value' ); */ function um_select_dropdown_dynamic_callback_options( $options, $data ) { if ( ! empty( $data['custom_dropdown_options_source'] ) && function_exists( $data['custom_dropdown_options_source'] ) ) { - if ( in_array( $data['custom_dropdown_options_source'], UM()->fields()->dropdown_options_source_blacklist(), true ) ) { + if ( UM()->fields()->is_source_blacklisted( $data['custom_dropdown_options_source'] ) ) { return $options; } $options = call_user_func( $data['custom_dropdown_options_source'] );