Files
metasploit-gs/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb
T
Brent Cook ddef5b4961 MSF5: Remove unneeded RHOST deregister in scanners
With Metasploit 5, RHOST and RHOSTS are aliases, so no need to
deregister one or the other, as they are the same option. Deregistering
one deregisters both.
2019-03-05 13:04:49 -06:00

253 lines
9.1 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
include Msf::Exploit::Remote::Tcp
def initialize
super(
'Name' => 'Simple FTP Fuzzer',
'Description' => %q{
This module will connect to a FTP server and perform pre- and post-authentication fuzzing
},
'Author' => [ 'corelanc0d3r <peter.ve[at]corelan.be>', 'jduck' ],
'License' => MSF_LICENSE
)
register_options(
[
Opt::RPORT(21),
OptInt.new('STARTATSTAGE', [ false, "Start at this test stage",1]),
OptInt.new('STEPSIZE', [ false, "Increase string size each iteration with this number of chars",10]),
OptInt.new('DELAY', [ false, "Delay between connections in seconds",1]),
OptInt.new('STARTSIZE', [ false, "Fuzzing string startsize",10]),
OptInt.new('ENDSIZE', [ false, "Fuzzing string endsize",20000]),
OptInt.new('STOPAFTER', [ false, "Stop after x number of consecutive errors",2]),
OptString.new('USER', [ false, "Username",'anonymous']),
OptString.new('PASS', [ false, "Password",'mozilla@example.com']),
OptBool.new('FASTFUZZ', [ false, "Only fuzz with cyclic pattern",true]),
OptBool.new('CONNRESET', [ false, "Break on CONNRESET error",true]),
])
@evilchars = [
'A','a','%s','%d','%n','%x','%p','-1','0','0xfffffffe','0xffffffff','A/','//','/..','//..',
'A%20','./A','.A',',A','A:','!A','&A','?A','\A','../A/','..?','//A:','\\A','{A','$A','A*',
'cmd','A@a.com','#A','A/../','~','~A','~A/','A`/','>A','<A','A%n','A../','.././','A../',
'....//','~?*/','.\../','\.//A','-%A','%Y','%H','/1','!','@','%','&','/?(*','*','(',')',
'`',',','~/','/.','\$:','/A~%n','=','=:;)}','1.2.','41414141','-1234','999999,','%00','+A',
'+123','..\'','??.','..\.\'','.../','1234123+',
'%Y%%Y%/','%FC%80%80%80%80%AE%FC%80%80%80%80%AE/','????/','\uff0e/','%%32%65%%32%65/',
'+B./','%%32%65%%32%65/','..%c0%af','..%e0%80%af','..%c1%9c'
]
@commands = [
'ABOR','ACCT','ALLO','APPE','AUTH','CWD','CDUP','DELE','FEAT','HELP','HOST','LANG','LIST',
'MDTM','MKD','MLST','MODE','NLST','NLST -al','NOOP','OPTS','PASV','PORT','PROT','PWD','REIN',
'REST','RETR','RMD','RNFR','RNTO','SIZE','SITE','SITE CHMOD','SITE CHOWN','SITE EXEC','SITE MSG',
'SITE PSWD','SITE ZONE','SITE WHO','SMNT','STAT','STOR','STOU','STRU','SYST','TYPE','XCUP',
'XCRC','XCWD','XMKD','XPWD','XRMD'
]
@emax = @evilchars.length
register_advanced_options(
[
OptString.new('FtpCommands', [ false, "Commands to fuzz at stages 4 and 5",@commands.join(" ")]),
OptBool.new('ExpandCrash', [ false, "Expand any crash strings",false]),
])
end
def get_pkt
buf = sock.get_once(-1, 10)
vprint_status("[in ] #{buf.inspect}")
buf
end
def send_pkt(pkt, get_resp = false)
vprint_status("[out] #{pkt.inspect}")
sock.put(pkt)
get_pkt if get_resp
end
def process_phase(phase_num, phase_name, prepend = '', initial_cmds = [])
print_status("[Phase #{phase_num}] #{phase_name} - #{Time.now.localtime}")
ecount = 1
@evilchars.each do |evilstr|
if datastore['FASTFUZZ']
evilstr = "Cyclic"
@emax = 1
end
if (@stopprocess == false)
count = datastore['STARTSIZE']
print_status(" Character : #{evilstr} (#{ecount}/#{@emax})")
ecount += 1
while count <= datastore['ENDSIZE']
begin
connect
if datastore['FASTFUZZ']
evil = Rex::Text.pattern_create(count)
else
evil = evilstr * count
end
print_status(" -> Fuzzing size set to #{count} (#{prepend}#{evilstr})")
initial_cmds.each do |cmd|
send_pkt(cmd, true)
end
pkt = prepend + evil + "\r\n"
send_pkt(pkt, true)
sock.put("QUIT\r\n")
select(nil, nil, nil, datastore['DELAY'])
disconnect
count += datastore['STEPSIZE']
rescue ::Exception => e
@error_cnt += 1
print_status("Exception #{@error_cnt} of #{@nr_errors}")
if (e.class.name == 'Rex::ConnectionRefused') or (e.class.name == 'EOFError') or (e.class.name == 'Errno::ECONNRESET' and datastore['CONNRESET']) or (e.class.name == 'Errno::EPIPE')
if datastore['ExpandCrash']
print_status("Crash string : #{prepend}#{evil}")
else
print_status("Crash string : #{prepend}#{evilstr} x #{count}")
end
if @error_cnt >= @nr_errors
print_status("System does not respond - exiting now\n")
@stopprocess = true
print_error("Error: #{e.class} #{e} #{e.backtrace}\n")
return
else
print_status("Exception triggered, need #{@nr_errors - @error_cnt} more exception(s) before interrupting process")
select(nil,nil,nil,3) #wait 3 seconds
end
end
if @error_cnt >= @nr_errors
count += datastore['STEPSIZE']
@error_cnt = 0
end
end
end
end
end
end
def ftp_commands
if datastore['FtpCommands'].to_s.upcase == "DEFAULT"
@commands
else
datastore['FtpCommands'].split(/[\s,]+/)
end
end
def run_host(ip)
startstage = datastore['STARTATSTAGE']
@nr_errors = datastore['STOPAFTER']
@error_cnt = 0
@stopprocess = false
if datastore['FASTFUZZ']
@evilchars = ['']
end
print_status("Connecting to host " + ip + " on port " + datastore['RPORT'].to_s)
if (startstage == 1)
process_phase(1, "Fuzzing without command")
startstage += 1
end
if (startstage == 2) and (@stopprocess == false)
process_phase(2, "Fuzzing USER", 'USER ')
startstage += 1
end
if (startstage == 3) and (@stopprocess == false)
process_phase(3, "Fuzzing PASS", 'PASS ',
[ "USER " + datastore['USER'] + "\r\n" ])
startstage += 1
end
if (startstage == 4)
print_status "[Phase 4] Fuzzing commands: #{ftp_commands.join(", ")}"
ftp_commands().each do |cmd|
if (@stopprocess == false)
process_phase(4, "Fuzzing command: #{cmd}", "#{cmd} ",
[
"USER " + datastore['USER'] + "\r\n",
"PASS " + datastore['PASS'] + "\r\n"
])
end
end
# Don't progress into stage 5, it must be selected manually.
#startstage += 1
end
# Fuzz other commands, all command combinations in one session
if (startstage == 5)
print_status("[Phase 5] Fuzzing other commands (Part 2, #{Time.now.localtime}): #{ftp_commands.join(", ")}")
ftp_commands().each do |cmd|
if (@stopprocess == false)
ecount = 1
count = datastore['STARTSIZE']
print_status("Fuzzing command #{cmd} - #{Time.now.localtime}" )
connect
pkt = "USER " + datastore['USER'] + "\r\n"
send_pkt(pkt, true)
pkt = "PASS " + datastore['PASS'] + "\r\n"
send_pkt(pkt, true)
while count <= datastore['ENDSIZE']
print_status(" -> Fuzzing size set to #{count}")
begin
@evilchars.each do |evilstr|
if datastore['FASTFUZZ']
evilstr = "Cyclic"
evil = Rex::Text.pattern_create(count)
@emax = 1
ecount = 1
else
evil = evilstr * count
end
print_status(" Command : #{cmd}, Character : #{evilstr} (#{ecount}/#{@emax})")
ecount += 1
pkt = cmd + " " + evil + "\r\n"
send_pkt(pkt, true)
select(nil, nil, nil, datastore['DELAY'])
@error_cnt = 0
end
rescue ::Exception => e
@error_cnt += 1
print_status("Exception #{@error_cnt} of #{@nr_errors}")
if (e.class.name == 'Rex::ConnectionRefused') or (e.class.name == 'EOFError') or (e.class.name == 'Errno::ECONNRESET' and datastore['CONNRESET']) or (e.class.name == 'Errno::EPIPE')
if @error_cnt >= @nr_errors
print_status("System does not respond - exiting now\n")
@stopprocess = true
print_error("Error: #{e.class} #{e} #{e.backtrace}\n")
return
else
print_status("Exception triggered, need #{@nr_errors - @error_cnt} more exception(s) before interrupting process")
select(nil,nil,nil,3) #wait 3 seconds
end
end
if @error_cnt >= @nr_errors
@error_cnt = 0
end
end
count += datastore['STEPSIZE']
end
sock.put("QUIT\r\n")
select(nil, nil, nil, datastore['DELAY'])
disconnect
end
end
end
end
end