CVE-2022-22111: Missing Authorization (CWE-862)
value: In DayByDay CRM, version 2.2.0 is vulnerable to missing authorization. Any application user in the application who has update user permission enabled is able to change the password of other users, including the administrator’s. This allows the attacker to gain access to the highest privileged user in the application.
CWE description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
CVSS v3 base score: 8.8
Published: 2022-01-05T15:15Z
Repository: https://github.com/Bottelet/DaybydayCRM
Language: PHP
Files changed: app/Http/Controllers/UsersController.php, app/Models/User.php, resources/views/users/form.blade.php
Commit date: 2021-06-25 21:31:54 +0200
Commit message: ensure only admin and owner can change role, and password, unless it is yourself

Diff: diff --git a/app/Http/Controllers/UsersController.php b/app/Http/Controllers/UsersController.php
index 3640868e..d2893b36 100644
--- a/app/Http/Controllers/UsersController.php
+++ b/app/Http/Controllers/UsersController.php
@@ -38,10 +38,14 @@ class UsersController extends Controller
         return view('users.index')->withUsers(User::all());
     }
 
     public function calendarUsers()
     {
+        if (!auth()->user()->can('absence-view')) {
+            session()->flash('flash_message_warning', __('You do not have permission to view this page'));
+            return redirect()->back();
+        }
         return User::with(['department', 'absences' =>  function ($q) {
             return $q->whereBetween('start_at', [today()->subWeeks(2)->startOfDay(), today()->addWeeks(4)->endOfDay()])
                       ->orWhereBetween('end_at', [today()->subWeeks(2)->startOfDay(), today()->addWeeks(4)->endOfDay()]);
         }
         ])->get();
@@ -242,10 +246,16 @@ class UsersController extends Controller
     {
         $user = $this->findByExternalId($external_id);
         $password = bcrypt($request->password);
         $role = $request->roles;
         $department = $request->departments;
+
+        if( !auth()->user()->canChangePasswordOn($user) ) {
+            unset($request['password']);
+        }
+
+
         if ($request->hasFile('image_path')) {
             $companyname = Setting::first()->external_id;
             $file =  $request->file('image_path');
 
             $filename = str_random(8) . '_' . $file->getClientOriginalName() ;
@@ -271,11 +281,13 @@ class UsersController extends Controller
         $user->fill($input)->save();
         $role = $user->roles->first();
         if ($role && $role->name == Role::OWNER_ROLE && $owners->count() <= 1) {
             Session()->flash('flash_message_warning', __('Not able to change owner role, please choose a new owner first'));
         } else {
-            $user->roles()->sync([$request->roles]);
+            if(auth()->user()->canChangeRole() ) {
+                $user->roles()->sync([$request->roles]);
+            }
         }
         $user->department()->sync([$department]);
 
         Session()->flash('flash_message', __('User successfully updated'));
         return redirect()->back();
diff --git a/app/Models/User.php b/app/Models/User.php
index 2350ecb1..bdcf3dd1 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -96,10 +96,23 @@ class User extends Authenticatable
     public function tokens()
     {
         return $this->hasMany(Token::class, 'user_id', 'id');
     }
 
+    public function canChangePasswordOn(User $user)
+    {
+        if($this->id === $user->id || ( $this->roles->first()->name == Role::OWNER_ROLE || $this->roles->first()->name == Role::ADMIN_ROLE)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public function canChangeRole()
+    {
+        return $this->roles->first()->name == Role::OWNER_ROLE || $this->roles->first()->name == Role::ADMIN_ROLE;
+    }
 
 
     public function isOnline()
     {
         return Cache::has('user-is-online-' . $this->id);
diff --git a/resources/views/users/form.blade.php b/resources/views/users/form.blade.php
index c0477d22..27d92a18 100644
--- a/resources/views/users/form.blade.php
+++ b/resources/views/users/form.blade.php
@@ -59,39 +59,43 @@
     </div>
 </div>
 <div class="col-sm-12">
     <hr>
 </div>
-
+@if(isset($user) && auth()->user()->canChangePasswordOn($user))
 <div class="col-sm-3">
     <label for="name" class="base-input-label">@lang('Security')</label>
 </div>
+
 <div class="col-sm-9">
     <div class="form-group col-sm-8">
         <label for="password" class="control-label thin-weight">@lang('Password')</label>
         <input type="password" name="password" class="form-control" value="">
     </div>
     <div class="form-group col-sm-8">
         <label for="password_confirmation" class="control-label thin-weight">@lang('Confirm password')</label>
         <input type="password" name="password_confirmation" class="form-control" value="">
     </div>
 </div>
+@endif
 <div class="col-sm-12">
     <hr>
 </div>
 <div class="col-sm-3">
     <label for="name" class="base-input-label">@lang('Access')</label>
 </div>
 <div class="col-sm-9">
+@if(isset($user) && auth()->user()->canChangeRole())
     <div class="form-group col-sm-8">
         <label for="roles" class="control-label thin-weight">@lang('Assign role')</label>
         <select name="roles" id="" class="form-control">
         @foreach($roles as $key => $role)
                 <option {{ isset($user) && optional($user->userRole)->role_id === $key ? "selected" : "" }} value="{{$key}}">{{$role}}</option>
         @endforeach
         </select>
     </div>
+@endif
     <div class="form-group col-sm-8">
         <label for="departments" class="control-label thin-weight">@lang('Assign department')</label>
         <select name="departments" id="" class="form-control">
             @foreach($departments as $key => $department)
                 <option {{ isset($user) && $user->department->first()->id === $key ? "selected" : "" }} value="{{$key}}">{{$department}}</option>

Vulnerable code:             $user->roles()->sync([$request->roles]);

Fixed code:         if (!auth()->user()->can('absence-view')) {
            session()->flash('flash_message_warning', __('You do not have permission to view this page'));
            return redirect()->back();
        }

        if( !auth()->user()->canChangePasswordOn($user) ) {
            unset($request['password']);
        }


            if(auth()->user()->canChangeRole() ) {
                $user->roles()->sync([$request->roles]);
            }
    public function canChangePasswordOn(User $user)
    {
        if($this->id === $user->id || ( $this->roles->first()->name == Role::OWNER_ROLE || $this->roles->first()->name == Role::ADMIN_ROLE)) {
            return true;
        }

        return false;
    }

    public function canChangeRole()
    {
        return $this->roles->first()->name == Role::OWNER_ROLE || $this->roles->first()->name == Role::ADMIN_ROLE;
    }
@if(isset($user) && auth()->user()->canChangePasswordOn($user))

@endif
@if(isset($user) && auth()->user()->canChangeRole())
@endif