Module: Msf::Exploit::Remote::Ftp

Includes:
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

Attributes included from Tcp

#sock

Instance Method Summary collapse

Methods included from Tcp

#chost, #cleanup, #connect_timeout, #cport, #disconnect, #handler, #lhost, #lport, #peer, #print_prefix, #proxies, #rhost, #rport, #set_tcp_evasions, #shutdown, #ssl, #ssl_cipher, #ssl_verify_mode, #ssl_version

Instance Attribute Details

This attribute holds the banner that was read in after a successful call to connect or connect_login.



381
382
383
# File 'lib/msf/core/exploit/remote/ftp.rb', line 381

def banner
  @banner
end

#datasocketObject (protected)

This attribute holds the banner that was read in after a successful call to connect or connect_login.



381
382
383
# File 'lib/msf/core/exploit/remote/ftp.rb', line 381

def datasocket
  @datasocket
end

Instance Method Details

#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.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/msf/core/exploit/remote/ftp.rb', line 49

def connect(global = true, verbose = nil)
  verbose ||= datastore['FTPDEBUG']
  verbose ||= datastore['VERBOSE']

  print_status("Connecting to FTP server #{rhost}:#{rport}...") if verbose

  fd = super(global)

  # Wait for a banner to arrive...
  self.banner = recv_ftp_resp(fd)

  print_status("Connected to target FTP server.") if verbose

  # 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.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/msf/core/exploit/remote/ftp.rb', line 135

def (global = true, verbose = nil)
  verbose ||= datastore['FTPDEBUG']
  verbose ||= datastore['VERBOSE']
  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



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
# File 'lib/msf/core/exploit/remote/ftp.rb', line 69

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_disconnectObject

This method handles disconnecting our data channel



120
121
122
123
124
125
126
127
128
129
# File 'lib/msf/core/exploit/remote/ftp.rb', line 120

def data_disconnect
  begin
    if datasocket
      datasocket.shutdown
      datasocket.close
    end
  rescue IOError
  end
  datasocket = nil if datasocket
end

#ftp_data_timeoutObject

Returns the number of seconds to wait to get more FTP data



369
370
371
# File 'lib/msf/core/exploit/remote/ftp.rb', line 369

def ftp_data_timeout
  (datastore['FTPDataTimeout'] || 1).to_i
end

#ftp_timeoutObject

Returns the number of seconds to wait for a FTP reply



362
363
364
# File 'lib/msf/core/exploit/remote/ftp.rb', line 362

def ftp_timeout
  (datastore['FTPTimeout'] || 10).to_i
end

#initialize(info = {}) ⇒ Object

Creates an instance of an FTP exploit module.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/msf/core/exploit/remote/ftp.rb', line 18

def initialize(info = {})
  super

  # Register the options that all FTP exploits may make use of.
  register_options(
    [
      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)

  register_advanced_options(
    [
      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

#passObject

Returns the user string from the 'FTPPASS' option.



355
356
357
# File 'lib/msf/core/exploit/remote/ftp.rb', line 355

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



334
335
336
337
# File 'lib/msf/core/exploit/remote/ftp.rb', line 334

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.



271
272
273
274
# File 'lib/msf/core/exploit/remote/ftp.rb', line 271

def raw_send_recv(cmd, nsock = self.sock)
  nsock.put(cmd)
  nsock.get_once(-1, ftp_timeout)
end

#recv_ftp_resp(nsock = self.sock) ⇒ Object

This method reads an FTP response based on FTP continuation stuff



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
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/msf/core/exploit/remote/ftp.rb', line 279

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
      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



194
195
196
197
198
199
200
201
# File 'lib/msf/core/exploit/remote/ftp.rb', line 194

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.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
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 212

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.



178
179
180
181
# File 'lib/msf/core/exploit/remote/ftp.rb', line 178

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.



186
187
188
189
# File 'lib/msf/core/exploit/remote/ftp.rb', line 186

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.



169
170
171
172
# File 'lib/msf/core/exploit/remote/ftp.rb', line 169

def send_user(user, nsock = self.sock)
  raw_send("USER #{user}\r\n", nsock)
  recv_ftp_resp(nsock)
end

#userObject

Returns the user string from the 'FTPUSER' option.



348
349
350
# File 'lib/msf/core/exploit/remote/ftp.rb', line 348

def user
  datastore['FTPUSER']
end