Path: blob/master/modules/auxiliary/admin/tftp/tftp_transfer_util.rb
19591 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Rex::Proto::TFTP7include Msf::Auxiliary::Report89def initialize10super(11'Name' => 'TFTP File Transfer Utility',12'Description' => %q{13This module will transfer a file to or from a remote TFTP server.14Note that the target must be able to connect back to the Metasploit system,15and NAT traversal for TFTP is often unsupported.1617Two actions are supported: "Upload" and "Download," which behave as one might18expect -- use 'set action Actionname' to use either mode of operation.1920If "Download" is selected, at least one of FILENAME or REMOTE_FILENAME21must be set. If "Upload" is selected, either FILENAME must be set to a valid path to22a source file, or FILEDATA must be populated. FILENAME may be a fully qualified path,23or the name of a file in the Msf::Config.local_directory or Msf::Config.data_directory.24},25'Author' => [ 'todb' ],26'References' => [27['URL', 'http://www.faqs.org/rfcs/rfc1350.html'],28['URL', 'http://www.networksorcery.com/enp/protocol/tftp.htm']29],30'Actions' => [31[ 'Download', { 'Description' => 'Download REMOTE_FILENAME as FILENAME from the server.' }],32[ 'Upload', { 'Description' => 'Upload FILENAME as REMOTE_FILENAME to the server.' }]33],34'DefaultAction' => 'Upload',35'License' => MSF_LICENSE,36'Notes' => {37'Stability' => [CRASH_SAFE],38'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK],39'Reliability' => []40}41)42register_options([43OptString.new('FILENAME', [false, 'The local filename' ]),44OptString.new('FILEDATA', [false, 'Data to upload in lieu of a real local file.' ]),45OptString.new('REMOTE_FILENAME', [false, 'The remote filename']),46OptAddress.new('RHOST', [true, 'The remote TFTP server']),47OptPort.new('LPORT', [false, 'The local port the TFTP client should listen on (default is random)' ]),48OptAddressLocal.new('LHOST', [false, 'The local address the TFTP client should bind to']),49OptString.new('MODE', [false, 'The TFTP mode; usual choices are netascii and octet.', 'octet']),50Opt::RPORT(69)51])52end5354def mode55datastore['MODE'] || 'octet'56end5758def remote_file59return datastore['REMOTE_FILENAME'] if datastore['REMOTE_FILENAME']60return ::File.split(datastore['FILENAME']).last if datastore['FILENAME']61end6263def rport64datastore['RPORT'] || 6965end6667def rhost68datastore['RHOST']69end7071# Used only to store loot, doesn't actually have any semantic meaning72# for the TFTP protocol.73def datatype74case datastore['MODE']75when 'netascii'76'text/plain'77else78'application/octet-stream'79end80end8182def file83if action.name == 'Upload'84fdata = datastore['FILEDATA'].to_s85fname = datastore['FILENAME'].to_s86if !fdata.empty?87"DATA:#{datastore['FILEDATA']}"88elsif ::File.readable? fname89fname90else91fname_local = ::File.join(Msf::Config.local_directory, fname)92fname_data = ::File.join(Msf::Config.data_directory, fname)93return fname_local if ::File.file?(fname_local) && ::File.readable?(fname_local)94return fname_data if ::File.file?(fname_data) && ::File.readable?(fname_data)9596return nil # Couldn't find it, giving up.97end98else # "Download"99begin100::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last101rescue StandardError102nil103end104end105end106107# Experimental message prepending thinger. Might make it up into the108# standard Metasploit lib like vprint_status and friends.109def rtarget(ip = nil)110if (ip || rhost) && rport111[ip || rhost, rport].map(&:to_s).join(':') << ' '112elsif ip || rhost113"#{rhost} "114else115''116end117end118119# This all happens before run(), and should give an idea on how to use120# the TFTP client mixin. Essentially, you create an instance of the121# Rex::Proto::TFTP::Client class, fill it up with the relevant host and122# file data, set it to either :upload or :download, then kick off the123# transfer as you like.124def setup125@lport = datastore['LPORT'] || (1025 + rand(0xffff - 1025))126@lhost = datastore['LHOST'] || '0.0.0.0'127@local_file = file128@remote_file = remote_file129130@tftp_client = Rex::Proto::TFTP::Client.new(131'LocalHost' => @lhost,132'LocalPort' => @lport,133'PeerHost' => rhost,134'PeerPort' => rport,135'LocalFile' => @local_file,136'RemoteFile' => @remote_file,137'Mode' => mode,138'Context' => { 'Msf' => framework, 'MsfExploit' => self },139'Action' => action.name.to_s.downcase.intern140)141end142143def run144case action.name145when 'Upload'146if file147run_upload148else149print_error('Need at least a local file name or file data to upload.')150return151end152when 'Download'153if remote_file154run_download155else156print_error('Need at least a remote file name to download.')157return158end159else160print_error "Unknown action: '#{action.name}'"161end162163until @tftp_client.complete164select(nil, nil, nil, 1)165print_status [rtarget, 'TFTP transfer operation complete.'].join166save_downloaded_file if action.name == 'Download'167end168end169170# Run in case something untoward happened with the connection and the171# client object didn't get stopped on its own. This can happen with172# transfers that got interrupted or malformed (like sending a 0 byte173# file).174def cleanup175if @tftp_client && @tftp_client.respond_to?(:complete)176until @tftp_client.complete177select(nil, nil, nil, 1)178vprint_status 'Cleaning up the TFTP client ports and threads.'179@tftp_client.stop180end181end182end183184def run_upload185print_status "Sending '#{file}' to #{rhost}:#{rport} as '#{remote_file}'"186@tftp_client.send_write_request { |msg| print_tftp_status(msg) }187end188189def run_download190print_status "Receiving '#{remote_file}' from #{rhost}:#{rport} as '#{file}'"191@tftp_client.send_read_request { |msg| print_tftp_status(msg) }192end193194def save_downloaded_file195print_status "Saving #{remote_file} as '#{file}'"196fh = @tftp_client.recv_tempfile197data = begin198File.open(fh, 'rb') { |f| f.read f.stat.size }199rescue StandardError200nil201end202if data && !data.empty?203unless framework.db.active204print_status 'No database connected, so not actually saving the data:'205print_line data206end207this_service = report_service(208host: rhost,209port: rport,210name: 'tftp',211proto: 'udp'212)213store_loot('tftp.file', datatype, rhost, data, file, remote_file, this_service)214else215print_status [rtarget, 'Did not find any data, so nothing to save.'].join216end217begin218fh.unlink219rescue StandardError220nil221end222end223224def print_tftp_status(msg)225case msg226when /Aborting/, /errors.$/227print_error [rtarget, msg].join228when /^WRQ accepted/, /^Sending/, /complete!$/229print_good [rtarget, msg].join230else231vprint_status [rtarget, msg].join232end233end234end235236237