Class: Honeycomb::Interact

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(db_path = nil, bin_path = nil, username = nil, key = nil, servers = nil, base_path = nil) ⇒ Interact

This initializes a Honeycomb::Interact object and sets all the necessary variables which are used by other methods of the object.

Variables and their purpose:

  • db_path - Path where databases are stored/saved

  • bin_path - Path where binaries are stored/saved

  • username - Username to connect to remote honeypot servers

  • key - Path to private key which is used for connections to honeypot

servers

  • servers - Array of servers to connect to

  • base_path - Base location where Dionaea is installed to (Default per installation instructions: /opt/dionaea)



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/honeycomb/interact/interact.rb', line 42

def initialize(db_path = nil, bin_path = nil, username = nil, key = nil, 
               servers = nil, base_path = nil)
  self.db_path = Honeycomb::Env::CONFIG[:download_databases] ||
    self.db_path = Pathname.new(__FILE__).dirname.dirname.dirname.dirname.expand_path.join('data').join('logsql/').to_s ||
      db_path
  self.bin_path = Honeycomb::Env::CONFIG[:download_binaries] ||
    self.bin_path = Pathname.new(__FILE__).dirname.dirname.dirname.dirname.expand_path.join('data').join('binaries/').to_s ||
      bin_path
  self.username = Honeycomb::Env::CONFIG["honey_config"]["username"] || 
    username
  self.key = Honeycomb::Env::CONFIG["honey_config"]["key"] || key
  self.servers = Honeycomb::Env::CONFIG["honey_config"]["servers"] || 
    servers
  self.base_path = Honeycomb::Env::CONFIG["honey_config"]["path"] ||
    base_path
end

Instance Attribute Details

#base_pathObject

Returns the value of attribute base_path.



28
29
30
# File 'lib/honeycomb/interact/interact.rb', line 28

def base_path
  @base_path
end

#bin_pathObject

Returns the value of attribute bin_path.



28
29
30
# File 'lib/honeycomb/interact/interact.rb', line 28

def bin_path
  @bin_path
end

#db_pathObject

Returns the value of attribute db_path.



28
29
30
# File 'lib/honeycomb/interact/interact.rb', line 28

def db_path
  @db_path
end

#keyObject

Returns the value of attribute key.



28
29
30
# File 'lib/honeycomb/interact/interact.rb', line 28

def key
  @key
end

#serversObject

Returns the value of attribute servers.



28
29
30
# File 'lib/honeycomb/interact/interact.rb', line 28

def servers
  @servers
end

#usernameObject

Returns the value of attribute username.



28
29
30
# File 'lib/honeycomb/interact/interact.rb', line 28

def username
  @username
end

Class Method Details

.get_all_md5_info(dir = Pathname.new(__FILE__).dirname.dirname.dirname.dirname.expand_path.join('data').join('binaries/').to_s) ⇒ Object

This method will call the get_md5_info method on all binaries located in the provided directory. This information is then outputted to screen (soon to be changed).

Again, only leaving this in here because it does a decent job of showing the kind of information you can pull.

Argument:

  • dif - Directory of binaries

Retruns:

  • Nothing

Multiple items are outputted to the screen.



255
256
257
258
259
260
261
262
263
264
# File 'lib/honeycomb/interact/interact.rb', line 255

def self.get_all_md5_info(dir = Pathname.new(__FILE__).dirname.dirname.dirname.dirname.expand_path.join('data').join('binaries/').to_s)
  results = []
  all_binaries = Dir.entries(dir)
  all_binaries.each do |bin|
    if bin =~ /\w{32}/
      require 'pp'
      pp self.get_md5_info(bin)
    end
  end
end

.get_md5_info(md5) ⇒ Object

Only leaving this in here because it does a decent job of showing some of the stuff you can do with these DataMapper database bindings.

This method was mainly created to be utilized for another project. It takes the md5 as in argument and performs a series of queries against all of the database to retrieve a large amount of data about that provided binary (in md5 checksum)

Multiple encounters can be returned. These encounters are uniqued based on the URL of the download. Additionally, if duplicates of a given url are discovered, the encounter with the earliest timestamp is added to the hash.

Argument:

  • md5 - MD5 String of the binary to be examined.

