ukemi 0.1.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/exe/ukemi CHANGED
@@ -5,4 +5,6 @@ $LOAD_PATH.unshift("#{__dir__}/../lib")
5
5
 
6
6
  require "ukemi"
7
7
 
8
- Ukemi::CLI.start
8
+ ARGV.unshift(Ukemi::CLI.default_task) unless Ukemi::CLI.all_tasks.key?(ARGV[0])
9
+
10
+ Ukemi::CLI.start(ARGV)
data/lib/ukemi.rb CHANGED
@@ -22,10 +22,14 @@ require "ukemi/record"
22
22
  require "ukemi/services/service"
23
23
 
24
24
  require "ukemi/services/circl"
25
+ require "ukemi/services/dnsdb"
26
+ require "ukemi/services/otx"
25
27
  require "ukemi/services/passivetotal"
26
28
  require "ukemi/services/securitytrails"
27
29
  require "ukemi/services/virustotal"
28
30
 
29
31
  require "ukemi/moderator"
30
32
 
33
+ require "ukemi/configuration"
34
+
31
35
  require "ukemi/cli"
data/lib/ukemi/cli.rb CHANGED
@@ -6,16 +6,32 @@ require "thor"
6
6
  module Ukemi
7
7
  class CLI < Thor
8
8
  desc "lookup [IP|DOMAIN]", "Lookup passive DNS services"
9
+ method_option :order_by, type: :string, desc: "Ordering of the passve DNS resolutions (last_seen or first_seen)", default: "-last_seen"
9
10
  def lookup(data)
10
11
  data = refang(data)
12
+ set_ordering options["order_by"]
13
+
11
14
  result = Moderator.lookup(data)
12
15
  puts JSON.pretty_generate(result)
13
16
  end
14
17
 
18
+ default_command :lookup
19
+
15
20
  no_commands do
16
21
  def refang(data)
17
22
  data.gsub("[.]", ".").gsub("(.)", ".")
18
23
  end
24
+
25
+ def set_ordering(order_by)
26
+ parts = order_by.split("-")
27
+ ordering_key = parts.last
28
+ sort_order = parts.length == 2 ? "DESC" : "ASC"
29
+
30
+ Ukemi.configure do |config|
31
+ config.ordering_key = ordering_key
32
+ config.sort_order = sort_order
33
+ end
34
+ end
19
35
  end
20
36
  end
21
37
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukemi
4
+ class Configuration
5
+ attr_accessor :ordering_key, :sort_order
6
+
7
+ def initialize
8
+ @ordering_key = "last_seen"
9
+ @sort_order = "DESC"
10
+ end
11
+ end
12
+
13
+ class << self
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ attr_writer :configuration
19
+
20
+ def configure
21
+ yield configuration
22
+ end
23
+ end
24
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "parallel"
4
4
  require "time"
5
+ require "date"
5
6
 
6
7
  module Ukemi
7
8
  class Moderator
@@ -12,34 +13,63 @@ module Ukemi
12
13
 
13
14
  begin
14
15
  service.lookup data
15
- rescue ::PassiveTotal::Error, ::VirusTotal::Error, ::SecurityTrails::Error, PassiveCIRCL::Error
16
+ rescue ::PassiveTotal::Error, ::VirusTotal::Error, ::SecurityTrails::Error, PassiveCIRCL::Error, DNSDB::Error, Faraday::Error
16
17
  nil
17
18
  end
18
19
  end.flatten.compact
19
20
 
21
+ format records
22
+ end
23
+
24
+ def format(records)
20
25
  memo = Hash.new { |h, k| h[k] = [] }
26
+
21
27
  records.each do |record|
22
28
  memo[record.data] << {
23
- firtst_seen: record.first_seen,
29
+ first_seen: record.first_seen,
24
30
  last_seen: record.last_seen,
25
- source: record.source,
31
+ source: record.source
26
32
  }
27
33
  end
34
+ # Merge first seen last seen and make the sources a list.
35
+ formatted = memo.map do |key, sources|
36
+ first_seens = sources.filter_map { |record| convert_to_unixtime record[:first_seen] }
37
+ last_seens = sources.filter_map { |record| convert_to_unixtime record[:last_seen] }
38
+ [
39
+ key,
40
+ {
41
+ first_seen: convert_to_date(first_seens.min),
42
+ last_seen: convert_to_date(last_seens.max),
43
+ sources: sources
44
+ }
45
+ ]
46
+ end.to_h
28
47
 
29
- memo.sort_by do |_key, array|
30
- last_seens = array.map do |record|
31
- parse_to_unixtime record.dig(:last_seen)
48
+ # Sorting
49
+ ordering_key = Ukemi.configuration.ordering_key.to_sym
50
+ sort_order = Ukemi.configuration.sort_order
51
+ formatted.sort_by do |_key, hash|
52
+ value = hash[ordering_key]
53
+ if sort_order == "DESC"
54
+ value ? -convert_to_unixtime(value) : -1
55
+ else
56
+ value ? convert_to_unixtime(value) : Float::MAX.to_i
32
57
  end
