From 1f9cf8c68f800ba8ad044eacf12cf3d78636a846 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Thu, 24 Apr 2014 13:39:04 -0500 Subject: [PATCH] add the mixins for tcp and ftp skimmed down, non-module dependent mixins for TCP client and Ftp client. neccesary for loginscanner work --- lib/metasploit/framework/ftp/client.rb | 276 ++++++++++++++++++ lib/metasploit/framework/login_scanner/ftp.rb | 0 lib/metasploit/framework/tcp/client.rb | 190 ++++++++++++ 3 files changed, 466 insertions(+) create mode 100644 lib/metasploit/framework/ftp/client.rb create mode 100644 lib/metasploit/framework/login_scanner/ftp.rb create mode 100644 lib/metasploit/framework/tcp/client.rb diff --git a/lib/metasploit/framework/ftp/client.rb b/lib/metasploit/framework/ftp/client.rb new file mode 100644 index 0000000000..2e66d177f0 --- /dev/null +++ b/lib/metasploit/framework/ftp/client.rb @@ -0,0 +1,276 @@ +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module Ftp + module Client + include Metasploit::Framework::Tcp::Client + + # + # This method establishes an FTP connection to host and port specified by + # the 'rhost' and 'rport' methods. After connecting, the banner + # message is read in and stored in the 'banner' attribute. + # + def connect(global = true) + fd = super(global) + + @ftpbuff = '' unless @ftpbuff + + # Wait for a banner to arrive... + self.banner = recv_ftp_resp(fd) + + # Return the file descriptor to the caller + fd + end + + # + # This method handles establishing datasocket for data channel + # + def data_connect(mode = nil, nsock = self.sock) + if mode + res = send_cmd([ 'TYPE' , mode ], true, nsock) + return nil if not res =~ /^200/ + end + + # force datasocket to renegotiate + self.datasocket.shutdown if self.datasocket != nil + + res = send_cmd(['PASV'], true, nsock) + return nil if not res =~ /^227/ + + # 227 Entering Passive Mode (127,0,0,1,196,5) + if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/ + # convert port to FTP syntax + datahost = "#{$1}.#{$2}.#{$3}.#{$4}" + dataport = ($5.to_i * 256) + $6.to_i + self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport) + end + self.datasocket + end + + # + # This method handles disconnecting our data channel + # + def data_disconnect + self.datasocket.shutdown + self.datasocket = nil + end + + # + # Connect and login to the remote FTP server using the credentials + # that have been supplied in the exploit options. + # + def connect_login(user,pass,global = true) + ftpsock = connect(global) + + if !(user and pass) + return false + end + + res = send_user(user, ftpsock) + + if (res !~ /^(331|2)/) + return false + end + + if (pass) + res = send_pass(pass, ftpsock) + if (res !~ /^2/) + return false + end + end + + return true + end + + # + # This method logs in as the supplied user by transmitting the FTP + # 'USER ' command. + # + def send_user(user, nsock = self.sock) + raw_send("USER #{user}\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method completes user authentication by sending the supplied + # password using the FTP 'PASS ' command. + # + def send_pass(pass, nsock = self.sock) + raw_send("PASS #{pass}\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method sends a QUIT command. + # + def send_quit(nsock = self.sock) + raw_send("QUIT\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method sends one command with zero or more parameters + # + def send_cmd(args, recv = true, nsock = self.sock) + cmd = args.join(" ") + "\r\n" + ret = raw_send(cmd, nsock) + if (recv) + return recv_ftp_resp(nsock) + end + return ret + end + + # + # This method transmits the command in args and receives / uploads DATA via data channel + # For commands not needing data, it will fall through to the original send_cmd + # + # For commands that send data only, the return will be the server response. + # For commands returning both data and a server response, an array will be returned. + # + # NOTE: This function always waits for a response from the server. + # + def send_cmd_data(args, data, mode = 'a', nsock = self.sock) + type = nil + # implement some aliases for various commands + if (args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i) + # TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i + args[0] = "LIST" + type = "get" + elsif (args[0] =~ /^GET$/i) + args[0] = "RETR" + type = "get" + elsif (args[0] =~ /^PUT$/i) + args[0] = "STOR" + type = "put" + end + + # fall back if it's not a supported data command + if not type + return send_cmd(args, true, nsock) + end + + # Set the transfer mode and connect to the remove server + return nil if not data_connect(mode) + + # Our pending command should have got a connection now. + res = send_cmd(args, true, nsock) + # make sure could open port + return nil unless res =~ /^(150|125) / + + # dispatch to the proper method + if (type == "get") + # failed listings jsut disconnect.. + begin + data = self.datasocket.get_once(-1, ftp_timeout) + rescue ::EOFError + data = nil + end + else + sent = self.datasocket.put(data) + end + + # close data channel so command channel updates + data_disconnect + + # get status of transfer + ret = nil + if (type == "get") + ret = recv_ftp_resp(nsock) + ret = [ ret, data ] + else + ret = recv_ftp_resp(nsock) + end + + ret + end + + # + # This method transmits a FTP command and waits for a response. If one is + # received, it is returned to the caller. + # + def raw_send_recv(cmd, nsock = self.sock) + nsock.put(cmd) + nsock.get_once(-1, ftp_timeout) + end + + # + # This method reads an FTP response based on FTP continuation stuff + # + def recv_ftp_resp(nsock = self.sock) + found_end = false + resp = "" + left = "" + if !@ftpbuff.empty? + left << @ftpbuff + @ftpbuff = "" + end + while true + data = nsock.get_once(-1, ftp_timeout) + if not data + @ftpbuff << resp + @ftpbuff << left + return data + end + + got = left + data + left = "" + + # handle the end w/o newline case + enlidx = got.rindex(0x0a.chr) + if enlidx != (got.length-1) + if not enlidx + left << got + next + else + left << got.slice!((enlidx+1)..got.length) + end + end + + # split into lines + rarr = got.split(/\r?\n/) + rarr.each do |ln| + if not found_end + resp << ln + resp << "\r\n" + if ln.length > 3 and ln[3,1] == ' ' + found_end = true + end + else + left << ln + left << "\r\n" + end + end + if found_end + @ftpbuff << left + return resp + end + end + end + + # + # This method transmits a FTP command and does not wait for a response + # + def raw_send(cmd, nsock = self.sock) + nsock.put(cmd) + end + + def ftp_timeout + raise NotImplementedError + end + + + + protected + + # + # This attribute holds the banner that was read in after a successful call + # to connect or connect_login. + # + attr_accessor :banner, :datasocket + + + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/login_scanner/ftp.rb b/lib/metasploit/framework/login_scanner/ftp.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb new file mode 100644 index 0000000000..6ba1d58b6e --- /dev/null +++ b/lib/metasploit/framework/tcp/client.rb @@ -0,0 +1,190 @@ +module Metasploit + module Framework + module Tcp + + module EvasiveTCP + attr_accessor :_send_size, :_send_delay, :evasive + + def denagle + begin + setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + rescue ::Exception + end + end + + def write(buf, opts={}) + + return super(buf, opts) if not @evasive + + ret = 0 + idx = 0 + len = @_send_size || buf.length + + while(idx < buf.length) + + if(@_send_delay and idx > 0) + ::IO.select(nil, nil, nil, @_send_delay) + end + + pkt = buf[idx, len] + + res = super(pkt, opts) + flush() + + idx += len + ret += res if res + end + ret + end + end + + module Client + + # + # Establishes a TCP connection to the specified RHOST/RPORT + # + # @see Rex::Socket::Tcp + # @see Rex::Socket::Tcp.create + def connect(global = true, opts={}) + + dossl = false + if(opts.has_key?('SSL')) + dossl = opts['SSL'] + else + dossl = ssl + end + + nsock = Rex::Socket::Tcp.create( + 'PeerHost' => opts['RHOST'] || rhost, + 'PeerPort' => (opts['RPORT'] || rport).to_i, + 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", + 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, + 'SSL' => dossl, + 'SSLVersion' => opts['SSLVersion'] || ssl_version, + 'Proxies' => proxies, + 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i + ) + + # enable evasions on this socket + set_tcp_evasions(nsock) + + # Set this socket to the global socket as necessary + self.sock = nsock if (global) + + return nsock + end + + # Enable evasions on a given client + def set_tcp_evasions(socket) + + if( max_send_size.to_i == 0 and send_delay.to_i == 0) + return + end + + return if socket.respond_to?('evasive') + + socket.extend(EvasiveTCP) + + if ( max_send_size.to_i > 0) + socket._send_size = max_send_size + socket.denagle + socket.evasive = true + end + + if ( send_delay.to_i > 0) + socket._send_delay = send_delay + socket.evasive = true + end + end + + # + # Closes the TCP connection + # + def disconnect(nsock = self.sock) + begin + if (nsock) + nsock.shutdown + nsock.close + end + rescue IOError + end + + if (nsock == sock) + self.sock = nil + end + + # Remove this socket from the list of sockets created by this exploit + remove_socket(nsock) + end + + ## + # + # Wrappers for getters + # + ## + + def max_send_size + raise NotImplementedError + end + + def send_delay + raise NotImplementedError + end + + # + # Returns the target host + # + def rhost + raise NotImplementedError + end + + # + # Returns the remote port + # + def rport + raise NotImplementedError + end + + # + # Returns the local host for outgoing connections + # + def chost + raise NotImplementedError + end + + # + # Returns the local port for outgoing connections + # + def cport + raise NotImplementedError + end + + # + # Returns the boolean indicating SSL + # + def ssl + raise NotImplementedError + end + + # + # Returns the string indicating SSLVersion + # + def ssl_version + raise NotImplementedError + end + + # + # Returns the proxy configuration + # + def proxies + raise NotImplementedError + end + + protected + + attr_accessor :sock + + end + end + end +end \ No newline at end of file