Class: Sourcefire::Rapid7SourceFireConnector

Inherits:
Object
  • Object
show all
Defined in:
lib/sourcefire_connector.rb

Instance Method Summary collapse

Instance Method Details

#connect_to_sourcefireObject



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/sourcefire_connector.rb', line 165

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) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/sourcefire_connector.rb', line 152

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) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/sourcefire_connector.rb', line 52

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 + "ScanFlush\n" unless current_asset.nil?
  @log.log_message("Total of #{assets.count} assets")
  assets
end

#process_nexpose_data(report_file) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/sourcefire_connector.rb', line 92

def process_nexpose_data(report_file)
  max_data_size = 524288

  @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?
      data_sets << header.dup + asset
    elsif (data_sets[-1].to_s + asset + footer).bytesize < max_data_size
      data_sets[-1] += asset
    else
      data_sets << header.dup + asset
    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) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/sourcefire_connector.rb', line 140

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) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/sourcefire_connector.rb', line 249

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) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/sourcefire_connector.rb', line 187

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

#setup(config_options) ⇒ Object



11
12
13
14
# File 'lib/sourcefire_connector.rb', line 11

def setup(config_options)
  @config = config_options
  @log = Sourcefire::NxLogger.instance
end

#startObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/sourcefire_connector.rb', line 16

def start
  puts "Nexpose Report Processing Starting"

  # Create a new Nexpose connection
  nxro = Sourcefire::ReportOps.new
  nxro.(@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