CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/metasploit/framework/ftp/client.rb
Views: 11784
1
require 'metasploit/framework/tcp/client'
2
3
module Metasploit
4
module Framework
5
module Ftp
6
module Client
7
extend ActiveSupport::Concern
8
include Metasploit::Framework::Tcp::Client
9
10
#
11
# This method establishes an FTP connection to host and port specified by
12
# the 'rhost' and 'rport' methods. After connecting, the banner
13
# message is read in and stored in the 'banner' attribute.
14
#
15
def connect(global = true)
16
fd = super(global)
17
18
@ftpbuff = '' unless @ftpbuff
19
20
# Wait for a banner to arrive...
21
self.banner = recv_ftp_resp(fd)
22
23
# Return the file descriptor to the caller
24
fd
25
end
26
27
#
28
# This method handles establishing datasocket for data channel
29
#
30
def data_connect(mode = nil, nsock = self.sock)
31
if mode
32
res = send_cmd([ 'TYPE' , mode ], true, nsock)
33
return nil if not res =~ /^200/
34
end
35
36
# force datasocket to renegotiate
37
self.datasocket.shutdown if self.datasocket != nil
38
39
res = send_cmd(['PASV'], true, nsock)
40
return nil if not res =~ /^227/
41
42
# 227 Entering Passive Mode (127,0,0,1,196,5)
43
if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/
44
# convert port to FTP syntax
45
datahost = "#{$1}.#{$2}.#{$3}.#{$4}"
46
dataport = ($5.to_i * 256) + $6.to_i
47
self.datasocket = Rex::Socket::Tcp.create(
48
'PeerHost' => datahost,
49
'PeerPort' => dataport,
50
'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module }
51
)
52
end
53
self.datasocket
54
end
55
56
#
57
# This method handles disconnecting our data channel
58
#
59
def data_disconnect
60
self.datasocket.shutdown
61
self.datasocket = nil
62
end
63
64
#
65
# Connect and login to the remote FTP server using the credentials
66
# that have been supplied in the exploit options.
67
#
68
def connect_login(user,pass,global = true)
69
ftpsock = connect(global)
70
71
if !(user and pass)
72
return false
73
end
74
75
res = send_user(user, ftpsock)
76
77
if (res !~ /^(331|2)/)
78
return false
79
end
80
81
if (pass)
82
res = send_pass(pass, ftpsock)
83
if (res !~ /^2/)
84
return false
85
end
86
end
87
88
return true
89
end
90
91
#
92
# This method logs in as the supplied user by transmitting the FTP
93
# 'USER <user>' command.
94
#
95
def send_user(user, nsock = self.sock)
96
raw_send("USER #{user}\r\n", nsock)
97
recv_ftp_resp(nsock)
98
end
99
100
#
101
# This method completes user authentication by sending the supplied
102
# password using the FTP 'PASS <pass>' command.
103
#
104
def send_pass(pass, nsock = self.sock)
105
raw_send("PASS #{pass}\r\n", nsock)
106
recv_ftp_resp(nsock)
107
end
108
109
#
110
# This method sends a QUIT command.
111
#
112
def send_quit(nsock = self.sock)
113
raw_send("QUIT\r\n", nsock)
114
recv_ftp_resp(nsock)
115
end
116
117
#
118
# This method sends one command with zero or more parameters
119
#
120
def send_cmd(args, recv = true, nsock = self.sock)
121
cmd = args.join(" ") + "\r\n"
122
ret = raw_send(cmd, nsock)
123
if (recv)
124
return recv_ftp_resp(nsock)
125
end
126
return ret
127
end
128
129
#
130
# This method transmits the command in args and receives / uploads DATA via data channel
131
# For commands not needing data, it will fall through to the original send_cmd
132
#
133
# For commands that send data only, the return will be the server response.
134
# For commands returning both data and a server response, an array will be returned.
135
#
136
# NOTE: This function always waits for a response from the server.
137
#
138
def send_cmd_data(args, data, mode = 'a', nsock = self.sock)
139
type = nil
140
# implement some aliases for various commands
141
if (args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i)
142
# TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i
143
args[0] = "LIST"
144
type = "get"
145
elsif (args[0] =~ /^GET$/i)
146
args[0] = "RETR"
147
type = "get"
148
elsif (args[0] =~ /^PUT$/i)
149
args[0] = "STOR"
150
type = "put"
151
end
152
153
# fall back if it's not a supported data command
154
if not type
155
return send_cmd(args, true, nsock)
156
end
157
158
# Set the transfer mode and connect to the remove server
159
return nil if not data_connect(mode)
160
161
# Our pending command should have got a connection now.
162
res = send_cmd(args, true, nsock)
163
# make sure could open port
164
return nil unless res =~ /^(150|125) /
165
166
# dispatch to the proper method
167
if (type == "get")
168
# failed listings just disconnect..
169
begin
170
data = self.datasocket.get_once(-1, ftp_timeout)
171
rescue ::EOFError
172
data = nil
173
end
174
else
175
sent = self.datasocket.put(data)
176
end
177
178
# close data channel so command channel updates
179
data_disconnect
180
181
# get status of transfer
182
ret = nil
183
if (type == "get")
184
ret = recv_ftp_resp(nsock)
185
ret = [ ret, data ]
186
else
187
ret = recv_ftp_resp(nsock)
188
end
189
190
ret
191
end
192
193
#
194
# This method transmits a FTP command and waits for a response. If one is
195
# received, it is returned to the caller.
196
#
197
def raw_send_recv(cmd, nsock = self.sock)
198
nsock.put(cmd)
199
nsock.get_once(-1, ftp_timeout)
200
end
201
202
#
203
# This method reads an FTP response based on FTP continuation stuff
204
#
205
def recv_ftp_resp(nsock = self.sock)
206
found_end = false
207
resp = ""
208
left = ""
209
if !@ftpbuff.empty?
210
left << @ftpbuff
211
@ftpbuff = ""
212
end
213
while true
214
data = nsock.get_once(-1, ftp_timeout)
215
if not data
216
@ftpbuff << resp
217
@ftpbuff << left
218
return data
219
end
220
221
got = left + data
222
left = ""
223
224
# handle the end w/o newline case
225
enlidx = got.rindex(0x0a.chr)
226
if enlidx != (got.length-1)
227
if not enlidx
228
left << got
229
next
230
else
231
left << got.slice!((enlidx+1)..got.length)
232
end
233
end
234
235
# split into lines
236
rarr = got.split(/\r?\n/)
237
rarr.each do |ln|
238
if not found_end
239
resp << ln
240
resp << "\r\n"
241
if ln.length > 3 and ln[3,1] == ' '
242
found_end = true
243
end
244
else
245
left << ln
246
left << "\r\n"
247
end
248
end
249
if found_end
250
@ftpbuff << left
251
return resp
252
end
253
end
254
end
255
256
#
257
# This method transmits a FTP command and does not wait for a response
258
#
259
def raw_send(cmd, nsock = self.sock)
260
nsock.put(cmd)
261
end
262
263
def ftp_timeout
264
raise NotImplementedError
265
end
266
267
268
269
protected
270
271
#
272
# This attribute holds the banner that was read in after a successful call
273
# to connect or connect_login.
274
#
275
attr_accessor :banner, :datasocket
276
277
278
end
279
end
280
end
281
end
282
283