Handle empty data sends sanely for TFTP.
Don't just hang forever -- let the user know they just send empty data. TFTP servers don't like this of course.unstable
parent
2b3e3725ac
commit
677cb4b152
|
@ -19,9 +19,9 @@ module TFTP
|
||||||
#
|
#
|
||||||
# Also, since TFTP clients act as both clients and servers, we use two
|
# Also, since TFTP clients act as both clients and servers, we use two
|
||||||
# threads to handle transfers, regardless of the direction. For this reason,
|
# threads to handle transfers, regardless of the direction. For this reason,
|
||||||
# the action of sending data is nonblocking; if you need to see the
|
# the transfer actions are nonblocking; if you need to see the
|
||||||
# results of a transfer before doing something else, check the boolean complete
|
# results of a transfer before doing something else, check the boolean complete
|
||||||
# attribute and any return data in the :return_data attribute. It's a little
|
# attribute and any return data in the :status attribute. It's a little
|
||||||
# weird like that.
|
# weird like that.
|
||||||
#
|
#
|
||||||
# Finally, most (all?) clients will alter the data in netascii mode in order
|
# Finally, most (all?) clients will alter the data in netascii mode in order
|
||||||
|
@ -36,11 +36,11 @@ class Client
|
||||||
attr_accessor :local_host, :local_port, :peer_host, :peer_port
|
attr_accessor :local_host, :local_port, :peer_host, :peer_port
|
||||||
attr_accessor :threads, :context, :server_sock, :client_sock
|
attr_accessor :threads, :context, :server_sock, :client_sock
|
||||||
attr_accessor :local_file, :remote_file, :mode, :action
|
attr_accessor :local_file, :remote_file, :mode, :action
|
||||||
attr_accessor :complete, :recv_tempfile, :return_data
|
attr_accessor :complete, :recv_tempfile, :status
|
||||||
|
|
||||||
# Returns an array of [code, type, msg]. Data packets
|
# Returns an array of [code, type, msg]. Data packets
|
||||||
# specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
|
# specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
|
||||||
def parse_tftp_msg(str)
|
def parse_tftp_response(str)
|
||||||
return nil unless str.length >= 4
|
return nil unless str.length >= 4
|
||||||
ret = str.unpack("nnA*")
|
ret = str.unpack("nnA*")
|
||||||
ret[2] = str[4,str.size] if ret[0] == OpData
|
ret[2] = str[4,str.size] if ret[0] == OpData
|
||||||
|
@ -86,24 +86,26 @@ class Client
|
||||||
yield "Listening for incoming ACKs" if block_given?
|
yield "Listening for incoming ACKs" if block_given?
|
||||||
res = self.server_sock.recvfrom(65535)
|
res = self.server_sock.recvfrom(65535)
|
||||||
if res and res[0]
|
if res and res[0]
|
||||||
code, type, data = parse_tftp_msg(res[0])
|
code, type, data = parse_tftp_response(res[0])
|
||||||
if code == OpAck && self.action == :upload
|
if code == OpAck and self.action == :upload
|
||||||
if block_given?
|
if block_given?
|
||||||
yield "WRQ accepted, sending the file." if type == 0
|
yield "WRQ accepted, sending the file." if type == 0
|
||||||
self.return_data = {:write_allowed => true}
|
|
||||||
send_data(res[1], res[2]) {|msg| yield msg}
|
send_data(res[1], res[2]) {|msg| yield msg}
|
||||||
else
|
else
|
||||||
send_data(res[1], res[2])
|
send_data(res[1], res[2])
|
||||||
end
|
end
|
||||||
elsif code == OpData && self.action == :download
|
elsif code == OpData and self.action == :download
|
||||||
if block_given?
|
if block_given?
|
||||||
recv_data(res[1], res[2], data) {|msg| yield msg}
|
recv_data(res[1], res[2], data) {|msg| yield msg}
|
||||||
else
|
else
|
||||||
recv_data(res[1], res[2], data)
|
recv_data(res[1], res[2], data)
|
||||||
end
|
end
|
||||||
|
elsif code == OpError
|
||||||
|
yield("Aborting, got error type:%d, message:'%s'" % [type, data]) if block_given?
|
||||||
|
self.status = {:error => [code, type, data]}
|
||||||
else
|
else
|
||||||
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
|
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given?
|
||||||
stop
|
self.status = {:error => [code, type, data]}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
stop
|
stop
|
||||||
|
@ -112,17 +114,18 @@ class Client
|
||||||
def monitor_client_sock
|
def monitor_client_sock
|
||||||
res = self.client_sock.recvfrom(65535)
|
res = self.client_sock.recvfrom(65535)
|
||||||
if res[1] # Got a response back, so that's never good; Acks come back on server_sock.
|
if res[1] # Got a response back, so that's never good; Acks come back on server_sock.
|
||||||
code, type, msg = parse_tftp_msg(res[0])
|
code, type, data = parse_tftp_response(res[0])
|
||||||
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
|
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given?
|
||||||
|
self.status = {:error => [code, type, data]}
|
||||||
stop
|
stop
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def stop
|
def stop
|
||||||
self.complete = true
|
self.complete = true
|
||||||
self.threads.each {|t| t.kill}
|
|
||||||
self.server_sock.close rescue nil # might be closed already
|
self.server_sock.close rescue nil # might be closed already
|
||||||
self.client_sock.close rescue nil # might be closed already
|
self.client_sock.close rescue nil # might be closed already
|
||||||
|
self.threads.each {|t| t.kill}
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -140,7 +143,7 @@ class Client
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_read_request(&block)
|
def send_read_request(&block)
|
||||||
self.return_data = nil
|
self.status = nil
|
||||||
self.complete = false
|
self.complete = false
|
||||||
if block_given?
|
if block_given?
|
||||||
start_server_socket {|msg| yield msg}
|
start_server_socket {|msg| yield msg}
|
||||||
|
@ -163,7 +166,7 @@ class Client
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
until self.complete
|
until self.complete
|
||||||
return self.return_data
|
return self.status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -183,7 +186,7 @@ class Client
|
||||||
while current_block.size == 512
|
while current_block.size == 512
|
||||||
res = self.server_sock.recvfrom(65535)
|
res = self.server_sock.recvfrom(65535)
|
||||||
if res and res[0]
|
if res and res[0]
|
||||||
code, block_num, current_block = parse_tftp_msg(res[0])
|
code, block_num, current_block = parse_tftp_response(res[0])
|
||||||
if code == 3
|
if code == 3
|
||||||
if block_given?
|
if block_given?
|
||||||
write_and_ack_data(current_block,block_num,host,port) {|msg| yield msg}
|
write_and_ack_data(current_block,block_num,host,port) {|msg| yield msg}
|
||||||
|
@ -200,7 +203,7 @@ class Client
|
||||||
if block_given?
|
if block_given?
|
||||||
yield("Transferred #{self.recv_tempfile.size} bytes in #{recvd_blocks} blocks, download complete!")
|
yield("Transferred #{self.recv_tempfile.size} bytes in #{recvd_blocks} blocks, download complete!")
|
||||||
end
|
end
|
||||||
self.return_data = {:success => [
|
self.status = {:success => [
|
||||||
self.local_file,
|
self.local_file,
|
||||||
self.remote_file,
|
self.remote_file,
|
||||||
self.recv_tempfile.size,
|
self.recv_tempfile.size,
|
||||||
|
@ -229,18 +232,21 @@ class Client
|
||||||
end
|
end
|
||||||
|
|
||||||
# Note that the local filename for uploading need not be a real filename --
|
# Note that the local filename for uploading need not be a real filename --
|
||||||
# if it begins with DATA: it can be any old string of bytes.
|
# if it begins with DATA: it can be any old string of bytes. If it's missing
|
||||||
|
# completely, then just quit.
|
||||||
def blockify_file_or_data
|
def blockify_file_or_data
|
||||||
if self.local_file =~ /^DATA:(.*)/m
|
if self.local_file =~ /^DATA:(.*)/m
|
||||||
data = $1
|
data = $1
|
||||||
|
elsif ::File.file?(self.local_file) and ::File.readable?(self.local_file)
|
||||||
|
data = ::File.open(self.local_file, "rb") {|f| f.read f.stat.size} rescue []
|
||||||
else
|
else
|
||||||
data = ::File.open(self.local_file, "rb") {|f| f.read f.stat.size}
|
return []
|
||||||
end
|
end
|
||||||
data.scan(/.{1,512}/)
|
data.scan(/.{1,512}/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_write_request(&block)
|
def send_write_request(&block)
|
||||||
self.return_data = nil
|
self.status = nil
|
||||||
self.complete = false
|
self.complete = false
|
||||||
if block_given?
|
if block_given?
|
||||||
start_server_socket {|msg| yield msg}
|
start_server_socket {|msg| yield msg}
|
||||||
|
@ -263,12 +269,18 @@ class Client
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
until self.complete
|
until self.complete
|
||||||
return self.return_data
|
return self.status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_data(host,port)
|
def send_data(host,port)
|
||||||
|
self.status = {:write_allowed => true}
|
||||||
data_blocks = blockify_file_or_data()
|
data_blocks = blockify_file_or_data()
|
||||||
|
if data_blocks.empty?
|
||||||
|
yield "Closing down since there is no data to send." if block_given?
|
||||||
|
self.status = {:success => [self.local_file, self.local_file, 0, 0]}
|
||||||
|
return nil
|
||||||
|
end
|
||||||
sent_data = 0
|
sent_data = 0
|
||||||
sent_blocks = 0
|
sent_blocks = 0
|
||||||
expected_blocks = data_blocks.size
|
expected_blocks = data_blocks.size
|
||||||
|
@ -284,7 +296,7 @@ class Client
|
||||||
end
|
end
|
||||||
res = self.server_sock.recvfrom(65535)
|
res = self.server_sock.recvfrom(65535)
|
||||||
if res
|
if res
|
||||||
code, type, msg = parse_tftp_msg(res[0])
|
code, type, msg = parse_tftp_response(res[0])
|
||||||
if code == 4
|
if code == 4
|
||||||
sent_blocks += 1
|
sent_blocks += 1
|
||||||
yield "Sent #{data_block.size} bytes in block #{sent_blocks}" if block_given?
|
yield "Sent #{data_block.size} bytes in block #{sent_blocks}" if block_given?
|
||||||
|
@ -304,7 +316,7 @@ class Client
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if sent_data == expected_size
|
if sent_data == expected_size
|
||||||
self.return_data = {:success => [
|
self.status = {:success => [
|
||||||
self.local_file,
|
self.local_file,
|
||||||
self.remote_file,
|
self.remote_file,
|
||||||
sent_data,
|
sent_data,
|
||||||
|
|
|
@ -82,28 +82,22 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# The local filename must be real if you are uploading. Otherwise,
|
|
||||||
# it can be made up, since for downloading it's only used for the
|
|
||||||
# name of the loot entry.
|
|
||||||
def file
|
def file
|
||||||
if action.name == "Upload"
|
if action.name == "Upload"
|
||||||
|
fdata = datastore['FILEDATA'].to_s
|
||||||
fname = datastore['FILENAME'].to_s
|
fname = datastore['FILENAME'].to_s
|
||||||
if fname.empty?
|
if not fdata.empty?
|
||||||
fdata = "DATA:#{datastore['FILEDATA']}"
|
fdata_decorated = "DATA:#{datastore['FILEDATA']}"
|
||||||
return fdata
|
elsif ::File.readable? fname
|
||||||
else
|
|
||||||
if ::File.readable? fname
|
|
||||||
fname
|
fname
|
||||||
else
|
else
|
||||||
fname_local = ::File.join(Msf::Config.local_directory,fname)
|
fname_local = ::File.join(Msf::Config.local_directory,fname)
|
||||||
fname_data = ::File.join(Msf::Config.data_directory,fname)
|
fname_data = ::File.join(Msf::Config.data_directory,fname)
|
||||||
return fname_local if ::File.readable? fname_local
|
return fname_local if ::File.file?(fname_local) and ::File.readable?(fname_local)
|
||||||
return fname_data if ::File.readable? fname_data
|
return fname_data if ::File.file?(fname_data) and ::File.readable?(fname_data)
|
||||||
return nil # Couldn't find it, giving up.
|
return nil # Couldn't find it, giving up.
|
||||||
end
|
end
|
||||||
end
|
else # "Download"
|
||||||
else # "Download
|
|
||||||
fname = ::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last rescue nil
|
fname = ::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last rescue nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -120,21 +114,12 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# At least one, but not both, are required.
|
|
||||||
def check_valid_filename
|
|
||||||
not (datastore['FILENAME'].to_s.empty? and datastore['REMOTE_FILENAME'].to_s.empty?)
|
|
||||||
end
|
|
||||||
|
|
||||||
# This all happens before run(), and should give an idea on how to use
|
# This all happens before run(), and should give an idea on how to use
|
||||||
# the TFTP client mixin. Essentially, you create an instance of the
|
# the TFTP client mixin. Essentially, you create an instance of the
|
||||||
# Rex::Proto::TFTP::Client class, fill it up with the relevant host and
|
# Rex::Proto::TFTP::Client class, fill it up with the relevant host and
|
||||||
# file data, set it to either :upload or :download, then kick off the
|
# file data, set it to either :upload or :download, then kick off the
|
||||||
# transfer as you like.
|
# transfer as you like.
|
||||||
def setup
|
def setup
|
||||||
unless check_valid_filename()
|
|
||||||
print_error "Need at least one valid filename."
|
|
||||||
return
|
|
||||||
end
|
|
||||||
@lport = datastore['LPORT'] || (1025 + rand(0xffff-1025))
|
@lport = datastore['LPORT'] || (1025 + rand(0xffff-1025))
|
||||||
@lhost = datastore['LHOST'] || "0.0.0.0"
|
@lhost = datastore['LHOST'] || "0.0.0.0"
|
||||||
@local_file = file
|
@local_file = file
|
||||||
|
@ -153,17 +138,13 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
return unless check_valid_filename()
|
|
||||||
run_upload() if action.name == 'Upload'
|
run_upload() if action.name == 'Upload'
|
||||||
run_download() if action.name == 'Download'
|
run_download() if action.name == 'Download'
|
||||||
while true
|
while not @tftp_client.complete
|
||||||
if @tftp_client.complete
|
select(nil,nil,nil,1)
|
||||||
print_status [rtarget,"TFTP transfer operation complete."].join
|
print_status [rtarget,"TFTP transfer operation complete."].join
|
||||||
save_downloaded_file() if action.name == 'Download'
|
save_downloaded_file() if action.name == 'Download'
|
||||||
break
|
break
|
||||||
else
|
|
||||||
select(nil,nil,nil,1) # 1 second delays are just fine.
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue