# -*- coding: binary -*- module Msf require 'msf/core/exploit/tcp' ### # # This module exposes methods that may be useful to exploits that deal with # clients that speak the File Transfer Protocol (FTP). # ### module Exploit::Remote::FtpServer include Exploit::Remote::TcpServer # # Creates an instance of an FTP exploit module. # def initialize(info = {}) super # Register the options that all FTP exploits may make use of. register_options( [ OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]), OptPort.new('PASVPORT', [ false, "The local PASV data port to listen on (0 is random)", 0 ]) ], Msf::Exploit::Remote::FtpServer) end # (see Msf::Exploit#setup) def setup super @state = {} end # (see TcpServer#on_client_connect) def on_client_connect(c) @state[c] = { :name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport, :user => nil, :pass => nil, :cwd => '/' } active_data_port_for_client(c, 20) c.put "220 FTP Server Ready\r\n" end # Dispatches client requests to command handlers. # # Handlers should be named +on_client_command_*+, ending with a # downcased FTP verb, e.g. +on_client_command_user+. If no handler # exists for the given command, returns a generic default response. # # @example Handle SYST requests # class Metasploit4 < Msf::Exploit # include Msf::Exploit::Remote::FtpServer # ... # def on_client_command_syst(cmd_conn, arg) # print_status("Responding to SYST request") # buf = build_exploit_buffer(cmd_conn) # cmd_conn.put("215 Unix Type: #{buf}\r\n") # end # end # # @param (see TcpServer#on_client_data) # @return (see TcpServer#on_client_data) def on_client_data(c) data = c.get_once return if not data cmd,arg = data.strip.split(/\s+/, 2) arg ||= "" return if not cmd # Allow per-command overrides if(self.respond_to?("on_client_command_#{cmd.downcase}")) return self.send("on_client_command_#{cmd.downcase}", c, arg) end case cmd.upcase when 'USER' @state[c][:user] = arg c.put "331 User name okay, need password...\r\n" return when 'PASS' @state[c][:pass] = arg print_status("#{@state[c][:name]} LOGIN #{@state[c][:user]} / #{@state[c][:pass]}") c.put "230 Login OK\r\n" return when 'QUIT' c.put "221 Logout\r\n" return when 'SYST' c.put "215 UNIX Type: L8\r\n" return when 'TYPE' c.put "200 Type is set\r\n" return when 'CWD' c.put "250 CWD command successful.\r\n" return when 'PWD' c.put "257 \"#{@state[c][:cwd]}\" is current directory.\r\n" return when 'SIZE' c.put "213 1\r\n" return when 'MDTM' c.put "213 #{Time.now.strftime("%Y%m%d%H%M%S")}\r\n" return when 'PORT' port = arg.split(',')[4,2] if(not port and port.length == 2) c.put("500 Illegal PORT command.\r\n") return end port = port.map{|x| x.to_i}.pack('C*').unpack('n')[0] active_data_port_for_client(c, port) c.put "200 PORT command successful.\r\n" return when 'PASV' daddr = Rex::Socket.source_address(c.peerhost) dport = passive_data_port_for_client(c) @state[c][:daddr] = daddr @state[c][:dport] = dport pasv = (daddr.split('.') + [dport].pack('n').unpack('CC')).join(',') c.put "227 Entering Passive Mode (#{pasv})\r\n" return when /^(STOR|MKD|REM|DEL|RMD)$/ c.put "500 Access denied\r\n" return else # Allow per-command overrides if(self.respond_to?("on_client_unknown_command")) return self.send("on_client_unknown_command", c, cmd.upcase, arg) end print_status("#{@state[c][:name]} UNKNOWN '#{cmd} #{arg}'") c.put("500 '#{cmd} #{arg}': command not understood.\r\n") return end return end def on_client_close(c) @state.delete(c) end def passive_data_port_for_client(c) @state[c][:mode] = :passive if(not @state[c][:passive_sock]) s = Rex::Socket::TcpServer.create( 'LocalHost' => '0.0.0.0', 'LocalPort' => datastore['PASVPORT'].to_i, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) dport = s.getsockname[2] @state[c][:passive_sock] = s @state[c][:passive_port] = dport add_socket(s) end @state[c][:passive_port] end def active_data_port_for_client(c,port) @state[c][:mode] = :active connector = Proc.new { host = c.peerhost.dup sock = Rex::Socket::Tcp.create( 'PeerHost' => host, 'PeerPort' => port, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) add_socket(sock) sock } @state[c][:active_connector] = connector @state[c][:active_port] = port end # Create a socket for the protocol data, either PASV or PORT, # depending on the client. # # @see http://tools.ietf.org/html/rfc3659 RFC 3659 # @see http://tools.ietf.org/html/rfc959 RFC 959 # @param c [Socket] Control connection socket # # @return [Socket] A connected socket for the data connection # @return [nil] on failure def establish_data_connection(c) begin Timeout.timeout(20) do if(@state[c][:mode] == :active) return @state[c][:active_connector].call() end if(@state[c][:mode] == :passive) c = @state[c][:passive_sock].accept add_socket(c) return c end end rescue ::Exception => e print_error("Failed to establish data connection: #{e.class} #{e}") end nil end end end