CVE-2022-25318: Incorrect Authorization (CWE-863)
value: An issue was discovered in Cerebrate through 1.4. An incorrect sharing group ACL allowed an unprivileged user to edit and modify sharing groups.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 4.3
Published: 2022-02-18T06:15Z
Repository: https://github.com/cerebrate-project/cerebrate
Language: PHP
Files changed: src/Controller/Component/CRUDComponent.php, src/Controller/SharingGroupsController.php
Commit date: 2022-02-04 00:16:24 +0100
Commit message: fix: [security] Sharing group ACL fixes

- added indirect object reference protection
- added correct ACL functionalities to delete, addOrg, removeOrg

- as reported by Dawid Czarnecki from Zigrin Security

Diff: diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php
index 1a1e7338..14369abe 100644
--- a/src/Controller/Component/CRUDComponent.php
+++ b/src/Controller/Component/CRUDComponent.php
@@ -418,14 +418,20 @@ class CRUDComponent extends Component
             $params['contain'][] = 'Tags';
             $this->setAllTags();
         }
 
         $data = $this->Table->get($id, $params);
+        if (empty($data)) {
+            throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
+        }
         $data = $this->attachMetaData($id, $data);
         if (isset($params['afterFind'])) {
             $data = $params['afterFind']($data);
         }
+        if (empty($data)) {
+            throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
+        }
         if ($this->Controller->ParamHandler->isRest()) {
             $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
         }
         $this->Controller->set('entity', $data);
     }
diff --git a/src/Controller/SharingGroupsController.php b/src/Controller/SharingGroupsController.php
index 764f0e64..aa96cb49 100644
--- a/src/Controller/SharingGroupsController.php
+++ b/src/Controller/SharingGroupsController.php
@@ -5,10 +5,11 @@ use App\Controller\AppController;
 use Cake\Utility\Inflector;
 use Cake\Utility\Hash;
 use Cake\Utility\Text;
 use \Cake\Database\Expression\QueryExpression;
 use Cake\Error\Debugger;
