CVE-2023-3700: Authorization Bypass Through User-Controlled Key (CWE-639)
value: Authorization Bypass Through User-Controlled Key in GitHub repository alextselegidis/easyappointments prior to 1.5.0.


CWE description: The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.
CVSS v3 base score: 4.3
Published: 2023-07-17T07:15Z
Repository: https://github.com/alextselegidis/easyappointments
Language: PHP
Files changed: application/controllers/Calendar.php
Commit date: 2023-07-17 08:14:15 +0200
Commit message: Only allow authorized users to manage appointments from the calendar page (#1387)

Diff: diff --git a/application/controllers/Calendar.php b/application/controllers/Calendar.php
index df14b57a6d..af247f0d10 100644
--- a/application/controllers/Calendar.php
+++ b/application/controllers/Calendar.php
@@ -179,13 +179,16 @@ class Calendar extends EA_Controller {
      */
     public function save_appointment()
     {
         try
         {
-            // Save customer changes to the database.
             $customer_data = request('customer_data');
+            $appointment_data = request('appointment_data');
+
+            $this->check_event_permissions($appointment_data['id_users_provider']);
 
+            // Save customer changes to the database.
             if ($customer_data)
             {
                 $customer = $customer_data;
 
                 $required_permissions = ! empty($customer['id'])
@@ -214,12 +217,10 @@ class Calendar extends EA_Controller {
 
                 $customer['id'] = $this->customers_model->save($customer);
             }
 
             // Save appointment changes to the database.
-            $appointment_data = request('appointment_data');
-
             $manage_mode = ! empty($appointment_data['id']);
 
             if ($appointment_data)
             {
                 $appointment = $appointment_data;
@@ -321,10 +322,13 @@ class Calendar extends EA_Controller {
                 throw new InvalidArgumentException('No appointment id provided.');
             }
 
             // Store appointment data for later use in this method.
             $appointment = $this->appointments_model->find($appointment_id);
+
+            $this->check_event_permissions($appointment['id_users_provider']);
+
             $provider = $this->providers_model->find($appointment['id_users_provider'], TRUE);
             $customer = $this->customers_model->find($appointment['id_users_customer'], TRUE);
             $service = $this->services_model->find($appointment['id_services'], TRUE);
 
             $settings = [
@@ -371,11 +375,15 @@ class Calendar extends EA_Controller {
             if ( ! $required_permissions)
             {
                 throw new RuntimeException('You do not have the required permissions for this task.');
             }
 
-            $provider = $this->providers_model->find($unavailability['id_users_provider']);
+            $provider_id = $unavailability['id_users_provider'];
+
+            $this->check_event_permissions($provider_id);
+
+            $provider = $this->providers_model->find($provider_id);
 
             $unavailability_id = $this->unavailabilities_model->save($unavailability);
 
             $unavailability = $this->unavailabilities_model->find($unavailability_id);
 
@@ -407,10 +415,12 @@ class Calendar extends EA_Controller {
             }
 
             $unavailability_id = request('unavailability_id');
 
             $unavailability = $this->appointments_model->find($unavailability_id);
+            
+            $this->check_event_permissions($unavailability['id_users_provider']);
 
             $provider = $this->providers_model->find($unavailability['id_users_provider']);
 
             $this->unavailabilities_model->delete($unavailability_id);
 
@@ -740,6 +750,22 @@ class Calendar extends EA_Controller {
         catch (Throwable $e)
         {
             json_exception($e);
         }
     }
+
+    private function check_event_permissions($provider_id)
+    {
+        $user_id = (int)session('user_id');
+        $role_slug = session('role_slug');
+
+        if ($role_slug === DB_SLUG_SECRETARY && ! $this->secretaries_model->is_provider_supported($user_id, $provider_id))
+        {
+            abort(403);
+        }
+
+        if ($role_slug === DB_SLUG_PROVIDER && $user_id !== $provider_id)
+        {
+            abort(403);
+        }
+    }
 }

Vulnerable code:             // Save customer changes to the database.
            $appointment_data = request('appointment_data');

            $provider = $this->providers_model->find($unavailability['id_users_provider']);
Fixed code:             $appointment_data = request('appointment_data');

            $this->check_event_permissions($appointment_data['id_users_provider']);
            // Save customer changes to the database.

            $this->check_event_permissions($appointment['id_users_provider']);

            $provider_id = $unavailability['id_users_provider'];

            $this->check_event_permissions($provider_id);

            $provider = $this->providers_model->find($provider_id);
            
            $this->check_event_permissions($unavailability['id_users_provider']);

    private function check_event_permissions($provider_id)
    {
        $user_id = (int)session('user_id');
        $role_slug = session('role_slug');

        if ($role_slug === DB_SLUG_SECRETARY && ! $this->secretaries_model->is_provider_supported($user_id, $provider_id))
        {
            abort(403);
        }

        if ($role_slug === DB_SLUG_PROVIDER && $user_id !== $provider_id)
        {
            abort(403);
        }
    }