mihari 1.3.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +68 -0
  3. data/README.md +20 -270
  4. data/Rakefile +1 -0
  5. data/build_frontend.sh +14 -0
  6. data/docker/Dockerfile +3 -2
  7. data/{screenshots → images}/alert.png +0 -0
  8. data/{screenshots → images}/eyecatch.png +0 -0
  9. data/images/logo.png +0 -0
  10. data/{screenshots → images}/misp.png +0 -0
  11. data/{screenshots → images}/slack.png +0 -0
  12. data/images/web_alerts.png +0 -0
  13. data/images/web_config.png +0 -0
  14. data/lib/mihari.rb +2 -2
  15. data/lib/mihari/analyzers/base.rb +1 -1
  16. data/lib/mihari/analyzers/basic.rb +3 -4
  17. data/lib/mihari/analyzers/binaryedge.rb +4 -7
  18. data/lib/mihari/analyzers/censys.rb +3 -7
  19. data/lib/mihari/analyzers/circl.rb +3 -5
  20. data/lib/mihari/analyzers/crtsh.rb +2 -6
  21. data/lib/mihari/analyzers/dnpedia.rb +3 -6
  22. data/lib/mihari/analyzers/dnstwister.rb +4 -9
  23. data/lib/mihari/analyzers/free_text.rb +2 -6
  24. data/lib/mihari/analyzers/http_hash.rb +3 -11
  25. data/lib/mihari/analyzers/onyphe.rb +3 -6
  26. data/lib/mihari/analyzers/otx.rb +4 -9
  27. data/lib/mihari/analyzers/passive_dns.rb +4 -9
  28. data/lib/mihari/analyzers/passive_ssl.rb +4 -9
  29. data/lib/mihari/analyzers/passivetotal.rb +9 -14
  30. data/lib/mihari/analyzers/pulsedive.rb +7 -12
  31. data/lib/mihari/analyzers/reverse_whois.rb +4 -9
  32. data/lib/mihari/analyzers/securitytrails.rb +12 -17
  33. data/lib/mihari/analyzers/securitytrails_domain_feed.rb +3 -7
  34. data/lib/mihari/analyzers/shodan.rb +9 -8
  35. data/lib/mihari/analyzers/spyse.rb +6 -11
  36. data/lib/mihari/analyzers/ssh_fingerprint.rb +2 -6
  37. data/lib/mihari/analyzers/urlscan.rb +21 -9
  38. data/lib/mihari/analyzers/virustotal.rb +6 -11
  39. data/lib/mihari/analyzers/zoomeye.rb +7 -11
  40. data/lib/mihari/cli.rb +20 -28
  41. data/lib/mihari/config.rb +1 -25
  42. data/lib/mihari/configurable.rb +4 -5
  43. data/lib/mihari/database.rb +7 -1
  44. data/lib/mihari/emitters/misp.rb +4 -2
  45. data/lib/mihari/emitters/slack.rb +18 -7
  46. data/lib/mihari/emitters/the_hive.rb +2 -2
  47. data/lib/mihari/errors.rb +2 -0
  48. data/lib/mihari/models/alert.rb +51 -0
  49. data/lib/mihari/models/artifact.rb +1 -1
  50. data/lib/mihari/notifiers/exception_notifier.rb +5 -5
  51. data/lib/mihari/serializers/alert.rb +1 -1
  52. data/lib/mihari/serializers/artifact.rb +1 -1
  53. data/lib/mihari/serializers/tag.rb +1 -1
  54. data/lib/mihari/status.rb +10 -10
  55. data/lib/mihari/type_checker.rb +4 -4
  56. data/lib/mihari/version.rb +1 -1
  57. data/lib/mihari/web/app.rb +126 -0
  58. data/lib/mihari/web/public/index.html +21 -0
  59. data/lib/mihari/web/public/static/favicon.ico +0 -0
  60. data/lib/mihari/web/public/static/fonts/fa-brands-400.099a9556.woff +0 -0
  61. data/lib/mihari/web/public/static/fonts/fa-brands-400.30cc681d.eot +0 -0
  62. data/lib/mihari/web/public/static/fonts/fa-brands-400.3b89dd10.ttf +0 -0
  63. data/lib/mihari/web/public/static/fonts/fa-brands-400.f7307680.woff2 +0 -0
  64. data/lib/mihari/web/public/static/fonts/fa-regular-400.1f77739c.ttf +0 -0
  65. data/lib/mihari/web/public/static/fonts/fa-regular-400.7124eb50.woff +0 -0
  66. data/lib/mihari/web/public/static/fonts/fa-regular-400.7630483d.eot +0 -0
  67. data/lib/mihari/web/public/static/fonts/fa-regular-400.f0f82301.woff2 +0 -0
  68. data/lib/mihari/web/public/static/fonts/fa-solid-900.1042e8ca.eot +0 -0
  69. data/lib/mihari/web/public/static/fonts/fa-solid-900.605ed792.ttf +0 -0
  70. data/lib/mihari/web/public/static/fonts/fa-solid-900.9fe5a17c.woff +0 -0
  71. data/lib/mihari/web/public/static/fonts/fa-solid-900.e8a427e1.woff2 +0 -0
  72. data/lib/mihari/web/public/static/img/fa-brands-400.ba7ed552.svg +3717 -0
  73. data/lib/mihari/web/public/static/img/fa-regular-400.0bb42845.svg +801 -0
  74. data/lib/mihari/web/public/static/img/fa-solid-900.376c1f97.svg +5034 -0
  75. data/lib/mihari/web/public/static/js/app.58b32d15.js +12 -0
  76. data/lib/mihari/web/public/static/js/app.58b32d15.js.map +1 -0
  77. data/mihari.gemspec +30 -25
  78. metadata +163 -56
  79. data/.travis.yml +0 -13
  80. data/lib/mihari/alert_viewer.rb +0 -23
