--- a/latepoint/latepoint.php
+++ b/latepoint/latepoint.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: LatePoint
* Description: Appointment Scheduling Software for WordPress
- * Version: 5.2.6
+ * Version: 5.2.7
* Author: LatePoint
* Author URI: https://latepoint.com
* Plugin URI: https://latepoint.com
@@ -29,7 +29,7 @@
* LatePoint version.
*
*/
- public $version = '5.2.6';
+ public $version = '5.2.7';
public $db_version = '2.3.0';
--- a/latepoint/lib/controllers/customers_controller.php
+++ b/latepoint/lib/controllers/customers_controller.php
@@ -149,7 +149,8 @@
public function create() {
$this->check_nonce( 'new_customer' );
$customer = new OsCustomerModel();
- $customer->set_data( $this->params['customer'] );
+ // Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
+ $customer->set_data( $this->params['customer'], LATEPOINT_PARAMS_SCOPE_PUBLIC );
if ( $customer->save() ) {
// translators: %s is the html of a customer edit link
$response_html = sprintf( __( 'Customer Created ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
@@ -178,7 +179,8 @@
$status = LATEPOINT_STATUS_ERROR;
} else {
$old_customer_data = $customer->get_data_vars();
- $customer->set_data( $this->params['customer'] );
+ // Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
+ $customer->set_data( $this->params['customer'], LATEPOINT_PARAMS_SCOPE_PUBLIC );
if ( $customer->save() ) {
// translators: %s is the html of a customer edit link
$response_html = sprintf( __( 'Customer Updated ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
--- a/latepoint/lib/controllers/orders_controller.php
+++ b/latepoint/lib/controllers/orders_controller.php
@@ -130,7 +130,9 @@
$customer = new OsCustomerModel();
$is_new_customer = true;
}
- $customer->set_data( $customer_params );
+ // Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
+ // Use 'public' role to restrict which fields can be set via user input.
+ $customer->set_data( $customer_params, LATEPOINT_PARAMS_SCOPE_PUBLIC );
if ( $customer->save() ) {
if ( $is_new_customer ) {
do_action( 'latepoint_customer_created', $customer );
--- a/latepoint/lib/helpers/settings_helper.php
+++ b/latepoint/lib/helpers/settings_helper.php
@@ -181,27 +181,82 @@
throw new Exception( __( 'Invalid JSON file format', 'latepoint' ) );
}
+ // Get whitelist of allowed LatePoint tables.
+ $allowed_tables = OsDatabaseHelper::get_all_latepoint_tables();
+
+ // Validate all tables before processing to ensure security.
+ foreach ( $data as $table => $table_data ) {
+ // Security check: Ensure table is in whitelist.
+ if ( ! in_array( $table, $allowed_tables, true ) ) {
+ throw new Exception( sprintf( __( 'Security: Table "%s" is not allowed for import', 'latepoint' ), esc_html( $table ) ) );
+ }
+
+ // Security check: Validate required fields exist.
+ if ( ! isset( $table_data['create'] ) || ! isset( $table_data['data'] ) ) {
+ throw new Exception( sprintf( __( 'Invalid data structure for table "%s"', 'latepoint' ), esc_html( $table ) ) );
+ }
+
+ // Security check: Validate CREATE statement only creates the expected table.
+ $create_statement = $table_data['create'];
+ if ( ! preg_match( '/^s*CREATEs+TABLEs+/i', $create_statement ) ) {
+ throw new Exception( sprintf( __( 'Security: Invalid CREATE statement for table "%s"', 'latepoint' ), esc_html( $table ) ) );
+ }
+
+ // Extract table name from CREATE statement and validate it matches.
+ if ( ! preg_match( '/CREATEs+TABLEs+(?:IFs+NOTs+EXISTSs+)?[`'"]?(' . preg_quote( $table, '/' ) . ')[`'"]?s+/i', $create_statement ) ) {
+ throw new Exception( sprintf( __( 'Security: CREATE statement table name mismatch for "%s"', 'latepoint' ), esc_html( $table ) ) );
+ }
+
+ // Security check: Ensure no dangerous SQL keywords in CREATE statement.
+ $dangerous_keywords = array( 'EXEC', 'EXECUTE', 'CALL', 'LOAD_FILE', 'INTO OUTFILE', 'INTO DUMPFILE' );
+ foreach ( $dangerous_keywords as $keyword ) {
+ if ( stripos( $create_statement, $keyword ) !== false ) {
+ throw new Exception( sprintf( __( 'Security: Dangerous SQL keyword "%s" detected in CREATE statement', 'latepoint' ), esc_html( $keyword ) ) );
+ }
+ }
+ }
+
+ // Process each table after validation.
foreach ( $data as $table => $table_data ) {
// Drop table if exists
- $wpdb->query( "DROP TABLE IF EXISTS {$table}" );
+ $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $table ) );
// Create table
- $wpdb->query( $table_data['create'] );
+ $result = $wpdb->query( $table_data['create'] );
+ if ( $result === false ) {
+ throw new Exception( sprintf( __( 'Error creating table "%s": %s', 'latepoint' ), esc_html( $table ), $wpdb->last_error ) );
+ }
// Insert data
- foreach ( $table_data['data'] as $row ) {
- $wpdb->insert( $table, $row );
+ if ( is_array( $table_data['data'] ) ) {
+ foreach ( $table_data['data'] as $row ) {
+ if ( is_array( $row ) ) {
+ $insert_result = $wpdb->insert( $table, $row );
+ if ( $insert_result === false ) {
+ // Log error but continue with other rows.
+ error_log( sprintf( 'LatePoint Import: Error inserting data into %s: %s', $table, $wpdb->last_error ) );
+ }
+ }
+ }
}
// Find auto-increment columns and their max values
- $columns = $wpdb->get_results( "SHOW COLUMNS FROM {$table} WHERE Extra = 'auto_increment'" );
- foreach ( $columns as $column ) {
- // Get the maximum value for this column
- $max_id = $wpdb->get_var( "SELECT MAX({$column->Field}) FROM {$table}" );
-
- // Set the auto_increment value to max + 1
- if ( $max_id ) {
- $wpdb->query( "ALTER TABLE {$table} AUTO_INCREMENT = " . ( $max_id + 1 ) );
+ $columns = $wpdb->get_results( $wpdb->prepare( "SHOW COLUMNS FROM %i WHERE Extra = 'auto_increment'", $table ) );
+ if ( is_array( $columns ) ) {
+ foreach ( $columns as $column ) {
+ if ( isset( $column->Field ) ) {
+ // Sanitize column name to prevent SQL injection.
+ $column_name = preg_replace( '/[^a-zA-Z0-9_]/', '', $column->Field );
+
+ // Get the maximum value for this column.
+ $max_id = $wpdb->get_var( $wpdb->prepare( "SELECT MAX(%i) FROM %i", $column_name, $table ) );
+
+ // Set the auto_increment value to max + 1.
+ if ( $max_id ) {
+ $next_id = intval( $max_id ) + 1;
+ $wpdb->query( $wpdb->prepare( "ALTER TABLE %i AUTO_INCREMENT = %d", $table, $next_id ) );
+ }
+ }
}
}
}
--- a/latepoint/lib/helpers/steps_helper.php
+++ b/latepoint/lib/helpers/steps_helper.php
@@ -467,6 +467,24 @@
public static function load_step( $step_code, $format = 'json', $params = [] ) {
self::$params = $params;
+ // Security: If loading existing booking by ID, verify ownership.
+ if ( ! empty( $params['booking']['id'] ) ) {
+ $booking_to_check = new OsBookingModel( $params['booking']['id'] );
+ if ( ! $booking_to_check->is_new_record() ) {
+ $current_customer_id = OsAuthHelper::get_logged_in_customer_id();
+ if ( ! $current_customer_id || $booking_to_check->customer_id != $current_customer_id ) {
+ // Unauthorized access - return error.
+ wp_send_json(
+ array(
+ 'status' => LATEPOINT_STATUS_ERROR,
+ 'message' => __( 'Not Allowed', 'latepoint' ),
+ )
+ );
+ return;
+ }
+ }
+ }
+
$step_code = self::check_step_code_access( $step_code );
if ( self::get_customer_object_id() && OsSettingsHelper::get_settings_value( 'max_future_bookings_per_customer' ) ) {
$customer = self::get_customer_object();
@@ -1744,13 +1762,13 @@
self::$vars_for_view['customer_verification_info'] = [];
}
- if(self::$params['auth']['action'] == 'register') {
+ if(isset(self::$params['auth']['action']) && self::$params['auth']['action'] == 'register') {
// set customer objects from params of the submitted form
self::$customer_object->set_data( self::customer_params() );
}
// logout - clear the customer
- if(self::$params['auth']['action'] == 'logout'){
+ if(isset(self::$params['auth']['action']) && self::$params['auth']['action'] == 'logout'){
self::$customer_object = new OsCustomerModel();
}