CVE-2023-33970: Missing Authorization (CWE-862)
value: Kanboard is open source project management software that focuses on the Kanban methodology. A vulnerability related to a `missing access control` was found, which allows a User with the lowest privileges to leak all the tasks and projects titles within the software, even if they are not invited or it's a personal project. This could also lead to private/critical information being leaked if such information is in the title. This issue has been addressed in version 1.2.30. Users are advised to upgrade. There are no known workarounds for this vulnerability.

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: 6.5
Published: 2023-06-05T20:15Z
Repository: https://github.com/kanboard/kanboard
Language: PHP
Files changed: app/Api/Procedure/TaskLinkProcedure.php, app/Controller/TaskInternalLinkController.php
Commit date: 2023-06-02 20:32:42 -0700
Commit message: Add missing permission check when creating/updating internal links

Diff: diff --git a/app/Api/Procedure/TaskLinkProcedure.php b/app/Api/Procedure/TaskLinkProcedure.php
index 375266fb3a..e794f2bbdd 100644
--- a/app/Api/Procedure/TaskLinkProcedure.php
+++ b/app/Api/Procedure/TaskLinkProcedure.php
@@ -49,10 +49,19 @@ class TaskLinkProcedure extends BaseProcedure
      * @return integer                       Task link id
      */
     public function createTaskLink($task_id, $opposite_task_id, $link_id)
     {
         TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskLink', $task_id);
+
+        if ($this->userSession->isLogged()) {
+            $opposite_task = $this->taskFinderModel->getById($opposite_task_id);
+
+            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
+                return false;
+            }
+        }
+
         return $this->taskLinkModel->create($task_id, $opposite_task_id, $link_id);
     }
 
     /**
      * Update a task link
@@ -65,10 +74,19 @@ class TaskLinkProcedure extends BaseProcedure
      * @return boolean
      */
     public function updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id)
     {
         TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTaskLink', $task_id);
+
+        if ($this->userSession->isLogged()) {
+            $opposite_task = $this->taskFinderModel->getById($opposite_task_id);
+
+            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
+                return false;
+            }
+        }
+
         return $this->taskLinkModel->update($task_link_id, $task_id, $opposite_task_id, $link_id);
     }
 
     /**
      * Remove a link between two tasks
diff --git a/app/Controller/TaskInternalLinkController.php b/app/Controller/TaskInternalLinkController.php
index 7ab01374a6..5f80a28bd7 100644
--- a/app/Controller/TaskInternalLinkController.php
+++ b/app/Controller/TaskInternalLinkController.php
@@ -1,9 +1,10 @@
 <?php
 
 namespace Kanboard\Controller;
 
+use Kanboard\Core\Controller\AccessForbiddenException;
 use Kanboard\Core\Controller\PageNotFoundException;
 
 /**
  * TaskInternalLink Controller
  *
@@ -51,10 +52,16 @@ class TaskInternalLinkController extends BaseController
         $values['task_id'] = $task['id'];
 
         list($valid, $errors) = $this->taskLinkValidator->validateCreation($values);
 
         if ($valid) {
+            $opposite_task = $this->taskFinderModel->getById($values['opposite_task_id']);
+
+            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
+                throw new AccessForbiddenException();
+            }
+
             if ($this->taskLinkModel->create($values['task_id'], $values['opposite_task_id'], $values['link_id']) !== false) {
                 $this->flash->success(t('Link added successfully.'));
 
                 if (isset($values['another_tasklink']) && $values['another_tasklink'] == 1) {
                     return $this->create(array(
@@ -119,10 +126,16 @@ class TaskInternalLinkController extends BaseController
         $values['id'] = $task_link['id'];
 
         list($valid, $errors) = $this->taskLinkValidator->validateModification($values);
 
         if ($valid) {
+            $opposite_task = $this->taskFinderModel->getById($values['opposite_task_id']);
+
+            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
+                throw new AccessForbiddenException();
+            }
+
             if ($this->taskLinkModel->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
                 $this->flash->success(t('Link updated successfully.'));
                 return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])).'#links');
             }
 

Fixed code: 
        if ($this->userSession->isLogged()) {
            $opposite_task = $this->taskFinderModel->getById($opposite_task_id);

            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
                return false;
            }
        }


        if ($this->userSession->isLogged()) {
            $opposite_task = $this->taskFinderModel->getById($opposite_task_id);

            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
                return false;
            }
        }

use Kanboard\Core\Controller\AccessForbiddenException;
            $opposite_task = $this->taskFinderModel->getById($values['opposite_task_id']);

            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
                throw new AccessForbiddenException();
            }

            $opposite_task = $this->taskFinderModel->getById($values['opposite_task_id']);

            if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) {
                throw new AccessForbiddenException();
            }