File without changes
File without changes
Binary file
Binary file
data/lib/mihari.rb CHANGED
@@ -79,8 +79,8 @@ require "mihari/emitters/slack"
79
79
  require "mihari/emitters/stdout"
80
80
  require "mihari/emitters/the_hive"
81
81
 
82
- require "mihari/alert_viewer"
83
-
84
82
  require "mihari/status"
85
83
 
84
+ require "mihari/web/app"
85
+
86
86
  require "mihari/cli"
@@ -42,7 +42,7 @@ module Mihari
42
42
 
43
43
  def run_emitter(emitter)
44
44
  emitter.run(title: title, description: description, artifacts: unique_artifacts, source: source, tags: tags)
45
- rescue StandardError => e
45
+ rescue => e
46
46
  puts "Emission by #{emitter.class} is failed: #{e}"
47
47
  end
48
48
 
@@ -4,12 +4,11 @@ module Mihari
4
4
  module Analyzers
5
5
  class Basic < Base
6
6
  attr_accessor :title
7
- attr_reader :description
8
- attr_reader :artifacts
9
- attr_reader :source
10
- attr_reader :tags
7
+ attr_reader :description, :artifacts, :source, :tags
11
8
 
12
9
  def initialize(title:, description:, artifacts:, source:, tags: [])
10
+ super()
11
+
13
12
  @title = title
14
13
  @description = description
15
14
  @artifacts = artifacts
@@ -5,10 +5,7 @@ require "binaryedge"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class BinaryEdge < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
8
+ attr_reader :title, :description, :query, :tags
12
9
 
13
10
  def initialize(query, title: nil, description: nil, tags: [])
14
11
  super()
@@ -24,7 +21,7 @@ module Mihari
24
21
  return [] unless results || results.empty?
25
22
 
26
23
  results.map do |result|
27
- events = result.dig("events") || []
24
+ events = result["events"] || []
28
25
  events.map do |event|
29
26
  event.dig "target", "ip"
30
27
  end.compact
@@ -47,7 +44,7 @@ module Mihari
47
44
  responses = []
48
45
  (1..Float::INFINITY).each do |page|
49
46
  res = search_with_page(query, page: page)
50
- total = res.dig("total").to_i
47
+ total = res["total"].to_i
51
48
 
52
49
  responses << res
53
50
  break if total <= page * PAGE_SIZE
@@ -56,7 +53,7 @@ module Mihari
56
53
  end
57
54
 
58
55
  def config_keys
