Skip to content

Commit 4aa6d68

Browse files
authored
Merge pull request #54 from msatdt/kanboard
Added write-up for CVE-2024-51747, CVE-2024-51748 and CVE-2024-55603
2 parents 43166dc + f9b1c3e commit 4aa6d68

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
---
2+
title: Multiple vulnerabilities in Kanboard (Exploiting web applications Part II)
3+
header: Multiple vulnerabilities in Kanboard (Exploiting web applications Part II)
4+
tags: ['advisories', 'writeup']
5+
cwes: ['Improper Limitation of a Pathname to a Restricted Directory (Path Traversal) (CWE-22)']
6+
affected_product: 'Kanboard'
7+
vulnerability_release_date: '2024-11-11'
8+
---
9+
10+
This article is a continuation of a write-up series, where we discuss web application vulnerabilities found during red team operations. This time, the target was the Kanboard software. <!--more-->
11+
12+
### Project Management in Kanboard style
13+
14+
With over 8000 stars on GitHub, [Kanboard](https://github.com/kanboard/kanboard) is one of the most popular applications for organizing projects following the Kanban approach.
15+
16+
During one of our red team assessments, we discovered that our client self-hosts an instance of Kanboard.
17+
Since it is open-source, we decided to hunt for vulnerabilities by reading the source code and penetration testing in parallel.
18+
19+
So, it all started with a `git clone`. We used the `ack` tool as an in place grep replacement, which helped us find interesting code sections. Browsing through the code leaves the first impression that it is well structured and cleanly written.
20+
21+
22+
#### Initial access and juicy features
23+
24+
But first, let's switch to the customer instance again - not having said yet that the good old `admin:admin` credential set helped us out once again :)
25+
We could successfully authenticate as the administrator and thus open us various possibilities to potentially exploit application functionalities.
26+
27+
Having administrative access to the Kanboard instance also gives us lots of interesting information about the target's project details including network and system configurations. But can we abuse the server?
28+
We browse through different projects, boards, comments, item details and uploaded attachments.
29+
30+
![Download Database](/assets/images/kanboard/download-database.png)
31+
32+
One interesting feature of Kanboard is allowing administrators to download the complete SQLite database as a gzip file or upload it to update the database.
33+
So we did this: a surprise backup of our target instance.
34+
We decompress the downloaded file by executing `gzip -d db.sqlite.gz` and open it with an SQLite browser.
35+
36+
We can see the raw data of projects, comments, etc.
37+
Especially the `project_has_files` table holds our attention, as it stores relative file paths of uploaded files.
38+
So we switched to the source code repository and looked through the source code, to determine how the the filepaths are read and used within the application.
39+
40+
##### Write and delete files - but what about reading them?
41+
42+
In the web UI, we see that uploaded files can be downloaded through URLs like this: `https://example.com/project/1/file/1/download/<hash>`.
43+
So searching through the code base for `/download` points us to the underlying PHP class that is responsible for serving files: the [FileViewerController](https://github.com/kanboard/kanboard/blob/v1.2.41/app/Controller/FileViewerController.php).
44+
45+
```bash
46+
$ ack "/download"
47+
ServiceProvider/RouteProvider.php
48+
77: $container['route']->addRoute('project/:project_id/file/:file_id/download/:etag', 'FileViewerController', 'download');
49+
148: $container['route']->addRoute('task/:task_id/file/:file_id/download/:etag', 'FileViewerController', 'download');
50+
```
51+
52+
The below `download()` function of the [FileViewerController.php](https://github.com/kanboard/kanboard/blob/v1.2.41/app/Controller/FileViewerController.php#L152) looks very simple. But what exactly do `$this->getFile();` and `$this->objectStorage->output($file['path']);` do?
53+
54+
```php
55+
public function download()
56+
{
57+
try {
58+
$file = $this->getFile();
59+
$this->response->withFileDownload($file['name']);
60+
$this->response->send();
61+
$this->objectStorage->output($file['path']);
62+
} catch (ObjectStorageException $e) {
63+
$this->logger->error($e->getMessage());
64+
}
65+
}
66+
```
67+
`$this->getFile()` is a call to the super class of [BaseController](https://github.com/kanboard/kanboard/blob/v1.2.41/app/Controller/BaseController.php#L93).
68+
69+
```php
70+
protected function getFile()
71+
{
72+
$project_id = $this->request->getIntegerParam('project_id');
73+
$task_id = $this->request->getIntegerParam('task_id');
74+
$file_id = $this->request->getIntegerParam('file_id');
75+
76+
[...]
77+
}
78+
```
79+
80+
We see that this function parses the `project_id` and `file_id` parameter values that we already saw in the route definition of download URLs. The function essentially parses the two values from the URL, performs a SQL select on the attachments and returns an array with the data, collected from the SQL entry. No path sanitization that we can see so far!
81+
82+
So lets check the `output()` function of the objectStorage, which is defined in [FileStorage.php](https://github.com/kanboard/kanboard/blob/v1.2.41/app/Core/ObjectStorage/FileStorage.php#L75).
83+
84+
```php
85+
public function output($key)
86+
{
87+
$filename = $this->path.DIRECTORY_SEPARATOR.$key;
88+
89+
if (! file_exists($filename)) {
90+
throw new ObjectStorageException('File not found: '.$filename);
91+
}
92+
93+
readfile($filename);
94+
}
95+
```
96+
97+
The function retrieves the `$key` parameter, which in this case is a relative path from the SQLite database, checks the file's existence and returns its content. No checks - this smells like a arbitrary file read if we are able to modify the path successfully.
98+
99+
Since we are certain that the application is vulnerable to an arbitrary file read, we immediately test it out:
100+
101+
1. We download the database
102+
2. Decompress it via `gzip -d db.sqlite.gz`
103+
3. Open it in a SQL browser and go to table `project_has_files`.
104+
4. For one of the already uploaded files, we modify the `path` to something we want to read `../../../../../../../etc/passwd`
105+
5. Commit our SQL changes and save the file
106+
6. Compress it again with `gzip db.sqlite`
107+
7. Upload it to the server
108+
8. Download the modified file via the web ui
109+
110+
And what we get is:
111+
112+
![/etc/passwd](/assets/images/kanboard/passwd.png)
113+
114+
Hurray! We are able to read arbitrary files from the server - further a "referenced" file can be deleted (if Kanboard has sufficient permissions) via the web ui.
115+
This vulnerability has been assigned CVE-2024-51747 - reported via [GHSA-78pf-vg56-5p8v](https://github.com/kanboard/kanboard/security/advisories/GHSA-78pf-vg56-5p8v).
116+
117+
##### code `exec`
118+
119+
File reads are nice - but we prefer to have code execution on the target. So we still decided to look deeper.
120+
When reviewing PHP code it's always a good start to check for the known dangerous php functions assembled [in this great collection](https://gist.github.com/mccabe615/b0907514d34b2de088c4996933ea1720).
121+
122+
```bash
123+
$ ack -i "system\s*\(" --php
124+
ServiceProvider/LoggingProvider.php
125+
42: $driver = new System();
126+
$ ack -i "shell_exec\s*\(" --php
127+
```
128+
129+
Unfortunately, we have no results except false positives in our source code, when we grepped for command execution functions.
130+
131+
However, one result with a `require` statement looks interesting.
132+
133+
```bash
134+
$ ack -i "require\s*\(" --php
135+
app/Core/Translator.php
136+
176: self::$locales = array_merge(self::$locales, require($filename));
137+
```
138+
139+
`require` is a PHP language statement to include other files, which leads directly to RCE, if the file is controllable.
140+
Having a `$filename` variable - that could be controllable by us - looks interesting.
141+
Let's see where `$filename` comes from and which value it has.
142+
It is defined in [Translator.php](https://github.com/kanboard/kanboard/blob/v1.2.41/app/Core/Translator.php#L173) in the load() function.
143+
144+
```php
145+
public static function load($language, $path = '')
146+
{
147+
if ($path === '') {
148+
$path = self::getDefaultFolder();
149+
}
150+
151+
$filename = implode(DIRECTORY_SEPARATOR, array($path, $language, 'translations.php'));
152+
153+
if (file_exists($filename)) {
154+
self::$locales = array_merge(self::$locales, require($filename));
155+
}
156+
}
157+
```
158+
159+
The `load()` function seems to be called from [LanguageModel.php](https://github.com/kanboard/kanboard/blob/v1.2.41/app/Model/LanguageModel.php#L214).
160+
161+
```bash
162+
$ ack -i "load\s*\(" --php
163+
Model/LanguageModel.php
164+
214: Translator::load($this->getCurrentLanguage());
165+
```
166+
167+
And it depends on `getCurrentLanguage()` from the same class.
168+
169+
```php
170+
public function getCurrentLanguage()
171+
{
172+
return $this->userSession->getLanguage() ?: $this->configModel->get('application_language', 'en_US');
173+
}
174+
175+
/**
176+
* Load translations for the current language
177+
*
178+
* @access public
179+
*/
180+
public function loadCurrentLanguage()
181+
{
182+
Translator::load($this->getCurrentLanguage());
183+
}
184+
```
185+
186+
Do you spot something fishy? Maybe not, since we haven't explained it yet - but `$this->configModel->get('application_language', 'en_US');` reads a configuration value from the SQLite `settings` table. In particular, it reads the `application_language` entry or defaults to `en_US`.
187+
Since we already inspected all further handling of the the `application_language` value we can conclude that this fields leads to a constrained RCE on the server!
188+
189+
If we set `application_language` to an arbitrary path via path traversal, the value will be used to construct the `$filename` path, which we saw before.
190+
However, at the end of the path, the code adds a `translations.php`. Meaning, if we are able to write a `translations.php` file anywhere on the server, where Kanboard can read it and modify the SQLite database accordingly, then we achieve code execution because Kanboard includes this file.
191+
192+
We tried to abuse this, by uploading a `translations.php` file via Kanboard's file attachments function, but the files are not saved with their original filename, but with a hash instead. This leaves us unlucky to abuse it all-at-once :S
193+
194+
195+
This vulnerability has been assigned CVE-2024-51748 - reported via [GHSA-jvff-x577-j95p](https://github.com/kanboard/kanboard/security/advisories/GHSA-jvff-x577-j95p).
196+
197+
198+
Last but not least, after reporting all vulnerabilities we noticed on retesting that we are still logged in in our testing instance after multiple days. How can this be?
199+
200+
It turns out that the session invalidation was not working properly, thus keeping sessions alive for an indefinite time.
201+
This vulnerability has been assigned CVE-2024-55603 - reported via [GHSA-gv5c-8pxr-p484](https://github.com/kanboard/kanboard/security/advisories/GHSA-gv5c-8pxr-p484) with additional details.
202+
203+
These findings once again show the danger of default credentials, giving initial access, which than can be used to exploit a system.
204+
Also it shows, that even configuration data cannot be trusted - user controllable input must be properly sanitized in all cases.
205+
206+
207+
-----
208+
209+
Timeline:
210+
211+
* **2024-10-31:** Vulnerability [CVE-2024-51747](https://github.com/kanboard/kanboard/security/advisories/GHSA-78pf-vg56-5p8v) and [CVE-2024-51748](https://github.com/kanboard/kanboard/security/advisories/GHSA-jvff-x577-j95p) has been reported to the vendor.
212+
* **2024-11-03:** Vendor has reported that the vulnerabilities will be fixed in next release.
213+
* **2024-11-10:** Kanboard 1.2.42 has been released with both fixes.
214+
* **2024-11-18:** Vulnerability [CVE-2024-55603](https://github.com/kanboard/kanboard/security/advisories/GHSA-gv5c-8pxr-p484) has been reported to the vendor.
215+
* **2024-12-08:** Vendor has reported that the vulnerability will be fixed in next release.
216+
* **2024-12-18:** Kanboard 1.2.43 has been released with the fix.
217+
* **2025-05-08:** This blog post was published.
218+
219+
<style>
220+
img {
221+
border: 1px solid #555;
222+
}
223+
</style>
32.7 KB
Loading

assets/images/kanboard/passwd.png

25 KB
Loading

0 commit comments

Comments
 (0)