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 ]) ], Msf::Exploit::Remote::FtpServer) end def setup super @state = {} end def on_client_connect(c) @state[c] = { :name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport, :user => nil, :pass => nil } active_data_port_for_client(c, 20) c.put "220 FTP Server Ready\r\n" end 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 \"/\" 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' => 0, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) dport = s.getsockname[2] @state[c][:passive_sock] = s @state[c][:passive_port] = dport 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 } ) } @state[c][:active_connector] = connector @state[c][:active_port] = port end 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) return @state[c][:passive_sock].accept end end rescue ::Exception => e print_error("Failed to establish data connection: #{e.class} #{e}") end nil end end end