59
- %w(binaryedge_api_key)
56
+ %w[binaryedge_api_key]
60
57
  end
61
58
 
62
59
  def api
@@ -5,11 +5,7 @@ require "censu"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Censys < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
12
- attr_reader :type
8
+ attr_reader :title, :description, :query, :tags, :type
13
9
 
14
10
  def initialize(query, title: nil, description: nil, tags: [], type: "ipv4")
15
11
  super()
@@ -37,7 +33,7 @@ module Mihari
37
33
  private
38
34
 
39
35
  def valid_type?
40
- %w(ipv4 websites certificates).include? type
36
+ %w[ipv4 websites certificates].include? type
41
37
  end
42
38
 
43
39
  def normalize(domain)
@@ -86,7 +82,7 @@ module Mihari
86
82
  end
87
83
 
88
84
  def config_keys
89
- %w(censys_id censys_secret)
85
+ %w[censys_id censys_secret]
90
86
  end
91
87
 
92
88
  def api
@@ -5,9 +5,7 @@ require "passive_circl"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class CIRCL < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :tags
8
+ attr_reader :title, :description, :tags
11
9
 
12
10
  def initialize(query, title: nil, description: nil, tags: [])
13
11
  super()
@@ -27,7 +25,7 @@ module Mihari
27
25
  private
28
26
 
29
27
  def config_keys
30
- %w(circl_passive_password circl_passive_username)
28
+ %w[circl_passive_password circl_passive_username]
31
29
  end
32
30
 
33
31
  def api
@@ -41,7 +39,7 @@ module Mihari
41
39
  when "hash"
42
40
  passive_ssl_lookup
43
41
  else
44
- raise InvalidInputError, "#{@query}(type: #{@type || 'unknown'}) is not supported."
42
+ raise InvalidInputError, "#{@query}(type: #{@type || "unknown"}) is not supported."
45
43
  end
46
44
  end
47
45
 
@@ -5,11 +5,7 @@ require "crtsh"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Crtsh < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
12
- attr_reader :exclude_expired
8
+ attr_reader :title, :description, :query, :tags, :exclude_expired
13
9
 
14
10
  def initialize(query, title: nil, description: nil, tags: [], exclude_expired: nil)
15
11
  super()
@@ -24,7 +20,7 @@ module Mihari
24
20
 
25
21
  def artifacts
26
22
  results = search
27
- name_values = results.map { |result| result.dig("name_value") }.compact
23
+ name_values = results.map { |result| result["name_value"] }.compact
28
24
  name_values.map(&:lines).flatten.uniq.map(&:chomp)
29
25
  end
30
26
 
@@ -5,10 +5,7 @@ require "dnpedia"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class DNPedia < Base
8
- attr_reader :query
9
- attr_reader :title
10
- attr_reader :description
11
- attr_reader :tags
8
+ attr_reader :query, :title, :description, :tags
12
9
 
13
10
  def initialize(query, title: nil, description: nil, tags: [])
14
11
  super()
@@ -31,9 +28,9 @@ module Mihari
31
28
 
32
29
  def lookup
33
30
  res = api.search(query)
34
- rows = res.dig("rows") || []
31
+ rows = res["rows"] || []
35
32
  rows.map do |row|
36
- [row.dig("name"), row.dig("zoneid")].join(".")
33
+ [row["name"], row["zoneid"]].join(".")
37
34
  end
38
35
  end
39
36
  end
@@ -7,12 +7,7 @@ require "parallel"
7
7
  module Mihari
8
8
  module Analyzers
9
9
  class DNSTwister < Base
10
- attr_reader :query
11
- attr_reader :type
12
-
13
- attr_reader :title
14
- attr_reader :description
15
- attr_reader :tags
10
+ attr_reader :query, :type, :title, :description, :tags
16
11
 
17
12
  def initialize(query, title: nil, description: nil, tags: [])
18
13
  super()
@@ -47,11 +42,11 @@ module Mihari
47
42
  end
48
43
 
49
44
  def lookup
50
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
45
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
51
46
 
52
47
  res = api.fuzz(query)
