mihari 1.3.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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