Class: Rex::Proto::Ftp::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/rex/proto/ftp/client.rb

Overview

Acts as a client to an FTP server See the RFC: https://www.w3.org/Protocols/rfc959/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, port = 21, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '', verbose = false) ⇒ Client

Creates a new client instance.



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/rex/proto/ftp/client.rb', line 20

def initialize(host, port = 21, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '', verbose = false)
  self.hostname = host
  self.port     = port.to_i
  self.context  = context
  self.ssl      = ssl
  self.ssl_version = ssl_version
  self.proxies  = proxies
  self.username = username
  self.password = password
  self.verbose  = verbose
end

Instance Attribute Details

#datasocket ⇒ Object

Returns the value of attribute datasocket.



15
16
17
# File 'lib/rex/proto/ftp/client.rb', line 15

def datasocket
  @datasocket
end

Instance Method Details

#cd(path) ⇒ Object

Function implementing 'cd' or change directory command



326
327
328
# File 'lib/rex/proto/ftp/client.rb', line 326

def cd(path)
  send_cmd(["cwd " + path])
end

#connect ⇒ Object

This method estabilishes a connection to the host on the port defined in opts{}, if the connection is successful, the method returns a socket which can be used to communicate with the client



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/rex/proto/ftp/client.rb', line 37

def connect
  nsock = Rex::Socket::Tcp.create(
    'PeerHost'      =>  self.hostname,
    'PeerPort'      =>  self.port,
    'LocalHost'     => "0.0.0.0",
    'LocalPort'     =>  0.to_i,
    'SSL'           =>  self.ssl,
    'SSLVersion'    =>  self.ssl_version,
    'Proxies'       => self.proxies
  )
  self.sock = nsock
  self.banner = recv_ftp_resp(nsock)
  print_status("Connected to target FTP server.") if self.verbose
  nsock
end

#connect_login ⇒ Object

This method uses the senduser and sendpass methods defined below in order to login to the ftp server



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rex/proto/ftp/client.rb', line 129

def 
  ftpsock = connect

  if !(self.user && self.pass)
    print_error("No username and password were supplied, unable to login")
    return false
  end

  print_status("Authenticating as #{user} with password #{pass}...") if self.verbose
  res = send_user(user, ftpsock)

  if res !~ /^(331|2)/
    print_error("The server rejected our username") if self.verbose
    return false
  end

  if pass
    print_status("Sending password...") if self.verbose
    res = send_pass(pass, ftpsock)
    if res !~ /^2/
      print_error("The server rejected our password") if self.verbose
      return false
    end
  end

  true
end

#data_connect(mode = nil, nsock = self.sock) ⇒ Object

This method handles establishing datasocket for data channel



178
179
180
181
182
183
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
# File 'lib/rex/proto/ftp/client.rb', line 178

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
  if datasocket
    old_datasocket = datasocket
    self.datasocket = nil
    old_datasocket.shutdown
    old_datasocket.close
  end

  res = send_cmd(['PASV'], true, nsock)
  return nil if not res =~ /^227/

  # 227 Entering Passive Mode (127,0,0,1,196,5)
  match = res.match(/\((\d{1,3}),(\d{1,3}),(\d{1,3}),(\d{1,3}),(\d{1,3}),(\d{1,3})\)/)
  return nil unless match

  values = match.captures.map(&:to_i)
  return nil unless values.all? { |value| value.between?(0, 255) }

  dataport = (values[4] * 256) + values[5]
  return nil if dataport.zero?

  # Prefer the original control connection host over the host advertised in the PASV response.
  self.datasocket = Rex::Socket::Tcp.create(
    'PeerHost' => nsock.peerhost,
    'PeerPort' => dataport
  )
  self.datasocket
end

#data_disconnect ⇒ Object

This method handles disconnecting our data channel



216
217
218
219
# File 'lib/rex/proto/ftp/client.rb', line 216

def data_disconnect
  self.datasocket.shutdown if self.datasocket
  self.datasocket = nil
end

#download(filename) ⇒ Object

Function implementing download command



333
334
335
336
337
338
339
340
341
# File 'lib/rex/proto/ftp/client.rb', line 333

def download(filename)
  datasocket = data_connect
  send_cmd(["retr", filename])
  output = datasocket.get
  file = File.open(filename, "wb")
  file.write(output)
  file.close
  data_disconnect
end

#ls ⇒ Object

Function implementing 'ls' or list files command



308
309
310
311
312
313
314
# File 'lib/rex/proto/ftp/client.rb', line 308

def ls
  datasocket = data_connect
  send_cmd(["list"])
  output = datasocket.get
  data_disconnect
  output
end

#pwd ⇒ Object

Function implementing 'pwd' or present working directory command



319
320
321
# File 'lib/rex/proto/ftp/client.rb', line 319

def pwd
  send_cmd(["pwd"])
end

#raw_send(cmd, nsock = self.sock) ⇒ Object

This method transmits a FTP command and does not wait for a response



111
112
113
114
# File 'lib/rex/proto/ftp/client.rb', line 111

def raw_send(cmd, nsock = self.sock)
  print_status("FTP send: #{cmd.inspect}") if self.verbose
  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.



120
121
122
123
# File 'lib/rex/proto/ftp/client.rb', line 120

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



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
# File 'lib/rex/proto/ftp/client.rb', line 56

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 !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 !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 !found_end
        resp << ln
        resp << "\r\n"
        if ln.length > 3 && 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 self.verbose
      return resp
    end
  end
end

#send_cmd(args, recv = true, nsock = self.sock) ⇒ Object

This method sends one command with zero or more parameters



224
225
226
227
228
229
230
231
# File 'lib/rex/proto/ftp/client.rb', line 224

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



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
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
# File 'lib/rex/proto/ftp/client.rb', line 250

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 !type
    return send_cmd(args, true, nsock)
  end

  # Set the transfer mode and connect to the remove server
  return nil if !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 = 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

#send_pass(pass, nsock = self.sock) ⇒ Object

This method completes user authentication by sending the supplied password using the FTP 'PASS ' command.



170
171
172
173
# File 'lib/rex/proto/ftp/client.rb', line 170

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.



236
237
238
239
# File 'lib/rex/proto/ftp/client.rb', line 236

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 ' command.



161
162
163
164
# File 'lib/rex/proto/ftp/client.rb', line 161

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

#upload ⇒ Object

Function implementing upload command



346
347
348
349
350
351
352
353
354
# File 'lib/rex/proto/ftp/client.rb', line 346

def upload
  datasocket = data_connect
  file = File.open(filename, "rb")
  data = file.read
  file.close
  send_cmd(["stor", filename])
  datasocket.write(data)
  data_disconnect
end