Kanboard - Spraying Malicious Tasks Across all Projects

Kanboard is a widely used Kanban project management software with over 10 Million downloads in their Docker Hub and today I'll walk you through how I found 3 vulnerabilities that let a user with the lowest privileges to leak private tasks and projects titles and spray malicious tasks containing an XSS payload across all projects.


  • CVE-2023-33968- Missing Access Control allows User to move and duplicate tasks to any project
  • CVE-2023-33968- Stored XSS in the Task External Link Functionality
  • CVE-2023-33968- Missing Access Control in internal task links feature

Note:
  • - This vulnerabilities are present in Kanboard versions <= 1.2.29
  • - The exploits showed in this post can be found here.

A Quick Look at Its Features and User Roles


Users Roles

In Kanboard, three roles can be assigned to a new user:


  • User- The role with the least privileges. Users can manage their tasks within the projects they've been added to
  • Manager- This role can manage team projects and assign tasks
  • Administrator- This role has the highest level of access, including the ability to create and manage all projects and users

Project Types

There are two types of projects: Team Projects and Personal Projects.


  • Team Projects can only be created by administrators and application managers.
  • Personal Projects belong to only one individual and do not involve any user management.

Tasks

In terms of tasks, they can be duplicated within the same project or moved to another project.
Tasks in Kanboard can also be linked to each other, which is useful for tracking dependencies or related work.


  • Internal links can be created between tasks within the same project or across projects.
  • External links is often used to reference resources or information outside of Kanboard, such as documentation, ticket numbers, etc... Creating an external link is similar to creating an internal link, but you will provide a URL and title for the link instead of linking to another task.

CVE-2023-33968 - Duplicate a task to any project within Kanboard


You can move or copy tasks from one project to another project you have permission to access. However, this vulnerability allows a user with basic permissions to take a task from their project and put it in any other project. Let's take a look at how the process works:

File: /app/Model/TaskProjectDuplicationModel.php, line 25


public function duplicateToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) {
           $values = $this->prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id);
           $this->checkDestinationProjectValues($values);
// ...

Our inputs ( $values ) go into the validateProjectModification() function, which then are validated.
Going inside this function we can find the following code:

File: /app/Model/TaskDuplicationModel.php, line 88


public function checkDestinationProjectValues(array &$values) {
       // Check if the assigned user is allowed for the destination project
       if ($values['owner_id'] > 0 && ! $this->projectPermissionModel->isUserAllowed($values['project_id'], $values['owner_id'])) {
           $values['owner_id'] = 0;
       }
// ...

So there's 2 issues with this check:


  • Despite the user not having access to the project where they want to move their task, the execution doesn't stop. Instead, it just sets the variable $values['owner_id'] to 0, which still allows the task to be moved to another project.
  • A user can bypass the isUserAllowed() function by setting the owner_id to the ID of the owner of the target project. This can be done either by knowing the ID already or by brute forcing.

Proof of Concept

  • Choose a task and click Move to Project.
  • Pick any project, click Save, and intercept the request.
  • Change the project_id to your target project ID.
  • The task will then be added to your target project.

Note: The video shows an admin project, which is a personal project.



CVE-2023-33969 - Stored XSS in the Task External Link


A Stored XSS vulnerability was also found in the file /app/Template/task_external_link/table.php, line 32.
We can see that $link['url'] is not properly escaped with the e() function, unlike the other variables used in the same context.


<a href="<?= $link['url'] ?>" title="<?= $this->text->e($link['url']) ?>" target="_blank">

Proof of Concept

  • First, make a task in any project you want
  • Then, generate an external link of any kind and hit save
  • Change the URL value one more time and place the following payload. The payload will simply redirect the user to example.com

    "><meta http-equiv="refresh" content="2;url=http://example.com/"/>


A not so straight forward XSS

You might wonder why I've used a redirect instead of a traditional alert box.
That's because the default Kanboard has some protections against XSS:


  • The default CSP uses Content-Security-Policy: default-src 'self'; meaning it only runs resources from the same origin.
  • Although we could bypass the above CSP by uploading an attachment with javascript in it, it also usesX-Content-Type-Options: nosniff, which stops the browser from interpreting content with a different MIME type than declared by the server, which means it will not return a Javascript MIME type despite the file being a Javascript file.
  • We could access the file directly via the /data/ endpoint, but the default Kanboard comes with configurations for Nginx and Apache which restrict access to this endpoint.

    Nginx: location ~ /data { return 404; }
    Apache: <IfVersion >= 2.3> Require all denied </IfVersion>

Spray Malicious Tasks Across all Projects


Leveraging these two vulnerabilities, an attacker can distribute a malicious task loaded with the XSS payload into any project, whether it's personal or not. The attacker then simply waits for any user of the software to view or preview this task.



CVE-2023-33970 - Leak every task and project title within Kanboard


We can link tasks between projects. But what if the task you want to connect to is in a project you don't have access to?
Let's check the code that creates these internal links:

File: /app/Model/TaskLinkModel.php, line 166


public function create($task_id, $opposite_task_id, $link_id) {
       $this->db->startTransaction();
       
       $opposite_link_id = $this->linkModel->getOppositeLinkId($link_id);
       $task_link_id1 = $this->createTaskLink($task_id, $opposite_task_id, $link_id);
       $task_link_id2 = $this->createTaskLink($opposite_task_id, $task_id, $opposite_link_id);

       if ($task_link_id1 === false || $task_link_id2 === false) {
           $this->db->cancelTransaction();
           return false;
       }

       $this->db->closeTransaction();
       $this->fireEvents(array($task_link_id1, $task_link_id2), self::EVENT_CREATE_UPDATE);

       return $task_link_id1;
   }

In our function we have the following variables:


  • Variable $task_id - Our original task ID
  • Variable $opposite_task_id - The ID of the task we want to link with
  • Variable $link_id - Link ID in the original task

This function initiates a database transaction, then tries to create two task links:


  • One from the initial task to the other ( $task_link_id1 ).
  • One from the other task back to the initial task ( $task_link_id2 ).

These task links are created with the createTaskLink() helper function, which simply writes these links into the database.


protected function createTaskLink($task_id, $opposite_task_id, $link_id) {
       return $this->db->table(self::TABLE)->persist(array(
           'task_id'          => $task_id,
           'opposite_task_id' => $opposite_task_id,
           'link_id'          => $link_id,
       ));
   }

After the links are created, the function checks if both were created successfully. If they weren't, it cancels the database transaction and the function ends. If both links were created successfully, the database transaction is finalized and the first task link ID is returned. Knowing this, we can see that there's no permission check before creating these task links. That means that a user with basic privileges, who may not have access to certain tasks, can create links with those tasks and thereby reveal their titles.


Proof of Concept

  • Go to any task you own, click on the feature Add Internal Link and click on Save.
  • Intercept the request and change the opposite_task_id to any task id you want.


  • Go back to your task, and as you can see you can leak the task and project title of any projects within the software.



This can also be automated to brute-force every task within the software, and that's what I did by making an over-engineered exploit



Timeline


DateEvent
28/05/2023Report Submitted
29/05/2023Report gets triggered and vendor is contacted
02/06/2023Vulnerability is patched and Kanboard version 1.2.30 is published

END


Well, hopefully, you enjoyed the blog post, also thanks to the Kanboard software owner for quickly triggering and fixing the vulnerabilities. Thank you for reading, best regards, Castilho.