53
- fuzzy_domains = res.dig("fuzzy_domains") || []
54
- domains = fuzzy_domains.map { |domain| domain.dig("domain") }
48
+ fuzzy_domains = res["fuzzy_domains"] || []
49
+ domains = fuzzy_domains.map { |domain| domain["domain"] }
55
50
  Parallel.map(domains) do |domain|
56
51
  resolvable?(domain) ? domain : nil
57
52
  end.compact
@@ -5,15 +5,11 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class FreeText < Base
8
- attr_reader :query
9
-
10
- attr_reader :title
11
- attr_reader :description
12
- attr_reader :tags
8
+ attr_reader :query, :title, :description, :tags
13
9
 
14
10
  ANALYZERS = [
15
11
  Mihari::Analyzers::BinaryEdge,
16
- Mihari::Analyzers::Censys,
12
+ Mihari::Analyzers::Censys
17
13
  ].freeze
18
14
 
19
15
  def initialize(query, title: nil, description: nil, tags: [])
@@ -5,15 +5,7 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class HTTPHash < Base
8
- attr_reader :md5
9
- attr_reader :sha256
10
- attr_reader :mmh3
11
-
12
- attr_reader :html
13
-
14
- attr_reader :title
15
- attr_reader :description
16
- attr_reader :tags
8
+ attr_reader :md5, :sha256, :mmh3, :html, :title, :description, :tags
17
9
 
18
10
  def initialize(_query, md5: nil, sha256: nil, mmh3: nil, html: nil, title: nil, description: nil, tags: [])
19
11
  super()
@@ -57,7 +49,7 @@ module Mihari
57
49
  [
58
50
  md5 ? "md5:#{md5}" : nil,
59
51
  sha256 ? "sha256:#{sha256}" : nil,
60
- mmh3 ? "mmh3:#{mmh3}" : nil,
52
+ mmh3 ? "mmh3:#{mmh3}" : nil
61
53
  ].compact.join(",")
62
54
  end
63
55
 
@@ -92,7 +84,7 @@ module Mihari
92
84
  binary_edge,
93
85
  censys,
94
86
  onyphe,
95
- shodan,
87
+ shodan
96
88
  ].compact
97
89
  end
98
90
 
@@ -5,10 +5,7 @@ require "onyphe"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Onyphe < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
8
+ attr_reader :title, :description, :query, :tags
12
9
 
13
10
  def initialize(query, title: nil, description: nil, tags: [])
14
11
  super()
@@ -35,7 +32,7 @@ module Mihari
35
32
  PAGE_SIZE = 10
36
33
 
37
34
  def config_keys
38
- %w(onyphe_api_key)
35
+ %w[onyphe_api_key]
39
36
  end
40
37
 
41
38
  def api
@@ -51,7 +48,7 @@ module Mihari
51
48
  (1..Float::INFINITY).each do |page|
52
49
  res = search_with_page(query, page: page)
53
50
  responses << res
54
- total = res.dig("total").to_i
51
+ total = res["total"].to_i
55
52
  break if total <= page * PAGE_SIZE
56
53
  end
57
54
  responses
@@ -5,12 +5,7 @@ require "otx_ruby"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class OTX < Base
8
- attr_reader :query
9
- attr_reader :type
10
-
11
- attr_reader :title
12
- attr_reader :description
13
- attr_reader :tags
8
+ attr_reader :query, :type, :title, :description, :tags
14
9
 
15
10
  def initialize(query, title: nil, description: nil, tags: [])
16
11
  super()
@@ -30,7 +25,7 @@ module Mihari
30
25
  private
31
26
 
32
27
  def config_keys
33
- %w(otx_api_key)
28
+ %w[otx_api_key]
34
29
  end
35
30
 
36
31
  def domain_client
@@ -42,7 +37,7 @@ module Mihari
42
37
  end
43
38
 
44
39
  def valid_type?
45
- %w(ip domain).include? type
40
+ %w[ip domain].include? type
46
41
  end
47
42
 
48
43
  def lookup
@@ -52,7 +47,7 @@ module Mihari
52
47
  when "ip"
53
48
  ip_lookup
54
49
  else
55
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
50
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
56
51
  end
57
52
  end
58
53
 
