Module: Msf::Exploit::Remote::Ftp
- Includes:
- Auxiliary::Report, Tcp
- Defined in:
- lib/msf/core/exploit/remote/ftp.rb
Overview
This module exposes methods that may be useful to exploits that deal with servers that speak the File Transfer Protocol (FTP).
Instance Attribute Summary collapse
-
#banner ⇒ Object
protected
This attribute holds the banner that was read in after a successful call to connect or connect_login.
-
#datasocket ⇒ Object
protected
This attribute holds the banner that was read in after a successful call to connect or connect_login.
Attributes included from Tcp
Instance Method Summary collapse
-
#banner_version ⇒ String?
Returns a normalised version string from the FTP banner Uses Recog - falls back to regex if no match.
-
#connect(global = true, verbose = nil) ⇒ Object
(also: #ftp_connect)
This method establishes an FTP connection to host and port specified by the ‘rhost’ and ‘rport’ methods.
-
#connect_login(global = true, verbose = nil) ⇒ Object
Connect and login to the remote FTP server using the credentials that have been supplied in the exploit options.
-
#data_connect(mode = nil, nsock = self.sock) ⇒ Object
This method handles establishing datasocket for data channel.
-
#data_disconnect ⇒ Object
This method handles disconnecting our data channel.
-
#ftp_data_timeout ⇒ Object
Returns the number of seconds to wait to get more FTP data.
-
#ftp_fingerprint(logged_in_as: 'anonymous') ⇒ void
Sends FEAT, STAT, and SYST and records the output as workspace notes Skips SYST-based OS detection if Recog already identified os.product.
-
#ftp_list_directory(logged_in_as: 'anonymous', save_loot: false) ⇒ String?
Lists the current remote directory and optionally stores the result as loot.
-
#ftp_timeout ⇒ Object
Returns the number of seconds to wait for a FTP reply.
-
#initialize(info = {}) ⇒ Object
Creates an instance of an FTP exploit module.
-
#pass ⇒ Object
Returns the user string from the ‘FTPPASS’ option.
-
#raw_send(cmd, nsock = self.sock) ⇒ Object
This method transmits a FTP command and does not wait for a response.
-
#raw_send_recv(cmd, nsock = self.sock) ⇒ Object
This method transmits a FTP command and waits for a response.
-
#recog_banner ⇒ Hash?
Matches the FTP banner against the Recog ftp.banner fingerprint set Tests each line of a multi-line banner after stripping the reply code prefix Result is memoised.
-
#recv_ftp_resp(nsock = self.sock) ⇒ Object
This method reads an FTP response based on FTP continuation stuff.
-
#send_cmd(args, recv = true, nsock = self.sock) ⇒ Object
This method sends one command with zero or more parameters.
-
#send_cmd_data(args, data, mode = 'a', nsock = self.sock) ⇒ Object
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.
-
#send_pass(pass, nsock = self.sock) ⇒ Object
This method completes user authentication by sending the supplied password using the FTP ‘PASS <pass>’ command.
-
#send_quit(nsock = self.sock) ⇒ Object
This method sends a QUIT command.
-
#send_user(user, nsock = self.sock) ⇒ Object
This method logs in as the supplied user by transmitting the FTP ‘USER <user>’ command.
-
#user ⇒ Object
Returns the user string from the ‘FTPUSER’ option.
Methods included from Auxiliary::Report
#active_db?, #create_cracked_credential, #create_credential, #create_credential_and_login, #create_credential_login, #db, #db_warning_given?, #get_client, #get_host, #inside_workspace_boundary?, #invalidate_login, #mytask, #myworkspace, #myworkspace_id, #report_auth_info, #report_client, #report_exploit, #report_host, #report_loot, #report_note, #report_service, #report_vuln, #report_web_form, #report_web_page, #report_web_site, #report_web_vuln, #store_cred, #store_local, #store_loot
Methods included from Metasploit::Framework::Require
optionally, optionally_active_record_railtie, optionally_include_metasploit_credential_creation, #optionally_include_metasploit_credential_creation, optionally_require_metasploit_db_gem_engines
Methods included from Tcp
#chost, #cleanup, #connect_timeout, #cport, #disconnect, #handler, #lhost, #lport, #peer, #print_prefix, #proxies, #replicant, #rhost, #rport, #set_tcp_evasions, #shutdown, #ssl, #ssl_cipher, #ssl_verify_mode, #ssl_version, #sslkeylogfile
Instance Attribute Details
#banner ⇒ Object (protected)
This attribute holds the banner that was read in after a successful call to connect or connect_login.
582 583 584 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 582 def @banner end |
#datasocket ⇒ Object (protected)
This attribute holds the banner that was read in after a successful call to connect or connect_login.
582 583 584 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 582 def datasocket @datasocket end |
Instance Method Details
#banner_version ⇒ String?
Returns a normalised version string from the FTP banner Uses Recog - falls back to regex if no match
164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 164 def info = return [info['service.product'], info['service.version']].compact.join(' ') if info # 220 (vsFTPd 2.3.4)\x0d\x0a -> vsFTPd 2.3.4 # 220 ProFTPD 1.3.1 Server (Debian) [::ffff:192.0.2.10]\x0d\x0a -> ProFTPD 1.3.1 Server (Debian) .to_s .sub(/^\d{3}[\s-]/, '') .strip .gsub(/\A\(|\)\z/, '') .gsub(/\s*\[(?:(?:\d{1,3}\.){3}\d{1,3}|[0-9A-Fa-f:]*:[0-9A-Fa-f:.]+)\]/, '') end |
#connect(global = true, verbose = nil) ⇒ Object Also known as: ftp_connect
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.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 50 def connect(global = true, verbose = nil) verbose = datastore['FTPDEBUG'] || datastore['VERBOSE'] if verbose.nil? print_status("Connecting to FTP server...") if verbose begin fd = super(global) rescue ::Rex::ConnectionRefused report_host(host: rhost) raise end # Wait for a banner to arrive... self. = recv_ftp_resp(fd) remove_instance_variable(:@recog_banner) if instance_variable_defined?(:@recog_banner) print_status('Connected to target FTP server') if verbose # Only record the service and banner when the greeting looks like FTP (RFC 959) if self.&.match?(/^(120|220)[\s-]/) # Cleaned up FTP banner report_service( host: rhost, port: rport, proto: 'tcp', name: 'ftp', info: Rex::Text.to_hex_ascii(), parents: { host: rhost, port: rport, proto: 'tcp', name: 'tcp' } ) # Raw FTP banner report_note( host: rhost, port: rport, proto: 'tcp', sname: 'ftp', type: 'ftp.banner', data: { banner: Rex::Text.to_hex_ascii(self..strip) } ) # Lookup FTP banner info = if info os_info = {} info.each_pair do |k, v| case k when 'os.product' then os_info[:os_name] = v when 'os.vendor' then os_info[:os_flavor] = v when 'os.version' then os_info[:os_sp] = v when 'os.cpe23', 'service.cpe23' report_note( host: rhost, port: rport, proto: 'tcp', sname: 'ftp', type: 'ftp.cpe', data: { cpe: v }, update: :unique_data ) end end report_host({ host: rhost }.merge(os_info)) unless os_info.empty? end else report_service( host: rhost, port: rport, proto: 'tcp', name: 'unknown', info: Rex::Text.to_hex_ascii((self. || @ftpbuff).to_s.strip).presence || 'Non-FTP service', parents: { host: rhost, port: rport, proto: 'tcp', name: 'tcp' } ) end # Return the file descriptor to the caller fd end |
#connect_login(global = true, verbose = nil) ⇒ Object
Connect and login to the remote FTP server using the credentials that have been supplied in the exploit options.
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 336 def connect_login(global = true, verbose = nil) verbose = datastore['FTPDEBUG'] || datastore['VERBOSE'] if verbose.nil? ftpsock = ftp_connect(global, verbose) if !(user and pass) print_error("No username and password were supplied, unable to login") return false end print_status("Authenticating as #{user} with password #{pass}...") if verbose res = send_user(user, ftpsock) if (res !~ /^(331|2)/) print_error("The server rejected our username") if verbose return false end if (pass) print_status("Sending password...") if verbose res = send_pass(pass, ftpsock) if (res !~ /^2/) print_error("The server rejected our password") if verbose return false end end return true end |
#data_connect(mode = nil, nsock = self.sock) ⇒ Object
This method handles establishing datasocket for data channel
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 270 def data_connect(mode = nil, nsock = self.sock) pass_mode = datastore['PassiveMode'] 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 # Need to be able to do both extended and normal # passive modes. normal passive mode is default # details of EPSV are in RFC2428 # pass_mode = true is EPSV; false is PASV if pass_mode res = send_cmd(['EPSV'], true, nsock) return nil if not res =~ /^229/ # 229 Entering Passive Mode (|||port|) if res =~ /\(\|\|\|(\d+)\|\)/ # convert port to FTP syntax datahost = "#{rhost}" dataport = $1.to_i self.datasocket = Rex::Socket::Tcp.create( 'PeerHost' => datahost, 'PeerPort' => dataport, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) end else 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, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) end end self.datasocket end |
#data_disconnect ⇒ Object
This method handles disconnecting our data channel
321 322 323 324 325 326 327 328 329 330 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 321 def data_disconnect begin if datasocket datasocket.shutdown datasocket.close end rescue IOError end datasocket = nil if datasocket end |
#ftp_data_timeout ⇒ Object
Returns the number of seconds to wait to get more FTP data
570 571 572 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 570 def ftp_data_timeout (datastore['FTPDataTimeout'] || 1).to_i end |
#ftp_fingerprint(logged_in_as: 'anonymous') ⇒ void
This method returns an undefined value.
Sends FEAT, STAT, and SYST and records the output as workspace notes Skips SYST-based OS detection if Recog already identified os.product
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 184 def ftp_fingerprint(logged_in_as: 'anonymous') print_status("Fingerprinting FTP service (as #{logged_in_as})") [ ['FEAT', 'ftp.cmd.feat'], # server-level ['STAT', 'ftp.cmd.stat'], # user-level ['SYST', 'ftp.cmd.syst'] # server-level ].each do |cmd, note_type| vprint_status("Sending FTP command: #{cmd}") response = send_cmd([cmd], true) raise Rex::ConnectionError.new(rhost, rport) unless response next if response.empty? response.strip.each_line.with_index do |line, i| prefix = i == 0 ? "FTP #{cmd}: " : ' ' vprint_status("#{prefix}#{line.strip}") end # Examples: # 215 UNIX Type: L8 # 215 Windows_NT # 215 UNIX emulated by FileZilla (Windows host but looks like *nix service) if cmd == 'SYST' os_name = if response.match?(/emulated/i) then nil elsif response.match?(/Windows_NT/i) then 'Windows' elsif response.match?(/UNIX/i) then '*nix' else nil end report_host(host: rhost, os_name: os_name) if os_name && !&.key?('os.product') end report_note( host: rhost, port: rport, proto: 'tcp', sname: 'ftp', type: note_type, data: { username: logged_in_as, output: response.strip } ) end end |
#ftp_list_directory(logged_in_as: 'anonymous', save_loot: false) ⇒ String?
Lists the current remote directory and optionally stores the result as loot
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 235 def ftp_list_directory(logged_in_as: 'anonymous', save_loot: false) print_status('Getting FTP root directory contents') listing = send_cmd_data(['LS'], nil) if listing.nil? print_warning('Could not retrieve directory listing (data connection failed)') return nil elsif listing[1].blank? vprint_status('Directory listing: (empty)') return nil end vprint_status('Directory listing:') listing[1].strip.each_line do |line| vprint_status(" #{line.strip}") end return listing[1] unless save_loot path = store_loot( 'ftp.dir_listing', 'text/plain', rhost, listing[1], "ftp_#{logged_in_as.to_s.downcase}.txt", "FTP directory listing for #{logged_in_as}" ) print_good("Directory listing stored to: #{path}") listing[1] end |
#ftp_timeout ⇒ Object
Returns the number of seconds to wait for a FTP reply
563 564 565 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 563 def ftp_timeout (datastore['FTPTimeout'] || 10).to_i end |
#initialize(info = {}) ⇒ Object
Creates an instance of an FTP exploit module.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 19 def initialize(info = {}) super # Register the options that all FTP exploits may make use of. ( [ Opt::RHOST, Opt::RPORT(21), OptString.new('FTPUSER', [ false, 'The username to authenticate as', 'anonymous'], fallbacks: ['USERNAME']), OptString.new('FTPPASS', [ false, 'The password for the specified username', 'mozilla@example.com'], fallbacks: ['PASSWORD']), ], Msf::Exploit::Remote::Ftp) ( [ OptInt.new('FTPTimeout', [ true, 'The number of seconds to wait for a reply from an FTP command', 16]), OptBool.new('FTPDEBUG', [ false, 'Whether or not to print verbose debug statements', false ]), OptBool.new('PassiveMode', [ false, 'Set true for extended passive (EPSV) ftp mode.', false]) ], Msf::Exploit::Remote::Ftp) register_autofilter_ports([ 21, 2121]) register_autofilter_services(%W{ ftp }) @ftpbuff = "" end |
#pass ⇒ Object
Returns the user string from the ‘FTPPASS’ option.
556 557 558 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 556 def pass datastore['FTPPASS'] end |
#raw_send(cmd, nsock = self.sock) ⇒ Object
This method transmits a FTP command and does not wait for a response
535 536 537 538 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 535 def raw_send(cmd, nsock = self.sock) print_status("FTP send: #{cmd.inspect}") if datastore['FTPDEBUG'] nsock.put(cmd) end |
#raw_send_recv(cmd, nsock = self.sock) ⇒ Object
This method transmits a FTP command and waits for a response. If one is received, it is returned to the caller.
472 473 474 475 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 472 def raw_send_recv(cmd, nsock = self.sock) nsock.put(cmd) nsock.get_once(-1, ftp_timeout) end |
#recog_banner ⇒ Hash?
Matches the FTP banner against the Recog ftp.banner fingerprint set Tests each line of a multi-line banner after stripping the reply code prefix Result is memoised
145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 145 def return nil unless return @recog_banner if instance_variable_defined?(:@recog_banner) @recog_banner = nil .to_s.each_line do |line| text = line.sub(/^\d{3}[\s-]/, '').strip next if text.empty? match = Recog::Nizer.match('ftp.banner', text) @recog_banner = match and break if match end @recog_banner end |
#recv_ftp_resp(nsock = self.sock) ⇒ Object
This method reads an FTP response based on FTP continuation stuff
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 480 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] == ' ' and ln[0,3] =~ /\A\d{3}\z/ found_end = true end else left << ln left << "\r\n" end end if found_end @ftpbuff << left print_status("FTP recv: #{resp.inspect}") if datastore['FTPDEBUG'] return resp end end end |
#send_cmd(args, recv = true, nsock = self.sock) ⇒ Object
This method sends one command with zero or more parameters
395 396 397 398 399 400 401 402 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 395 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 |
#send_cmd_data(args, data, mode = 'a', nsock = self.sock) ⇒ Object
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.
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 413 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 just disconnect.. begin data = datasocket.get(ftp_timeout, ftp_data_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 |
#send_pass(pass, nsock = self.sock) ⇒ Object
This method completes user authentication by sending the supplied password using the FTP ‘PASS <pass>’ command.
379 380 381 382 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 379 def send_pass(pass, nsock = self.sock) raw_send("PASS #{pass}\r\n", nsock) recv_ftp_resp(nsock) end |
#send_quit(nsock = self.sock) ⇒ Object
This method sends a QUIT command.
387 388 389 390 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 387 def send_quit(nsock = self.sock) raw_send("QUIT\r\n", nsock) recv_ftp_resp(nsock) end |
#send_user(user, nsock = self.sock) ⇒ Object
This method logs in as the supplied user by transmitting the FTP ‘USER <user>’ command.
370 371 372 373 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 370 def send_user(user, nsock = self.sock) raw_send("USER #{user}\r\n", nsock) recv_ftp_resp(nsock) end |
#user ⇒ Object
Returns the user string from the ‘FTPUSER’ option.
549 550 551 |
# File 'lib/msf/core/exploit/remote/ftp.rb', line 549 def user datastore['FTPUSER'] end |