33
- -last_seens.max
34
58
  end.to_h
35
59
  end
36
60
 
37
- def parse_to_unixtime(date)
38
- return -1 unless date
61
+ def convert_to_unixtime(date)
62
+ return nil unless date
39
63
 
40
64
  Time.parse(date).to_i
41
65
  end
42
66
 
67
+ def convert_to_date(time)
68
+ return nil unless time
69
+
70
+ Time.at(time).to_date.to_s
71
+ end
72
+
43
73
  class << self
44
74
  def lookup(data)
45
75
  new.lookup data
data/lib/ukemi/record.rb CHANGED
@@ -2,10 +2,7 @@
2
2
 
3
3
  module Ukemi
4
4
  class Record
5
- attr_reader :data
6
- attr_reader :first_seen
7
- attr_reader :last_seen
8
- attr_reader :source
5
+ attr_reader :data, :first_seen, :last_seen, :source
9
6
 
10
7
  def initialize(data:, first_seen: nil, last_seen: nil, source: nil)
11
8
  @data = data
@@ -8,7 +8,7 @@ module Ukemi
8
8
  private
9
9
 
10
10
  def config_keys
11
- %w(CIRCL_PASSIVE_USERNAME CIRCL_PASSIVE_PASSWORD)
11
+ %w[CIRCL_PASSIVE_USERNAME CIRCL_PASSIVE_PASSWORD]
12
12
  end
13
13
 
14
14
  def api
@@ -26,14 +26,14 @@ module Ukemi
26
26
  def passive_dns_lookup(data, key = nil)
27
27
  results = api.dns.query(data)
28
28
  results = results.select do |result|
29
- result.dig("rrtype") == "A"
29
+ result["rrtype"] == "A"
30
30
  end
31
31
 
32
32
  results.map do |result|
33
33
  Record.new(
34
- data: result.dig(key),
35
- first_seen: Time.at(result.dig("time_first")).to_date.to_s,
36
- last_seen: Time.at(result.dig("time_last")).to_date.to_s,
34
+ data: result[key],
35
+ first_seen: Time.at(result["time_first"]).to_date.to_s,
36
+ last_seen: Time.at(result["time_last"]).to_date.to_s,
37
37
  source: name
38
38
  )
39
39
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "dnsdb"
5
+
6
+ module Ukemi
7
+ module Services
8
+ class DNSDB < Service
9
+ private
10
+
11
+ def config_keys
12
+ %w[DNSDB_API_KEY]
13
+ end
14
+
15
+ def api
16
+ @api ||= ::DNSDB::API.new
17
+ end
18
+
19
+ def lookup_by_ip(data)
20
+ results = api.lookup.rdata(type: "ip", value: data, rrtype: "A")
21
+ results.map do |result|
22
+ rrname = result["rrname"]
23
+ # Remove the last dot (e.g. "example.com.")
24
+ data = rrname[0..-2]
25
+ Record.new(
26
+ data: data,
27
+ first_seen: Time.at(result["time_first"]).to_date.to_s,
28
+ last_seen: Time.at(result["time_last"]).to_date.to_s,
29
+ source: name
30
+ )
31
+ end
32
+ end
33
+
34
+ def lookup_by_domain(data)
35
+ results = api.lookup.rrset(owner_name: data, rrtype: "A")
36
+ results.map do |result|
37
+ first_seen = Time.at(result["time_first"]).to_date.to_s
38
+ last_seen = Time.at(result["time_last"]).to_date.to_s
39
+
40
+ values = result["rdata"] || []
41
+ values.map do |value|
42
+ Record.new(
43
+ data: value,
44
+ first_seen: first_seen,
45
+ last_seen: last_seen,
46
+ source: name
47
+ )
48
+ end
49
+ end.flatten
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "otx_ruby"
5
+
6
+ module Ukemi
7
+ module Services
8
+ class OTX < Service
9
+ private
10
+
11
+ def config_keys
12
+ %w[OTX_API_KEY]
13
+ end
14
+
15
+ def api_key
16
+ @api_key ||= ENV["OTX_API_KEY"]
17
+ end
18
+
19
+ def domain_client
20
+ @domain_client ||= ::OTX::Domain.new(api_key)
21
+ end
22
+
23
+ def ip_client
24
+ @ip_client ||= ::OTX::IP.new(api_key)
25
+ end
26
+
27
+ def lookup_by_ip(data)
28
+ records = ip_client.get_passive_dns(data)
29
+ memo = Hash.new { |h, k| h[k] = [] }
30
+ records.each do |record|
31
+ next if record.record_type != "A"
32
+
33
+ domain = record.hostname
34
+ memo[domain] << Date.parse(record.last).to_s
35
+ memo[domain] << Date.parse(record.first).to_s
36
+ end
37
+
38
+ memo.keys.map do |domain|
39
+ Record.new(
40
+ data: domain,
41
+ first_seen: memo[domain].min,
42
+ last_seen: memo[domain].max,
43
+ source: name
44
+ )
45
+ end
46
+ end
47
+
48
+ def lookup_by_domain(data)
49
+ records = domain_client.get_passive_dns(data)
50
+
51
+ memo = Hash.new { |h, k| h[k] = [] }
52
+ records.each do |record|
53
+ next if record.record_type != "A"
54
+ next if record.hostname != data
55
+
56
+ ip = record.address
57
+ memo[ip] << Date.parse(record.last).to_s
58
+ memo[ip] << Date.parse(record.first).to_s
59
+ end
60
+
61
+ memo.keys.map do |ip|
62
+ Record.new(
63
+ data: ip,
64
+ first_seen: memo[ip].min,
65
+ last_seen: memo[ip].max,
66
+ source: name
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -12,12 +12,12 @@ module Ukemi
12
12
  end
13
13
 
14
14
  def config_keys
15
- %w(PASSIVETOTAL_USERNAME PASSIVETOTAL_API_KEY)
15
+ %w[PASSIVETOTAL_USERNAME PASSIVETOTAL_API_KEY]
16
16
  end
17
17
 
18
18
  def lookup_by_ip(data)
19
19
  res = api.dns.passive(data)
20
- results = res.dig("results") || []
20
+ results = res["results"] || []
21
21
  convert_to_records results
22
22
  end
23
23
 
@@ -27,9 +27,9 @@ module Ukemi
27
27
 
28
28
  def convert_to_records(results)
29
29
  results.map do |result|
30
- data = result.dig("resolve")
31
- first_seen = result.dig("firstSeen").to_s.split.first
32
- last_seen = result.dig("lastSeen").to_s.split.first
30
+ data = result["resolve"]
31
+ first_seen = result["firstSeen"].to_s.split.first
32
+ last_seen = result["lastSeen"].to_s.split.first
33
33
  Record.new(
34
34
  data: data,
35
35
  first_seen: first_seen,
@@ -9,7 +9,7 @@ module Ukemi
9
9
  private
10
10
 
11
11
  def config_keys
12
- %w(SECURITYTRAILS_API_KEY)
12
+ %w[SECURITYTRAILS_API_KEY]
13
13
  end
14
14
 
15
15
  def api
@@ -17,9 +17,9 @@ module Ukemi
17
17
  end
18
18
 
19
19
  def lookup_by_ip(data)
20
- result = api.domains.search( filter: { ipv4: data })
21
- records = result.dig("records") || []
22
- hostnames = records.map { |record| record.dig("hostname") }
20
+ result = api.domains.search(filter: { ipv4: data })
21
+ records = result["records"] || []
22
+ hostnames = records.map { |record| record["hostname"] }
23
23
  hostnames.map do |hostname|
24
24
  Record.new(
25
25
  data: hostname,
@@ -32,24 +32,25 @@ module Ukemi
32
32
 
33
33
  def lookup_by_domain(data)
34
34
  result = api.history.get_all_dns_history(data, type: "a")
35
- records = result.dig("records") || []
36
- records.map do |record|
37
- values = record.dig("values") || []
38
- values.map do |value|
39
- Record.new(
40
- data: value.dig("ip"),
41
- first_seen: record.dig("first_seen"),
42
- last_seen: record.dig("last_seen"),
43
- source: name
44
- )
35
+ records = result["records"] || []
36
+
37
+ memo = Hash.new { |h, k| h[k] = [] }
38
+ records.each do |record|
39
+ values = record["values"] || []
40
+ values.each do |value|
41
+ ip = value["ip"]
42
+ memo[ip] << record["first_seen"]
43
+ memo[ip] << record["last_seen"]
45
44
  end
46
- end.flatten
47
- end
45
+ end
48
46
 
49
- def extract_attributes(response)
50
- data = response.dig("data") || []
51
- data.map do |item|
52
- item.dig("attributes") || []
47
+ memo.keys.map do |ip|
48
+ Record.new(
49
+ data: ip,
50
+ first_seen: memo[ip].min,
51
+ last_seen: memo[ip].max,
52
+ source: name
53
+ )
53
54
  end
54
55
  end
55
56
  end
@@ -9,7 +9,7 @@ module Ukemi
9
9
  private
10
10
 
11
11
  def config_keys
12
- %w(VIRUSTOTAL_API_KEY)
12
+ %w[VIRUSTOTAL_API_KEY]
13
13
  end
14
14
 
15
15
  def api
@@ -29,9 +29,9 @@ module Ukemi
29
29
  end
30
30
 
31
31
  def extract_attributes(response)
32
- data = response.dig("data") || []
32
+ data = response["data"] || []
33
33
  data.map do |item|
34
- item.dig("attributes") || []
34
+ item["attributes"] || []
35
35
  end
36
36
  end
37
37
 
@@ -39,8 +39,8 @@ module Ukemi
39
39
  memo = Hash.new { |h, k| h[k] = [] }
40
40
 
41
41
  attributes.each do |attribute|
42
- data = attribute.dig(key)
43
- date = Time.at(attribute.dig("date")).to_date.to_s
42
+ data = attribute[key]
43
+ date = Time.at(attribute["date"]).to_date.to_s
44
44
  memo[data] << date
45
45
  end
46
46