+use Cake\Http\Exception\NotFoundException;
 
 class SharingGroupsController extends AppController
 {
     public $filterFields = ['SharingGroups.uuid', 'SharingGroups.name', 'description', 'releasability', 'Organisations.name', 'Organisations.uuid'];
     public $quickFilterFields = ['SharingGroups.uuid', ['SharingGroups.name' => true], ['description' => true], ['releasability' => true]];
@@ -52,12 +53,29 @@ class SharingGroupsController extends AppController
         $this->set('metaGroup', 'Trust Circles');
     }
 
     public function view($id)
     {
+        $currentUser = $this->ACL->getUser();
         $this->CRUD->view($id, [
-            'contain' => ['SharingGroupOrgs', 'Organisations', 'Users' => ['fields' => ['id', 'username']]]
+            'contain' => ['SharingGroupOrgs', 'Organisations', 'Users' => ['fields' => ['id', 'username']]],
+            'afterFind' => function($data) use ($currentUser) {
+                if (empty($currentUser['role']['perm_admin'])) {
+                    $orgFround = false;
+                    if (!empty($data['sharing_group_orgs'])) {
+                        foreach ($data['sharing_group_orgs'] as $org) {
+                            if ($org['id'] === $currentUser['organisation_id']) {
+                                $orgFound = true;
+                            }
+                        }
+                    }
+                    if ($data['organisation_id'] !== $currentUser['organisation_id'] && !$orgFround) {
+                        return null;
+                    }
+                }
+                return $data;
+            }
         ]);
         $responsePayload = $this->CRUD->getResponsePayload();
         if (!empty($responsePayload)) {
             return $responsePayload;
         }
@@ -85,23 +103,36 @@ class SharingGroupsController extends AppController
         $this->render('add');
     }
 
     public function delete($id)
     {
-        $this->CRUD->delete($id);
+        $currentUser = $this->ACL->getUser();
+        if (empty($currentUser['role']['perm_admin'])) {
+            $params['conditions'] = ['organisation_id' => $currentUser['organisation_id']];
+        }
+        $this->CRUD->delete($id, $params);
         $responsePayload = $this->CRUD->getResponsePayload();
         if (!empty($responsePayload)) {
             return $responsePayload;
         }
         $this->set('metaGroup', 'Trust Circles');
     }
 
     public function addOrg($id)
     {
+        $currentUser = $this->ACL->getUser();
         $sharingGroup = $this->SharingGroups->get($id, [
             'contain' => 'SharingGroupOrgs'
         ]);
+        if (empty($currentUser['role']['perm_admin'])) {
+            if ($sharingGroup['organisation_id'] !== $currentUser['organisation_id']) {
+                $sharingGroup = null;
+            }
+        }
+        if (empty($sharingGroup)) {
+            throw new NotFoundException(__('Invalid SharingGroup.'));
+        }
         $conditions = [];
         $containedOrgIds = array_values(\Cake\Utility\Hash::extract($sharingGroup, 'sharing_group_orgs.{n}.id'));
         if (!empty($containedOrgIds)) {
             $conditions = [
                 'NOT' => [
@@ -154,13 +185,22 @@ class SharingGroupsController extends AppController
         $this->set(compact('dropdownData'));
     }
 
     public function removeOrg($id, $org_id)
     {
+        $currentUser = $this->ACL->getUser();
         $sharingGroup = $this->SharingGroups->get($id, [
             'contain' => 'SharingGroupOrgs'
         ]);
+        if (empty($currentUser['role']['perm_admin'])) {
+            if ($sharingGroup['organisation_id'] !== $currentUser['organisation_id']) {
+                $sharingGroup = null;
+            }
+        }
+        if (empty($sharingGroup)) {
+            throw new NotFoundException(__('Invalid SharingGroup.'));
+        }
         if ($this->request->is('post')) {
             $org = $this->SharingGroups->SharingGroupOrgs->get($org_id);
             $result = (bool)$this->SharingGroups->SharingGroupOrgs->unlink($sharingGroup, [$org]);
             if ($result) {
                 $message = __('Organisation(s) removed from the sharing group.');

Vulnerable code:             'contain' => ['SharingGroupOrgs', 'Organisations', 'Users' => ['fields' => ['id', 'username']]]
        $this->CRUD->delete($id);
Fixed code:         if (empty($data)) {
            throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
        }
        if (empty($data)) {
            throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
        }
use Cake\Http\Exception\NotFoundException;
        $currentUser = $this->ACL->getUser();
            'contain' => ['SharingGroupOrgs', 'Organisations', 'Users' => ['fields' => ['id', 'username']]],
            'afterFind' => function($data) use ($currentUser) {
                if (empty($currentUser['role']['perm_admin'])) {
                    $orgFround = false;
                    if (!empty($data['sharing_group_orgs'])) {
                        foreach ($data['sharing_group_orgs'] as $org) {
                            if ($org['id'] === $currentUser['organisation_id']) {
                                $orgFound = true;
                            }
                        }
                    }
                    if ($data['organisation_id'] !== $currentUser['organisation_id'] && !$orgFround) {
                        return null;
                    }
                }
                return $data;
            }
        $currentUser = $this->ACL->getUser();
        if (empty($currentUser['role']['perm_admin'])) {
            $params['conditions'] = ['organisation_id' => $currentUser['organisation_id']];
        }
        $this->CRUD->delete($id, $params);
        $currentUser = $this->ACL->getUser();
        if (empty($currentUser['role']['perm_admin'])) {
            if ($sharingGroup['organisation_id'] !== $currentUser['organisation_id']) {
                $sharingGroup = null;
            }
        }
        if (empty($sharingGroup)) {
            throw new NotFoundException(__('Invalid SharingGroup.'));
        }
        $currentUser = $this->ACL->getUser();
        if (empty($currentUser['role']['perm_admin'])) {
            if ($sharingGroup['organisation_id'] !== $currentUser['organisation_id']) {
                $sharingGroup = null;
            }
        }
        if (empty($sharingGroup)) {
            throw new NotFoundException(__('Invalid SharingGroup.'));
        }