ddef5b4961
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.
253 lines
9.1 KiB
Ruby
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
|