class Sourcefire::Rapid7SourceFireConnector
Public Instance Methods
batch_single_ip(data_sets, asset, header, footer, max_data_size)
click to toggle source
# File lib/sourcefire_connector.rb, line 175 def batch_single_ip(data_sets, asset, header, footer, max_data_size) split_asset = nil initial = true update_footer = "ScanUpdate" asset.each_line do |line| if split_asset.nil? split_asset = header.dup + line elsif (split_asset + line + footer).bytesize < max_data_size split_asset += line else data_sets << split_asset + (initial ? footer : update_footer) initial = false split_asset = header.dup redo end end data_sets << split_asset + update_footer data_sets << header.dup end
connect_to_sourcefire()
click to toggle source
# File lib/sourcefire_connector.rb, line 221 def connect_to_sourcefire() @log.log_message('Establishing connection to SourceFire...') p12_utils = Sourcefire::PkcsOps.new p12_utils.extract_pkcs_12(@config[:options][:p12_location], @config[:sourcefire_pkcs12_password]) ctx = OpenSSL::SSL::SSLContext.new() ctx.cert = p12_utils.cert ctx.key = p12_utils.key @log.log_message('Parsed cert and key from pkcs file. Creating socket...') socket = TCPSocket.new(@config[:sourcefire_address],@config[:sourcefire_port]) @log.log_message('Socket connection established. Initiating SSL handshake...') ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx) ssl_socket.sync_close = true ssl_socket.sync = true ssl_socket.connect @log.log_message('SSL connection established! Returning socket.') ssl_socket end
generate_vuln_id(vuln_title)
click to toggle source
# File lib/sourcefire_connector.rb, line 208 def generate_vuln_id(vuln_title) vuln_id = '' md5_title = Digest::MD5::hexdigest(vuln_title) md5_title[0..7].chars.map { |ch| if (ch != '0') && (ch.to_i == 0) vuln_id += (ch.ord - 'a'.ord + 1).to_s else vuln_id +=ch end } vuln_id end
get_assets(report_file)
click to toggle source
# File lib/sourcefire_connector.rb, line 75 def get_assets(report_file) assets = [] current_asset = nil current_ip = nil CSV.foreach(report_file, headers: true) do |row| if current_asset.nil? current_asset = "AddHost,#{row['ip_address']}\n" current_asset << "SetOS,#{row['ip_address']},#{row['vendor']},#{row['name']},#{row['version']}\n" current_ip = row['ip_address'] end if row['ip_address'] == current_ip sf_csv = "" sf_csv << "AddScanResult,#{row['ip_address']},\"NeXpose\",#{generate_vuln_id(row['nexpose_id'])}," (row['port'].to_i == -1) ? sf_csv << ',' : sf_csv << "#{row['port']}," (row['protocol_id'].to_i == -1) ? sf_csv << ',' : sf_csv << "#{row['protocol_id']}," sf_csv << "\"#{row['title'].tr('"', "'")}\"," sf_csv << "\"NeXpose ID: #{row['nexpose_id'].tr('"', "'")}; References: #{row['references'] ? row['references'].scan(/<(.*?:.*?)>/).join(' ').downcase.tr('"', "'") : row['references']}; Severity: #{row['severity_score']}; PCI Severity: #{row['pci_severity_score']}; CVSS Score: #{row['cvss_score']}; CVSS Vector: (#{row['cvss_vector']})\"," row['references'].nil? ? sf_csv << "\"cve_ids: \"," : sf_csv << "\"cve_ids: #{row['references'].scan(/<CVE:(.*?)>/).join(' ').tr('"', "'")}\"," sf_csv << "\"bugtraq_ids: \"\n" current_asset += sf_csv next end #Next asset assets << current_asset current_asset = nil current_asset_id = nil redo end assets << current_asset unless current_asset.nil? @log.log_message("Total of #{assets.count} assets") assets end
process_nexpose_data(report_file)
click to toggle source
# File lib/sourcefire_connector.rb, line 115 def process_nexpose_data(report_file) #Originally using the value returned from SourceFire as the max data size. #However, this caused issues with some ticket creation / batching, and the #reduced value was hard-coded below. #max_data_size = 524288 max_data_size = 450000 @log.log_message('Creating data sets') header = "SetSource,NeXpose Scan Report\n" footer = "ScanFlush" puts 'Processing vulnerability list.' assets = get_assets(report_file) ssl_socket = connect_to_sourcefire ssl_socket.write([2,4].pack('NN')) ssl_socket.write([1].pack('N')) msg_details = read_from_socket(ssl_socket) if msg_details.kind_of?(Array) @log.log_message("Got a message of type <#{msg_details[0]}> and size <#{msg_details[1]}>") max_size = read_from_socket(ssl_socket, msg_details[1], msg_details[0]) @log.log_message("Max message length is <#{max_size}>") #max_data_size = max_size.first end ssl_socket.close data_sets = [] current_data_set = nil assets.each do |asset| if data_sets[-1].nil? if (asset + footer).bytesize < max_data_size data_sets << header.dup + asset else batch_single_ip(data_sets, asset, header, footer, max_data_size) end elsif (data_sets[-1].to_s + asset + footer).bytesize < max_data_size data_sets[-1] += asset elsif (header.dup + asset + footer).bytesize < max_data_size data_sets[-1] += footer data_sets << header.dup + asset else data_sets[-1] += footer batch_single_ip(data_sets, asset, header, footer, max_data_size) end end if data_sets.count == 0 @log.log_message("No data found. Returning <0> assets.") return [] end #mark the overall scan finish data_sets[-1] = data_sets.last + footer @log.log_message("Number of batches to submit: #{data_sets.count}") puts 'Nexpose report processing complete.' data_sets end
process_nexpose_data_alt(report_file)
click to toggle source
# File lib/sourcefire_connector.rb, line 196 def process_nexpose_data_alt(report_file) max_data_size = 524288 assets = get_assets(report_file) header = "SetSource,NeXpose Scan Report\n" footer = "ScanFlush" assets[0] = header + assets.first assets[-1] = assets.last + footer assets end
read_from_socket(ssl_socket, read_size=nil, msg_type = nil)
click to toggle source
# File lib/sourcefire_connector.rb, line 305 def read_from_socket(ssl_socket, read_size=nil, msg_type = nil) readable = IO.select([ssl_socket], nil, nil, 10) if readable.nil? @log.log_message('No response from server.') return end readable[0].each do |socket| next unless socket == ssl_socket if read_size.nil? data = ssl_socket.read_nonblock(10_000) type = data[0..3].unpack('N') read_size = data[4..7].unpack('N') return [type[0],read_size[0]] end begin data = ssl_socket.read_nonblock(read_size) return data.unpack('N') if msg_type == 1 return data rescue IO::WaitReadable @log.log_message("Waiting for data of length <#{read_size}>") IO.select([ssl_socket], nil, nil, 10) retry end end end
send_processed_data(data_sets, ssl_socket)
click to toggle source
# File lib/sourcefire_connector.rb, line 243 def send_processed_data(data_sets, ssl_socket) overall_data_size = 0 file = File.open("update_sets.csv", 'w') data_sets.each { |data| file.puts(data) } file.close @log.log_message('Starting to transmit data to SourceFire...') #Send the data number_of_commands = 0 count = 0 response = '' data_sets.each do |data| count += 1 #Inform SourceFire of the type of data. ssl_socket.write([2,4].pack('NN')) ssl_socket.write([1].pack('N')) #Send the data type and size ssl_socket.write([3,data.bytesize].pack('NN')) progress = "[#{((count-1)*100/Float(data_sets.count)).round(2)}%]" message_log = "Sending #{count.to_s.rjust(3, ' ')}/#{data_sets.count} #{progress}: Sending #{data.bytesize} bytes to socket." @log.log_message(message_log) print "\r#{message_log}" print ssl_socket.write(data) ssl_socket.flush msg_details = read_from_socket(ssl_socket) if msg_details.kind_of?(Array) @log.log_message("Got a message of type <#{msg_details[0]}> and size <#{msg_details[1]}>") response = read_from_socket(ssl_socket, msg_details[1], msg_details[0]) @log.log_message("Message is <#{response}>") end #get the response msg_details = read_from_socket(ssl_socket) response = '' if msg_details.kind_of?(Array) @log.log_message("Got a message of type <#{msg_details[0]}> and size <#{msg_details[1]}>") response = read_from_socket(ssl_socket, msg_details[1], msg_details[0]) @log.log_message("Message is <#{response}>") response = response.kind_of?(Array) ? response.first : response.to_s current_number_of_commands = response.to_s.scan(/\d+/).first.to_s.to_i number_of_commands += current_number_of_commands if current_number_of_commands > 0 @log.log_message("Sent #{current_number_of_commands} commands for latest batch to Sourcefire console.") end end end print "\rSent #{data_sets.count.to_s.rjust(3, ' ')}/#{data_sets.count} [100%]#{' '*40}" @log.log_message("Sent #{number_of_commands} commands total to Sourcefire console.") @log.log_message('Data transmission complete.') puts "\nProcessing complete." response end
set_variables(options)
click to toggle source
# File lib/sourcefire_connector.rb, line 66 def set_variables(options) options.each_key do |key| value = ENV[key.to_s.upcase] value ||= options[key] @log.log_message('No configuration value found for #{key}') if value.nil? @config[key] = value end end
setup(config_options)
click to toggle source
# File lib/sourcefire_connector.rb, line 11 def setup(config_options) @config = {} @log = Sourcefire::NxLogger.instance set_variables(config_options[:nexpose_options]) set_variables(config_options[:sourcefire_options]) @config[:options] = config_options[:options] if @config[:nexpose_address].nil? || @config[:nexpose_username].nil? || @config[:nexpose_password].nil? raise 'Must configure Nexpose settings before starting' end if @config[:sourcefire_address].nil? || @config[:sourcefire_port].nil? || @config[:sourcefire_pkcs12_password].nil? raise 'Must configure SourceFire settings before starting' end end
start()
click to toggle source
# File lib/sourcefire_connector.rb, line 29 def start puts "Nexpose Report Processing Starting" # Create a new Nexpose connection nxro = Sourcefire::ReportOps.new nxro.login(@config[:nexpose_address],@config[:nexpose_username], @config[:nexpose_password], @config[:options][:timeout], @config[:nexpose_port]) #Generate the required data from Nexpose time = Time.now.to_i report_file = File.open("nexpose_report_#{time}.csv", 'w') puts "Site ID: #{@config[:options][:sites].join(', ')}" puts 'Generating report.' nxro.generate_sourcefire_nexpose_report(report_file, @config[:options][:sites]) puts 'Report generation complete.' #Process the Nexpose results.report("name:") { TESTS.times { } }ort into SourceFire format data_sets = process_nexpose_data(report_file) #Establish connection with Sourcefire puts "Connecting to Sourcefire: #{@config[:sourcefire_address]}" ssl_socket = connect_to_sourcefire #Send the Data to SourceFire send_processed_data(data_sets, ssl_socket) #Cleanup ssl_socket.close if(@config[:options][:keep_csv] == 'N') File.delete(processed_report_file) File.delete(report_file) end @log.log_message('Finished processing. Exiting...') end