Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/fuzzers/ftp/client_ftp.rb
Views: 11623
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45##6# Fuzzer written by corelanc0d3r - <peter.ve [at] corelan.be>7# http://www.corelan.be:8800/index.php/2010/10/12/death-of-an-ftp-client/8#9##101112class MetasploitModule < Msf::Auxiliary13include Exploit::Remote::TcpServer1415def initialize()16super(17'Name' => 'Simple FTP Client Fuzzer',18'Description' => %q{19This module will serve an FTP server and perform FTP client interaction fuzzing20},21'Author' => [ 'corelanc0d3r <peter.ve[at]corelan.be>' ],22'License' => MSF_LICENSE,23'References' =>24[25[ 'URL', 'http://www.corelan.be:8800/index.php/2010/10/12/death-of-an-ftp-client/' ],26]27)28register_options(29[30OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]),31OptString.new('FUZZCMDS', [ true, "Comma separated list of commands to fuzz (Uppercase).", "LIST,NLST,LS,RETR", nil, /(?:[A-Z]+,?)+/ ]),32OptInt.new('STARTSIZE', [ true, "Fuzzing string startsize.",1000]),33OptInt.new('ENDSIZE', [ true, "Max Fuzzing string size.",200000]),34OptInt.new('STEPSIZE', [ true, "Increment fuzzing string each attempt.",1000]),35OptBool.new('RESET', [ true, "Reset fuzzing values after client disconnects with QUIT cmd.",true]),36OptString.new('WELCOME', [ true, "FTP Server welcome message.","Evil FTP Server Ready"]),37OptBool.new('CYCLIC', [ true, "Use Cyclic pattern instead of A's (fuzzing payload).",true]),38OptBool.new('ERROR', [ true, "Reply with error codes only",false]),39OptBool.new('EXTRALINE', [ true, "Add extra CRLF's in response to LIST",true])40])41end424344# Not compatible today45def support_ipv6?46false47end4849def setup50super51@state = {}52end5354def run55@fuzzsize=datastore['STARTSIZE'].to_i56exploit()57end5859# Handler for new FTP client connections60def on_client_connect(c)61@state[c] = {62:name => "#{c.peerhost}:#{c.peerport}",63:ip => c.peerhost,64:port => c.peerport,65:user => nil,66:pass => nil67}68# set up an active data port on port 2069print_status("Client connected : " + c.peerhost)70active_data_port_for_client(c, 20)71send_response(c,"","WELCOME",220," "+datastore['WELCOME'])72# from this point forward, on_client_data() will take over73end7475def on_client_close(c)76@state.delete(c)77end7879# Active and Passive data connections80def passive_data_port_for_client(c)81@state[c][:mode] = :passive82if(not @state[c][:passive_sock])83s = Rex::Socket::TcpServer.create(84'LocalHost' => '0.0.0.0',85'LocalPort' => 0,86'Context' => { 'Msf' => framework, 'MsfExploit' => self }87)88dport = s.getsockname[2]89@state[c][:passive_sock] = s90@state[c][:passive_port] = dport91print_status(" - Set up passive data port #{dport}")92end93@state[c][:passive_port]94end959697def active_data_port_for_client(c,port)98@state[c][:mode] = :active99connector = Proc.new {100host = c.peerhost.dup101sock = Rex::Socket::Tcp.create(102'PeerHost' => host,103'PeerPort' => port,104'Context' => { 'Msf' => framework, 'MsfExploit' => self }105)106}107@state[c][:active_connector] = connector108@state[c][:active_port] = port109print_status(" - Set up active data port #{port}")110end111112113def establish_data_connection(c)114print_status(" - Establishing #{@state[c][:mode]} data connection")115begin116Timeout.timeout(20) do117if(@state[c][:mode] == :active)118return @state[c][:active_connector].call()119end120if(@state[c][:mode] == :passive)121return @state[c][:passive_sock].accept122end123end124print_status(" - Data connection active")125rescue ::Exception => e126print_error("Failed to establish data connection: #{e.class} #{e}")127end128nil129end130131# FTP Client-to-Server Command handlers132def on_client_data(c)133# get the client data134data = c.get_once135return if not data136# split data into command and arguments137cmd,arg = data.strip.split(/\s+/, 2)138arg ||= ""139140return if not cmd141# convert commands to uppercase and strip spaces142case cmd.upcase.strip143144when 'USER'145@state[c][:user] = arg146send_response(c,arg,"USER",331," User name okay, need password")147return148149when 'PASS'150@state[c][:pass] = arg151send_response(c,arg,"PASS",230,"-Password accepted.\r\n230 User logged in.")152return153154when 'QUIT'155if (datastore['RESET'])156print_status("Resetting fuzz settings")157@fuzzsize = datastore['STARTSIZE']158@stepsize = datastore['STEPSIZE']159end160print_status("** Client disconnected **")161send_response(c,arg,"QUIT",221," User logged out")162return163164when 'SYST'165send_response(c,arg,"SYST",215," UNIX Type: L8")166return167168when 'TYPE'169send_response(c,arg,"TYPE",200," Type set to #{arg}")170return171172when 'CWD'173send_response(c,arg,"CWD",250," CWD Command successful")174return175176when 'PWD'177send_response(c,arg,"PWD",257," \"/\" is current directory.")178return179180when 'REST'181send_response(c,arg,"REST",200," OK")182return183184when 'XPWD'185send_response(c,arg,"PWD",257," \"/\" is current directory")186return187188when 'SIZE'189send_response(c,arg,"SIZE",213," 1")190return191192when 'MDTM'193send_response(c,arg,"MDTM",213," #{Time.now.strftime("%Y%m%d%H%M%S")}")194return195196when 'CDUP'197send_response(c,arg,"CDUP",257," \"/\" is current directory")198return199200when 'PORT'201port = arg.split(',')[4,2]202if(not port and port.length == 2)203c.put("500 Illegal PORT command.\r\n")204return205end206port = port.map{|x| x.to_i}.pack('C*').unpack('n')[0]207active_data_port_for_client(c, port)208send_response(c,arg,"PORT",200," PORT command successful")209return210211when 'PASV'212print_status("Handling #{cmd.upcase} command")213daddr = Rex::Socket.source_address(c.peerhost)214dport = passive_data_port_for_client(c)215@state[c][:daddr] = daddr216@state[c][:dport] = dport217pasv = (daddr.split('.') + [dport].pack('n').unpack('CC')).join(',')218dofuzz = fuzz_this_cmd("PASV")219code = 227220if datastore['ERROR']221code = 557222end223if (dofuzz==1)224print_status(" * Fuzzing response for PASV, payload length #{@fuzzdata.length}")225send_response(c,arg,"PASV",code," Entering Passive Mode (#{@fuzzdata},1,1,1,1,1)\r\n")226incr_fuzzsize()227else228send_response(c,arg,"PASV",code," Entering Passive Mode (#{pasv})")229end230return231232when /^(LIST|NLST|LS)$/233# special case - requires active/passive connection234print_status("Handling #{cmd.upcase} command")235conn = establish_data_connection(c)236if(not conn)237c.put("425 Can't build data connection\r\n")238return239end240print_status(" - Data connection set up")241code = 150242if datastore['ERROR']243code = 550244end245c.put("#{code} Here comes the directory listing.\r\n")246code = 226247if datastore['ERROR']248code = 550249end250c.put("#{code} Directory send ok.\r\n")251strfile = "passwords.txt"252strfolder = "Secret files"253dofuzz = fuzz_this_cmd("LIST")254if (dofuzz==1)255strfile = @fuzzdata + ".txt"256strfolder = @fuzzdata257paylen = @fuzzdata.length258print_status("* Fuzzing response for LIST, payload length #{paylen}")259incr_fuzzsize()260end261print_status(" - Sending directory list via data connection")262dirlist = ""263if datastore['EXTRALINE']264extra = "\r\n"265else266extra = ""267end268dirlist = "drwxrwxrwx 1 100 0 11111 Jun 11 21:10 #{strfolder}\r\n" + extra269dirlist << "-rw-rw-r-- 1 1176 1176 1060 Aug 16 22:22 #{strfile}\r\n" + extra270conn.put("total 2\r\n"+dirlist)271conn.close272return273274when 'RETR'275# special case - requires active/passive connection276print_status("Handling #{cmd.upcase} command")277conn = establish_data_connection(c)278if(not conn)279c.put("425 Can't build data connection\r\n")280return281end282print_status(" - Data connection set up")283strcontent = "blahblahblah"284dofuzz = fuzz_this_cmd("LIST")285if (dofuzz==1)286strcontent = @fuzzdata287paylen = @fuzzdata.length288print_status("* Fuzzing response for RETR, payload length #{paylen}")289incr_fuzzsize()290end291c.put("150 Opening BINARY mode data connection #{strcontent}\r\n")292print_status(" - Sending data via data connection")293conn.put(strcontent)294c.put("226 Transfer complete\r\n")295conn.close296return297298when /^(STOR|MKD|REM|DEL|RMD)$/299send_response(c,arg,cmd.upcase,500," Access denied")300return301302when 'FEAT'303send_response(c,arg,"FEAT","","211-Features:\r\n211 End")304return305306when 'HELP'307send_response(c,arg,"HELP",214," Syntax: #{arg} - (#{arg}-specific commands)")308309when 'SITE'310send_response(c,arg,"SITE",200," OK")311return312313when 'NOOP'314send_response(c,arg,"NOOP",200," OK")315return316317when 'ABOR'318send_response(c,arg,"ABOR",225," Abor command successful")319return320321when 'ACCT'322send_response(c,arg,"ACCT",200," OK")323return324325when 'RNFR'326send_response(c,arg,"RNRF",350," File.exist")327return328329when 'RNTO'330send_response(c,arg,"RNTO",350," File.exist")331return332else333send_response(c,arg,cmd.upcase,200," Command not understood")334return335end336return337end338339# Fuzzer functions340341# Do we need to fuzz this command ?342def fuzz_this_cmd(cmd)343@fuzzcommands = datastore['FUZZCMDS'].split(",")344fuzzme = 0345@fuzzcommands.each do |thiscmd|346if ((cmd.upcase == thiscmd.upcase) || (thiscmd=="*")) && (fuzzme==0)347fuzzme = 1348end349end350if fuzzme==1351# should we use a cyclic pattern, or just A's ?352if datastore['CYCLIC']353@fuzzdata = Rex::Text.pattern_create(@fuzzsize)354else355@fuzzdata = "A" * @fuzzsize356end357end358return fuzzme359end360361def incr_fuzzsize362@stepsize = datastore['STEPSIZE'].to_i363@fuzzsize = @fuzzsize + @stepsize364print_status("(i) Setting next payload size to #{@fuzzsize}")365if (@fuzzsize > datastore['ENDSIZE'].to_i)366@fuzzsize = datastore['ENDSIZE'].to_i367end368end369370371# Send data back to the server372def send_response(c,arg,cmd,code,msg)373if arg.length > 40374showarg = arg[0,40] + "..."375else376showarg = arg377end378if cmd.length > 40379showcmd = cmd[0,40] + "..."380else381showcmd = cmd382end383print_status("Sending response for '#{showcmd}' command, arg #{showarg}")384dofuzz = fuzz_this_cmd(cmd)385## Fuzz this command ? (excluding PASV, which is handled in the command handler)386if (dofuzz==1) && (cmd.upcase != "PASV")387paylen = @fuzzdata.length388print_status("* Fuzzing response for #{cmd.upcase}, payload length #{paylen}")389if datastore['ERROR']390code = "550 "391end392if cmd=="FEAT"393@fuzzdata = "211-Features:\r\n "+@fuzzdata+"\r\n211 End"394end395if cmd=="PWD"396@fuzzdata = " \"/"+@fuzzdata+"\" is current directory"397end398cmsg = code.to_s + " " + @fuzzdata399c.put("#{cmsg}\r\n")400print_status("* Fuzz data sent")401incr_fuzzsize()402else403# Do not fuzz404cmsg = code.to_s + msg405cmsg = cmsg.strip406c.put("#{cmsg}\r\n")407end408return409end410end411412413