Returns:

  • Hash - => <md5_provided>,

    :encounters = [{ :url => <url_discovered>,
                     :original_filename => <md5_provided>,
                     :remote_host => <ip_address>,
                     :source_timestamp => <Time>,
                     :source => "Honeypot - <ip_address>",
                     :virustotal => {:url => <url>,
                       :timestamp => <timestamp_of_vt_scan>,
                       :results => {:scanner => <scanner>,
                                    :result => <result>
                       }
                   }
    


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
# File 'lib/honeycomb/interact/interact.rb', line 196

def self.get_md5_info(md5)
  all_encounters = []
  all_instances = self.all{Honeycomb::Download.all(:download_md5_hash => md5)}
  all_instances.each do |instance|
    all_encounters << {:url => instance.download_url}
  end
  num = 0 
  all_encounters.uniq! {|e| e[:url] }
  all_encounters.each do |url|
    all_connections = self.all{Honeycomb::Download.all(:download_md5_hash => md5, 
                                                       :download_url => url[:url]).connections}
    connection = {}
    all_connections.each do |conn|
      if not connection[:source_timestamp].nil?
        if Time.at(connection[:source_timestamp]) > Time.at(conn.connection_timestamp.to_i)
          connection[:source_timestamp] = Time.at(conn.connection_timestamp.to_i)
          connection[:remote_host] = conn.remote_host
          connection[:source] = conn.local_host
        end
      else
        connection[:source_timestamp] = Time.at(conn.connection_timestamp.to_i)
        connection[:remote_host] = conn.remote_host
        connection[:source] = conn.local_host
      end
    end
    all_encounters[num][:original_filename] = md5
    all_encounters[num][:remote_host] = connection[:remote_host]
    all_encounters[num][:source_timestamp] = connection[:source_timestamp]
    all_encounters[num][:source] = "Honeypot - #{connection[:source]}"
    virustotal_links = self.all{Honeycomb::Virustotal.first(:virustotal_md5_hash => md5).virustotal_permalink}
    virustotal_timestamp = self.all{Honeycomb::Virustotal.first(:virustotal_md5_hash => md5).virustotal_timestamp}
    virustotal_results = {}
    self.all{Honeycomb::Virustotal.first(:virustotal_md5_hash => md5).virustotalscans}.each do |vtscan|
      virustotal_results[:scanner] = vtscan.virustotalscan_scanner
      virustotal_results[:result] = vtscan.virustotalscan_result
    end
    all_encounters[num][:virustotal] = {:url => virustotal_links, 
      :timestamp => virustotal_timestamp, :results => virustotal_results} if virustotal_links
    num += 1
  end
  return {:md5 => md5, :encounters => all_encounters}
end

.ip_reputation_ruleObject

This method was created mainly for my own benefit, but I figured I’d leave it in here in case anyone else would like to use it. Was doing some statistics for a co-worker who was looking for information about the IP addresses which has connected to my honeypots.

It’s a little clunky right now, and I ended up just performing actual SQL queries due to memory issues (Some of my databases are over 10 gigs in size). It will write a list of IP addresses and the number of times encountered to the following log file:

ip_reputation.txt

The format of the data is:

<ip_address>,<count>

In order from most encountered to least encountered. I also ignore people that have connected to these honeypots less than 200 times.



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/honeycomb/interact/interact.rb', line 326

def self.ip_reputation_rule
  all_connections_hash = {}

  ::DataMapper::Repository.adapters.each do |repo|
    if repo[0] == :default
      next
    end
    response = repo[1].select("SELECT COUNT(remote_host), remote_host FROM connections WHERE connection_type = \"accept\" GROUP BY remote_host ORDER BY COUNT(remote_host)")
    response.each do |struct|
      ip = struct.to_a[1]
      count = struct.to_a[0]
      if all_connections_hash[ip]
        all_connections_hash[ip] = all_connections_hash[ip].to_i + 
          count.to_i
      else
        all_connections_hash[ip] = count.to_i
      end
    end
  end
  all_connections_hash = all_connections_hash.sort_by { |k,v| -1*v }
  File.open("ip_reputation.txt", 'w') do |f|
    all_connections_hash.each do |ip,count|
      if count > 200
        f.write("#{ip},#{count}\n") 
      end
    end
  end   
end

Instance Method Details

#all(&block) ⇒ Object

Used for executing a query against all databases at once.



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/honeycomb/interact/interact.rb', line 267

def all(&block)
  all_values = []
  ::DataMapper::Repository.adapters.each do |repo|
    if repo[0] == :default
      next
    end
    begin
      response = DataMapper.repository(repo[0]) {yield}
      if response.kind_of?(DataMapper::Collection)
        response.each do |x|
          all_values << x 
        end
      else
        all_values << response if response
      end
    rescue Exception => e
      #puts e.message
    end
  end
  all_values
end

#check_diskspaceObject

This method will query the diskspace on all remote servers by calling the internal ssh_command method. It executes the command ‘df -h /’ and parses the results. The response is then parsed to return the total percentage of diskspace being used currently on each host.

Arguments:

  • None

Returns:

  • [ {:server => “Server Hostname”, :result =>

Multiple strings with the results are outputted to the screen.



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/honeycomb/interact/interact.rb', line 154

def check_diskspace
  response = self.ssh_command("df -h /") 
  all_usage = []
  response.each do |server_hash|
    usage = server_hash[:result]
    if usage =~ /^(\/\w+)+.+\S+\s+\S+\s+\S+\s+(([0-9]+)%)/m
      all_usage << {:server => server_hash[:server], :result => $2}
    end
  end
  all_usage
end

#download_binaries(server = self.servers) ⇒ Object

This method will attempt to download all binaries from all servers specified in Honeycomb::Interact.servers.

It will attempt to store all binaries into the folder specified in Honeycomb::Interact.bin_path.

Additionally, rsync is utilized to transfer these files. It was chosen to use rsync over scp in order to limit the amount of bandwidth used between the client and servers.

Arguments:

  • server - Array of servers to query



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/honeycomb/interact/interact.rb', line 71

def download_binaries(server = self.servers)
  server.each do |server|
    tries = 0
    puts "Downloading binaries from #{server} ..."
    begin
      Open3::popen3("rsync -v --force --ignore-errors --times -r -u -e \"ssh -i #{self.key}\" #{self.username}@#{server}:#{self.base_path}/var/dionaea/binaries/ #{self.bin_path}") { |stdin, stdout, stderr|
        puts stdout.read.strip
        puts stderr.read.strip
      }
    rescue
      tries += 1
      retry if tries <= 3
      puts "Unable to connect. Moving on ..."
      next
    end
  end
end

#download_databases(server = self.servers) ⇒ Object

This method will attempt to download all databases from all servers specified in Honeycomb::Interact.servers.

It will attempt to store all binaries into the folder specified in Honeycomb::Interact.db_path.

Additionally, scp is utilized to transfer these files. During tests, it was discovered that rsync had less than ideal results when downloading these files. While the transfer would appear to occur without error, the databases were often found to be corrupt.

Arguments:

  • server - Array of servers to query



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/honeycomb/interact/interact.rb', line 102

def download_databases(server = self.servers)
  server.each do |server|
    tries = 0
    begin
      Net::SSH.start(server, self.username, :keys => self.key) do |session|
        puts "Downloading database from #{server} ..."
        session.scp.download!(base_path + "/var/dionaea/logsql.sqlite", 
                              self.db_path + "#{server}.sqlite")
      end
    rescue Errno::ETIMEDOUT
      tries += 1
      retry if tries <= 3
      puts "Unable to connect. Moving on ..."
      next
    rescue Exception => e
      puts "Error encountered: #{e.message}"
      next
    end
  end
end

#execute_command(command) ⇒ Object

This method will execute a command via ssh on all servers specified in the Honeycomb::Interact.servers variable. This command calls the internal ssh_command method in order to properly function.

Argument:

  • command - Command to execute

Returns:

  • Nothing

Multiple strings with the results are outputted to the screen.



134
135
136
137
138
139
140
# File 'lib/honeycomb/interact/interact.rb', line 134

def execute_command(command)
  response = self.ssh_command(command)
  response.each do |server_hash|
    puts "Executing #{command} on #{server_hash[:server]}:"
    puts "\t#{server_hash[:result].gsub!(/\n/,"\n\t")}"
  end
end

#individual(repo, &block) ⇒ Object

Used for executing a query against a single database.



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/honeycomb/interact/interact.rb', line 290

def individual(repo, &block)
  all_values = []
  begin
    response = DataMapper.repository(repo[0]) {yield}
    if response.kind_of?(DataMapper::Collection)
      response.each do |x|
      all_values << x 
      end
    else
      all_values << response if response
    end
  rescue Exception => e
    #puts e.message
  end
  all_values
end

#ssh_command(command) ⇒ Object

This method is used internally by the execute_command method. It will take a command as an argument and execute it on ever server that is stored in Honeycomb::Interact.servers. The results are stored in a hash which is returned in an Array.

Argument:

  • command - Command to be executed

Returns:

  • Array of hashes -

    => <server_name>, :result => <result_of_command>


366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/honeycomb/interact/interact.rb', line 366

def ssh_command(command)
  results = []
  self.servers.each do |server|
    begin
      Net::SSH.start(server, self.username, :keys => self.key) do |session|
        session.exec command do |ch, stream, data|
        if stream == :stderr
            results << {:server => server, :result => "ERROR: #{data}"}
          else
            results << {:server => server, :result => data}
          end
        end
      end
    rescue 
      next
    end
  end
  return results
end