CVE-2023-33968: Missing Authorization (CWE-862)
value: Kanboard is open source project management software that focuses on the Kanban methodology. Versions prior to 1.2.30 are subject to a missing access control vulnerability that allows a user with low privileges to create or transfer tasks to any project within the software, even if they have not been invited or the project is personal. The vulnerable features are `Duplicate to project` and `Move to project`, which both utilize the `checkDestinationProjectValues()` function to check his values. 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: 5.4
Published: 2023-06-05T20:15Z
Repository: https://github.com/kanboard/kanboard
Language: PHP
Files changed: app/Controller/TaskDuplicationController.php
Commit date: 2023-06-02 20:30:43 -0700
Commit message: Add missing project permission check for Move/Duplicate task to another project

Diff: diff --git a/app/Controller/TaskDuplicationController.php b/app/Controller/TaskDuplicationController.php
index 6ebb6d592c..a700d00036 100644
--- a/app/Controller/TaskDuplicationController.php
+++ b/app/Controller/TaskDuplicationController.php
@@ -1,9 +1,11 @@
 <?php
 
 namespace Kanboard\Controller;
 
+use Kanboard\Core\Controller\AccessForbiddenException;
+
 /**
  * Task Duplication controller
  *
  * @package  Kanboard\Controller
  * @author   Frederic Guillot
@@ -48,18 +50,24 @@ class TaskDuplicationController extends BaseController
 
         if ($this->request->isPost()) {
             $values = $this->request->getValues();
             list($valid, ) = $this->taskValidator->validateProjectModification($values);
 
-            if ($valid && $this->taskProjectMoveModel->moveToProject($task['id'],
+            if ($valid) {
+                if (! $this->projectPermissionModel->isUserAllowed($values['project_id'], $this->userSession->getId())) {
+                    throw new AccessForbiddenException();
+                }
+
+                if ($this->taskProjectMoveModel->moveToProject($task['id'],
                                                                 $values['project_id'],
                                                                 $values['swimlane_id'],
                                                                 $values['column_id'],
                                                                 $values['category_id'],
                                                                 $values['owner_id'])) {
-                $this->flash->success(t('Task updated successfully.'));
-                return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])));
+                    $this->flash->success(t('Task updated successfully.'));
+                    return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])));
+                }
             }
 
             $this->flash->failure(t('Unable to update your task.'));
         }
 
@@ -78,13 +86,21 @@ class TaskDuplicationController extends BaseController
         if ($this->request->isPost()) {
             $values = $this->request->getValues();
             list($valid, ) = $this->taskValidator->validateProjectModification($values);
 
             if ($valid) {
+                if (! $this->projectPermissionModel->isUserAllowed($values['project_id'], $this->userSession->getId())) {
+                    throw new AccessForbiddenException();
+                }
+
                 $task_id = $this->taskProjectDuplicationModel->duplicateToProject(
-                    $task['id'], $values['project_id'], $values['swimlane_id'],
-                    $values['column_id'], $values['category_id'], $values['owner_id']
+                    $task['id'],
+                    $values['project_id'],
+                    $values['swimlane_id'],
+                    $values['column_id'],
+                    $values['category_id'],
+                    $values['owner_id']
                 );
 
                 if ($task_id > 0) {
                     $this->flash->success(t('Task created successfully.'));
                     return $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $task['project_id'])), true);

Vulnerable code:             if ($valid && $this->taskProjectMoveModel->moveToProject($task['id'],
                $this->flash->success(t('Task updated successfully.'));
                return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])));
                    $task['id'], $values['project_id'], $values['swimlane_id'],
                    $values['column_id'], $values['category_id'], $values['owner_id']
Fixed code: use Kanboard\Core\Controller\AccessForbiddenException;

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

                if ($this->taskProjectMoveModel->moveToProject($task['id'],
                    $this->flash->success(t('Task updated successfully.'));
                    return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])));
                }
                if (! $this->projectPermissionModel->isUserAllowed($values['project_id'], $this->userSession->getId())) {
                    throw new AccessForbiddenException();
                }

                    $task['id'],
                    $values['project_id'],
                    $values['swimlane_id'],
                    $values['column_id'],
                    $values['category_id'],
                    $values['owner_id']