@@ -5,12 +5,7 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class PassiveDNS < Base
8
- attr_reader :query
9
- attr_reader :type
10
-
11
- attr_reader :title
12
- attr_reader :description
13
- attr_reader :tags
8
+ attr_reader :query, :type, :title, :description, :tags
14
9
 
15
10
  ANALYZERS = [
16
11
  Mihari::Analyzers::CIRCL,
@@ -18,7 +13,7 @@ module Mihari
18
13
  Mihari::Analyzers::PassiveTotal,
19
14
  Mihari::Analyzers::Pulsedive,
20
15
  Mihari::Analyzers::SecurityTrails,
21
- Mihari::Analyzers::VirusTotal,
16
+ Mihari::Analyzers::VirusTotal
22
17
  ].freeze
23
18
 
24
19
  def initialize(query, title: nil, description: nil, tags: [])
@@ -41,11 +36,11 @@ module Mihari
41
36
  private
42
37
 
43
38
  def valid_type?
44
- %w(ip domain).include? type
39
+ %w[ip domain].include? type
45
40
  end
46
41
 
47
42
  def analyzers
48
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
43
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
49
44
 
50
45
  ANALYZERS.map do |klass|
51
46
  klass.new(query)
@@ -5,16 +5,11 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class PassiveSSL < Base
8
- attr_reader :query
9
- attr_reader :type
10
-
11
- attr_reader :title
12
- attr_reader :description
13
- attr_reader :tags
8
+ attr_reader :query, :type, :title, :description, :tags
14
9
 
15
10
  ANALYZERS = [
16
11
  Mihari::Analyzers::CIRCL,
17
- Mihari::Analyzers::PassiveTotal,
12
+ Mihari::Analyzers::PassiveTotal
18
13
  ].freeze
19
14
 
20
15
  def initialize(query, title: nil, description: nil, tags: [])
@@ -37,11 +32,11 @@ module Mihari
37
32
  private
38
33
 
39
34
  def valid_type?
40
- %w(sha1).include? type
35
+ %w[sha1].include? type
41
36
  end
42
37
 
43
38
  def analyzers
44
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
39
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
45
40
 
46
41
  ANALYZERS.map do |klass|
47
42
  klass.new(query)
@@ -5,12 +5,7 @@ require "passivetotal"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class PassiveTotal < Base
8
- attr_reader :query
9
- attr_reader :type
10
-
11
- attr_reader :title
12
- attr_reader :description
13
- attr_reader :tags
8
+ attr_reader :query, :type, :title, :description, :tags
14
9
 
15
10
  def initialize(query, title: nil, description: nil, tags: [])
16
11
  super()
@@ -30,7 +25,7 @@ module Mihari
30
25
  private
31
26
 
32
27
  def config_keys
33
- %w(passivetotal_username passivetotal_api_key)
28
+ %w[passivetotal_username passivetotal_api_key]
34
29
  end
35
30
 
36
31
  def api
@@ -38,7 +33,7 @@ module Mihari
38
33
  end
39
34
 
40
35
  def valid_type?
41
- %w(ip domain mail).include? type
36
+ %w[ip domain mail].include? type
42
37
  end
43
38
 
44
39
  def lookup
@@ -52,28 +47,28 @@ module Mihari
52
47
  when "hash"
53
48
  ssl_lookup
54
49
  else
55
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
50
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
56
51
  end
57
52
  end
58
53
 
59
54
  def passive_dns_lookup
60
55
  res = api.dns.passive_unique(query)
61
- res.dig("results") || []
56
+ res["results"] || []
62
57
  end
63
58
 
64
59
  def reverse_whois_lookup
65
60
  res = api.whois.search(query: query, field: "email")
66
- results = res.dig("results") || []
61
+ results = res["results"] || []
67
62
  results.map do |result|
68
- result.dig("domain")
63
+ result["domain"]
69
64
  end.flatten.compact.uniq
70
65
  end
71
66
 
72
67
  def ssl_lookup
73
68
  res = api.ssl.history(query)
74
- results = res.dig("results") || []
69
+ results = res["results"] || []
75
70
  results.map do |result|
76
- result.dig("ipAddresses")
71
+ result["ipAddresses"]
77
72
  end.flatten.compact.uniq
78
73
  end
79
74
  end