Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb
19721 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Auxiliary::Scanner
8
include Msf::Exploit::Remote::Tcp
9
10
def initialize
11
super(
12
'Name' => 'Simple FTP Fuzzer',
13
'Description' => %q{
14
This module will connect to a FTP server and perform pre- and post-authentication fuzzing
15
},
16
'Author' => [ 'corelanc0d3r <peter.ve[at]corelan.be>', 'jduck' ],
17
'License' => MSF_LICENSE,
18
'Notes' => {
19
'Stability' => [CRASH_SERVICE_DOWN],
20
'SideEffects' => [],
21
'Reliability' => []
22
}
23
)
24
25
register_options(
26
[
27
Opt::RPORT(21),
28
OptInt.new('STARTATSTAGE', [ false, 'Start at this test stage', 1]),
29
OptInt.new('STEPSIZE', [ false, 'Increase string size each iteration with this number of chars', 10]),
30
OptInt.new('DELAY', [ false, 'Delay between connections in seconds', 1]),
31
OptInt.new('STARTSIZE', [ false, 'Fuzzing string startsize', 10]),
32
OptInt.new('ENDSIZE', [ false, 'Fuzzing string endsize', 20000]),
33
OptInt.new('STOPAFTER', [ false, 'Stop after x number of consecutive errors', 2]),
34
OptString.new('USER', [ false, 'Username', 'anonymous']),
35
OptString.new('PASS', [ false, 'Password', '[email protected]']),
36
OptBool.new('FASTFUZZ', [ false, 'Only fuzz with cyclic pattern', true]),
37
OptBool.new('CONNRESET', [ false, 'Break on CONNRESET error', true]),
38
]
39
)
40
41
@evilchars = [
42
'A', 'a', '%s', '%d', '%n', '%x', '%p', '-1', '0', '0xfffffffe', '0xffffffff', 'A/', '//', '/..', '//..',
43
'A%20', './A', '.A', ',A', 'A:', '!A', '&A', '?A', '\A', '../A/', '..?', '//A:', '\\A', '{A', '$A', 'A*',
44
'cmd', '[email protected]', '#A', 'A/../', '~', '~A', '~A/', 'A`/', '>A', '<A', 'A%n', 'A../', '.././', 'A../',
45
'....//', '~?*/', '.\../', '\.//A', '-%A', '%Y', '%H', '/1', '!', '@', '%', '&', '/?(*', '*', '(', ')',
46
'`', ',', '~/', '/.', '\$:', '/A~%n', '=', '=:;)}', '1.2.', '41414141', '-1234', '999999,', '%00', '+A',
47
'+123', '..\'', '??.', '..\.\'', '.../', '1234123+',
48
'%Y%%Y%/', '%FC%80%80%80%80%AE%FC%80%80%80%80%AE/', '????/', '\uff0e/', '%%32%65%%32%65/',
49
'+B./', '%%32%65%%32%65/', '..%c0%af', '..%e0%80%af', '..%c1%9c'
50
]
51
@commands = [
52
'ABOR', 'ACCT', 'ALLO', 'APPE', 'AUTH', 'CWD', 'CDUP', 'DELE', 'FEAT', 'HELP', 'HOST', 'LANG', 'LIST',
53
'MDTM', 'MKD', 'MLST', 'MODE', 'NLST', 'NLST -al', 'NOOP', 'OPTS', 'PASV', 'PORT', 'PROT', 'PWD', 'REIN',
54
'REST', 'RETR', 'RMD', 'RNFR', 'RNTO', 'SIZE', 'SITE', 'SITE CHMOD', 'SITE CHOWN', 'SITE EXEC', 'SITE MSG',
55
'SITE PSWD', 'SITE ZONE', 'SITE WHO', 'SMNT', 'STAT', 'STOR', 'STOU', 'STRU', 'SYST', 'TYPE', 'XCUP',
56
'XCRC', 'XCWD', 'XMKD', 'XPWD', 'XRMD'
57
]
58
@emax = @evilchars.length
59
60
register_advanced_options(
61
[
62
OptString.new('FtpCommands', [ false, 'Commands to fuzz at stages 4 and 5', @commands.join(' ')]),
63
OptBool.new('ExpandCrash', [ false, 'Expand any crash strings', false]),
64
]
65
)
66
end
67
68
def get_pkt
69
buf = sock.get_once(-1, 10)
70
vprint_status("[in ] #{buf.inspect}")
71
buf
72
end
73
74
def send_pkt(pkt, get_resp: true)
75
vprint_status("[out] #{pkt.inspect}")
76
sock.put(pkt)
77
get_pkt if get_resp
78
end
79
80
def process_phase(phase_num, phase_name, prepend = '', initial_cmds = [])
81
print_status("[Phase #{phase_num}] #{phase_name} - #{Time.now.localtime}")
82
ecount = 1
83
@evilchars.each do |evilstr|
84
if datastore['FASTFUZZ']
85
evilstr = 'Cyclic'
86
@emax = 1
87
end
88
89
next unless (@stopprocess == false)
90
91
count = datastore['STARTSIZE']
92
print_status(" Character : #{evilstr} (#{ecount}/#{@emax})")
93
ecount += 1
94
while count <= datastore['ENDSIZE']
95
begin
96
connect
97
if datastore['FASTFUZZ']
98
evil = Rex::Text.pattern_create(count)
99
else
100
evil = evilstr * count
101
end
102
print_status(" -> Fuzzing size set to #{count} (#{prepend}#{evilstr})")
103
initial_cmds.each do |cmd|
104
send_pkt(cmd)
105
end
106
pkt = prepend + evil + "\r\n"
107
send_pkt(pkt)
108
sock.put("QUIT\r\n")
109
select(nil, nil, nil, datastore['DELAY'])
110
disconnect
111
112
count += datastore['STEPSIZE']
113
rescue StandardError => e
114
@error_cnt += 1
115
print_status("Exception #{@error_cnt} of #{@nr_errors}")
116
if e.instance_of?(::Rex::ConnectionRefused) || e.instance_of?(::EOFError) || (e.instance_of?(::Errno::ECONNRESET) && datastore['CONNRESET']) || e.instance_of?(::Errno::EPIPE)
117
if datastore['ExpandCrash']
118
print_status("Crash string : #{prepend}#{evil}")
119
else
120
print_status("Crash string : #{prepend}#{evilstr} x #{count}")
121
end
122
if @error_cnt >= @nr_errors
123
print_status("System does not respond - exiting now\n")
124
@stopprocess = true
125
print_error("Error: #{e.class} #{e} #{e.backtrace}\n")
126
break
127
else
128
print_status("Exception triggered, need #{@nr_errors - @error_cnt} more exception(s) before interrupting process")
129
select(nil, nil, nil, 3) # wait 3 seconds
130
end
131
end
132
if @error_cnt >= @nr_errors
133
count += datastore['STEPSIZE']
134
@error_cnt = 0
135
end
136
end
137
end
138
end
139
end
140
141
def ftp_commands
142
if datastore['FtpCommands'].to_s.upcase == 'DEFAULT'
143
@commands
144
else
145
datastore['FtpCommands'].split(/[\s,]+/)
146
end
147
end
148
149
def run_host(ip)
150
startstage = datastore['STARTATSTAGE']
151
152
@nr_errors = datastore['STOPAFTER']
153
@error_cnt = 0
154
@stopprocess = false
155
156
if datastore['FASTFUZZ']
157
@evilchars = ['']
158
end
159
160
print_status('Connecting to host ' + ip + ' on port ' + datastore['RPORT'].to_s)
161
162
if (startstage == 1)
163
process_phase(1, 'Fuzzing without command')
164
startstage += 1
165
end
166
167
if (startstage == 2) && (@stopprocess == false)
168
process_phase(2, 'Fuzzing USER', 'USER ')
169
startstage += 1
170
end
171
172
if (startstage == 3) && (@stopprocess == false)
173
process_phase(3, 'Fuzzing PASS', 'PASS ',
174
[ 'USER ' + datastore['USER'] + "\r\n" ])
175
startstage += 1
176
end
177
178
if (startstage == 4)
179
print_status "[Phase 4] Fuzzing commands: #{ftp_commands.join(', ')}"
180
ftp_commands.each do |cmd|
181
next unless (@stopprocess == false)
182
183
process_phase(
184
4,
185
"Fuzzing command: #{cmd}", "#{cmd} ",
186
[
187
'USER ' + datastore['USER'] + "\r\n",
188
'PASS ' + datastore['PASS'] + "\r\n"
189
]
190
)
191
end
192
# Don't progress into stage 5, it must be selected manually.
193
# startstage += 1
194
end
195
196
# Fuzz other commands, all command combinations in one session
197
if (startstage == 5)
198
print_status("[Phase 5] Fuzzing other commands (Part 2, #{Time.now.localtime}): #{ftp_commands.join(', ')}")
199
ftp_commands.each do |cmd|
200
next unless (@stopprocess == false)
201
202
ecount = 1
203
count = datastore['STARTSIZE']
204
print_status("Fuzzing command #{cmd} - #{Time.now.localtime}")
205
206
connect
207
pkt = 'USER ' + datastore['USER'] + "\r\n"
208
send_pkt(pkt)
209
pkt = 'PASS ' + datastore['PASS'] + "\r\n"
210
send_pkt(pkt)
211
212
while count <= datastore['ENDSIZE']
213
print_status(" -> Fuzzing size set to #{count}")
214
begin
215
@evilchars.each do |evilstr|
216
if datastore['FASTFUZZ']
217
evilstr = 'Cyclic'
218
evil = Rex::Text.pattern_create(count)
219
@emax = 1
220
ecount = 1
221
else
222
evil = evilstr * count
223
end
224
print_status(" Command : #{cmd}, Character : #{evilstr} (#{ecount}/#{@emax})")
225
ecount += 1
226
pkt = cmd + ' ' + evil + "\r\n"
227
send_pkt(pkt)
228
select(nil, nil, nil, datastore['DELAY'])
229
@error_cnt = 0
230
end
231
rescue StandardError => e
232
@error_cnt += 1
233
print_status("Exception #{@error_cnt} of #{@nr_errors}")
234
if e.instance_of?(::Rex::ConnectionRefused) || e.instance_of?(::EOFError) || (e.instance_of?(::Errno::ECONNRESET) && datastore['CONNRESET']) || e.instance_of?(::Errno::EPIPE)
235
if @error_cnt >= @nr_errors
236
print_status("System does not respond - exiting now\n")
237
@stopprocess = true
238
print_error("Error: #{e.class} #{e} #{e.backtrace}\n")
239
break
240
end
241
242
print_status("Exception triggered, need #{@nr_errors - @error_cnt} more exception(s) before interrupting process")
243
select(nil, nil, nil, 3) # wait 3 seconds
244
end
245
if @error_cnt >= @nr_errors
246
@error_cnt = 0
247
end
248
end
249
count += datastore['STEPSIZE']
250
end
251
sock.put("QUIT\r\n")
252
select(nil, nil, nil, datastore['DELAY'])
253
disconnect
254
end
255
end
256
end
257
end
258
259