mihari 1.4.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  4. data/.github/workflows/test.yml +68 -0
  5. data/.standard.yml +4 -0
  6. data/README.md +22 -270
  7. data/Rakefile +1 -0
  8. data/build_frontend.sh +14 -0
  9. data/docker/Dockerfile +5 -3
  10. data/{screenshots → images}/alert.png +0 -0
  11. data/{screenshots → images}/eyecatch.png +0 -0
  12. data/images/logo.png +0 -0
  13. data/{screenshots → images}/misp.png +0 -0
  14. data/{screenshots → images}/slack.png +0 -0
  15. data/images/web_alerts.png +0 -0
  16. data/images/web_config.png +0 -0
  17. data/lib/mihari.rb +2 -2
  18. data/lib/mihari/analyzers/basic.rb +3 -4
  19. data/lib/mihari/analyzers/binaryedge.rb +4 -7
  20. data/lib/mihari/analyzers/censys.rb +3 -7
  21. data/lib/mihari/analyzers/circl.rb +3 -5
  22. data/lib/mihari/analyzers/crtsh.rb +2 -6
  23. data/lib/mihari/analyzers/dnpedia.rb +3 -6
  24. data/lib/mihari/analyzers/dnstwister.rb +4 -9
  25. data/lib/mihari/analyzers/free_text.rb +2 -6
  26. data/lib/mihari/analyzers/http_hash.rb +3 -11
  27. data/lib/mihari/analyzers/onyphe.rb +3 -6
  28. data/lib/mihari/analyzers/otx.rb +4 -9
  29. data/lib/mihari/analyzers/passive_dns.rb +4 -9
  30. data/lib/mihari/analyzers/passive_ssl.rb +4 -9
  31. data/lib/mihari/analyzers/passivetotal.rb +9 -14
  32. data/lib/mihari/analyzers/pulsedive.rb +7 -12
  33. data/lib/mihari/analyzers/reverse_whois.rb +4 -9
  34. data/lib/mihari/analyzers/securitytrails.rb +12 -17
  35. data/lib/mihari/analyzers/securitytrails_domain_feed.rb +3 -7
  36. data/lib/mihari/analyzers/shodan.rb +9 -8
  37. data/lib/mihari/analyzers/spyse.rb +6 -11
  38. data/lib/mihari/analyzers/ssh_fingerprint.rb +2 -6
  39. data/lib/mihari/analyzers/urlscan.rb +4 -12
  40. data/lib/mihari/analyzers/virustotal.rb +6 -11
  41. data/lib/mihari/analyzers/zoomeye.rb +7 -11
  42. data/lib/mihari/cli.rb +62 -299
  43. data/lib/mihari/commands/binaryedge.rb +21 -0
  44. data/lib/mihari/commands/censys.rb +22 -0
  45. data/lib/mihari/commands/circl.rb +21 -0
  46. data/lib/mihari/commands/config.rb +21 -0
  47. data/lib/mihari/commands/crtsh.rb +22 -0
  48. data/lib/mihari/commands/dnpedia.rb +21 -0
  49. data/lib/mihari/commands/dnstwister.rb +19 -0
  50. data/lib/mihari/commands/free_text.rb +21 -0
  51. data/lib/mihari/commands/http_hash.rb +25 -0
  52. data/lib/mihari/commands/json.rb +36 -0
  53. data/lib/mihari/commands/onyphe.rb +19 -0
  54. data/lib/mihari/commands/otx.rb +21 -0
  55. data/lib/mihari/commands/passive_dns.rb +19 -0
  56. data/lib/mihari/commands/passive_ssl.rb +21 -0
  57. data/lib/mihari/commands/passivetotal.rb +21 -0
  58. data/lib/mihari/commands/pulsedive.rb +21 -0
  59. data/lib/mihari/commands/reverse_whois.rb +21 -0
  60. data/lib/mihari/commands/securitytrails.rb +20 -0
  61. data/lib/mihari/commands/securitytrails_domain_feed.rb +23 -0
  62. data/lib/mihari/commands/shodan.rb +19 -0
  63. data/lib/mihari/commands/spyse.rb +20 -0
  64. data/lib/mihari/commands/ssh_fingerprint.rb +21 -0
  65. data/lib/mihari/commands/urlscan.rb +23 -0
  66. data/lib/mihari/commands/virustotal.rb +19 -0
  67. data/lib/mihari/commands/web.rb +20 -0
  68. data/lib/mihari/commands/zoomeye.rb +20 -0
  69. data/lib/mihari/config.rb +13 -25
  70. data/lib/mihari/configurable.rb +4 -5
  71. data/lib/mihari/database.rb +7 -1
  72. data/lib/mihari/emitters/misp.rb +4 -2
  73. data/lib/mihari/emitters/slack.rb +18 -7
  74. data/lib/mihari/emitters/the_hive.rb +1 -1
  75. data/lib/mihari/errors.rb +2 -0
  76. data/lib/mihari/models/alert.rb +51 -0
  77. data/lib/mihari/models/artifact.rb +1 -1
  78. data/lib/mihari/notifiers/exception_notifier.rb +1 -1
  79. data/lib/mihari/serializers/alert.rb +1 -1
  80. data/lib/mihari/serializers/artifact.rb +1 -1
  81. data/lib/mihari/serializers/tag.rb +1 -1
  82. data/lib/mihari/status.rb +10 -10
  83. data/lib/mihari/type_checker.rb +4 -4
  84. data/lib/mihari/version.rb +1 -1
  85. data/lib/mihari/web/app.rb +166 -0
  86. data/lib/mihari/web/public/index.html +21 -0
  87. data/lib/mihari/web/public/static/favicon.ico +0 -0
  88. data/lib/mihari/web/public/static/fonts/fa-brands-400.099a9556.woff +0 -0
  89. data/lib/mihari/web/public/static/fonts/fa-brands-400.30cc681d.eot +0 -0
  90. data/lib/mihari/web/public/static/fonts/fa-brands-400.3b89dd10.ttf +0 -0
  91. data/lib/mihari/web/public/static/fonts/fa-brands-400.f7307680.woff2 +0 -0
  92. data/lib/mihari/web/public/static/fonts/fa-regular-400.1f77739c.ttf +0 -0
  93. data/lib/mihari/web/public/static/fonts/fa-regular-400.7124eb50.woff +0 -0
  94. data/lib/mihari/web/public/static/fonts/fa-regular-400.7630483d.eot +0 -0
  95. data/lib/mihari/web/public/static/fonts/fa-regular-400.f0f82301.woff2 +0 -0
  96. data/lib/mihari/web/public/static/fonts/fa-solid-900.1042e8ca.eot +0 -0
  97. data/lib/mihari/web/public/static/fonts/fa-solid-900.605ed792.ttf +0 -0
  98. data/lib/mihari/web/public/static/fonts/fa-solid-900.9fe5a17c.woff +0 -0
  99. data/lib/mihari/web/public/static/fonts/fa-solid-900.e8a427e1.woff2 +0 -0
  100. data/lib/mihari/web/public/static/img/fa-brands-400.ba7ed552.svg +3717 -0
  101. data/lib/mihari/web/public/static/img/fa-regular-400.0bb42845.svg +801 -0
  102. data/lib/mihari/web/public/static/img/fa-solid-900.376c1f97.svg +5034 -0
  103. data/lib/mihari/web/public/static/js/app.280cbdb7.js +12 -0
  104. data/lib/mihari/web/public/static/js/app.280cbdb7.js.map +1 -0
  105. data/mihari.gemspec +31 -24
  106. metadata +213 -49
  107. data/.travis.yml +0 -13
  108. data/lib/mihari/alert_viewer.rb +0 -23
@@ -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
@@ -5,12 +5,7 @@ require "pulsedive"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Pulsedive < 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(pulsedive_api_key)
28
+ %w[pulsedive_api_key]
34
29
  end
35
30
 
36
31
  def api
@@ -38,18 +33,18 @@ module Mihari
38
33
  end
39
34
 
40
35
  def valid_type?
41
- %w(ip domain).include? type
36
+ %w[ip domain].include? type
42
37
  end
43
38
 
44
39
  def lookup
45
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
40
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
46
41
 
47
42
  indicator = api.indicator.get_by_value(query)
48
- iid = indicator.dig("iid")
43
+ iid = indicator["iid"]
49
44
 
50
45
  properties = api.indicator.get_properties_by_id(iid)
51
- (properties.dig("dns") || []).map do |property|
52
- property.dig("value") if ["A", "PTR"].include?(property.dig("name"))
46
+ (properties["dns"] || []).map do |property|
47
+ property["value"] if ["A", "PTR"].include?(property["name"])
53
48
  end.compact
54
49
  end
55
50
  end
@@ -5,16 +5,11 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class ReveseWhois < 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::PassiveTotal,
17
- Mihari::Analyzers::SecurityTrails,
12
+ Mihari::Analyzers::SecurityTrails
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(mail).include? type
35
+ %w[mail].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 "securitytrails"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class SecurityTrails < 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(securitytrails_api_key)
28
+ %w[securitytrails_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
@@ -50,28 +45,28 @@ module Mihari
50
45
  when "mail"
51
46
  mail_lookup
52
47
  else
53
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
48
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
54
49
  end
55
50
  end
56
51
 
57
52
  def domain_lookup
58
53
  result = api.history.get_all_dns_history(query, type: "a")
59
- records = result.dig("records") || []
54
+ records = result["records"] || []
60
55
  records.map do |record|
61
- (record.dig("values") || []).map { |value| value.dig("ip") }
56
+ (record["values"] || []).map { |value| value["ip"] }
62
57
  end.flatten.compact.uniq
63
58
  end
64
59
 
65
60
  def ip_lookup
66
- result = api.domains.search( filter: { ipv4: query })
67
- records = result.dig("records") || []
68
- records.map { |record| record.dig("hostname") }.compact.uniq
61
+ result = api.domains.search(filter: { ipv4: query })
62
+ records = result["records"] || []
63
+ records.map { |record| record["hostname"] }.compact.uniq
69
64
  end
70
65
 
71
66
  def mail_lookup
72
- result = api.domains.search( filter: { whois_email: query })
73
- records = result.dig("records") || []
74
- records.map { |record| record.dig("hostname") }.compact.uniq
67
+ result = api.domains.search(filter: { whois_email: query })
68
+ records = result["records"] || []
69
+ records.map { |record| record["hostname"] }.compact.uniq
75
70
  end
76
71
  end
77
72
  end
@@ -5,11 +5,7 @@ require "securitytrails"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class SecurityTrailsDomainFeed < Base
8
- attr_reader :type
9
-
10
- attr_reader :title
11
- attr_reader :description
12
- attr_reader :tags
8
+ attr_reader :type, :title, :description, :tags
13
9
 
14
10
  def initialize(regexp, type: "registered", title: nil, description: nil, tags: [])
15
11
  super()
@@ -32,7 +28,7 @@ module Mihari
32
28
  private
33
29
 
34
30
  def config_keys
35
- %w(securitytrails_api_key)
31
+ %w[securitytrails_api_key]
36
32
  end
37
33
 
38
34
  def api
@@ -40,7 +36,7 @@ module Mihari
40
36
  end
41
37
 
42
38
  def valid_type?
43
- %w(all new registered).include? type
39
+ %w[all new registered].include? type
44
40
  end
45
41
 
46
42
  def regexp
@@ -5,10 +5,7 @@ require "shodan"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Shodan < 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,9 +21,9 @@ module Mihari
24
21
  return [] unless results || results.empty?
25
22
 
26
23
  results.map do |result|
27
- matches = result.dig("matches") || []
24
+ matches = result["matches"] || []
28
25
  matches.map do |match|
29
- match.dig "ip_str"
26
+ match["ip_str"]
30
27
  end.compact
31
28
  end.flatten.compact.uniq
32
29
  end
@@ -36,7 +33,7 @@ module Mihari
36
33
  PAGE_SIZE = 100
37
34
 
38
35
  def config_keys
39
- %w(shodan_api_key)
36
+ %w[shodan_api_key]
40
37
  end
41
38
 
42
39
  def api
@@ -58,7 +55,11 @@ module Mihari
58
55
  break unless res
59
56
 
60
57
  responses << res
61
- break if res.dig("total").to_i <= page * PAGE_SIZE
58
+ break if res["total"].to_i <= page * PAGE_SIZE
59
+ rescue JSON::ParserError
60
+ # ignore JSON::ParserError
61
+ # ref. https://github.com/ninoseki/mihari/issues/197
62
+ next
62
63
  end
63
64
  responses
64
65
  end
@@ -6,12 +6,7 @@ require "json"
6
6
  module Mihari
7
7
  module Analyzers
8
8
  class Spyse < Base
9
- attr_reader :query
10
- attr_reader :type
11
-
12
- attr_reader :title
13
- attr_reader :description
14
- attr_reader :tags
9
+ attr_reader :query, :type, :title, :description, :tags
15
10
 
16
11
  def initialize(query, title: nil, description: nil, tags: [], type: "domain")
17
12
  super()
@@ -35,7 +30,7 @@ module Mihari
35
30
  end
36
31
 
37
32
  def config_keys
38
- %w(spyse_api_key)
33
+ %w[spyse_api_key]
39
34
  end
40
35
 
41
36
  def api
@@ -43,14 +38,14 @@ module Mihari
43
38
  end
44
39
 
45
40
  def valid_type?
46
- %w(ip domain cert).include? type
41
+ %w[ip domain cert].include? type
47
42
  end
48
43
 
49
44
  def domain_lookup
50
45
  res = api.domain.search(search_params, limit: 100)
51
46
  items = res.dig("data", "items") || []
52
47
  items.map do |item|
53
- item.dig("name")
48
+ item["name"]
54
49
  end.uniq.compact
55
50
  end
56
51
 
@@ -58,7 +53,7 @@ module Mihari
58
53
  res = api.ip.search(search_params, limit: 100)
59
54
  items = res.dig("data", "items") || []
60
55
  items.map do |item|
61
- item.dig("ip")
56
+ item["ip"]
62
57
  end.uniq.compact
63
58
  end
64
59
 
@@ -69,7 +64,7 @@ module Mihari
69
64
  when "ip"
70
65
  ip_lookup
71
66
  else
72
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
67
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
73
68
  end
74
69
  end
75
70
  end
@@ -5,11 +5,7 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class SSHFingerprint < Base
8
- attr_reader :fingerprint
9
-
10
- attr_reader :title
11
- attr_reader :description
12
- attr_reader :tags
8
+ attr_reader :fingerprint, :title, :description, :tags
13
9
 
14
10
  def initialize(fingerprint, title: nil, description: nil, tags: [])
15
11
  super()
@@ -46,7 +42,7 @@ module Mihari
46
42
 
47
43
  [
48
44
  binary_edge,
49
- shodan,
45
+ shodan
50
46
  ].compact
51
47
  end
52
48
 
@@ -5,15 +5,7 @@ require "urlscan"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Urlscan < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
12
-
13
- attr_reader :filter
14
- attr_reader :target_type
15
- attr_reader :use_pro
16
- attr_reader :use_similarity
8
+ attr_reader :title, :description, :query, :tags, :filter, :target_type, :use_pro, :use_similarity
17
9
 
18
10
  def initialize(
19
11
  query,
@@ -44,7 +36,7 @@ module Mihari
44
36
  result = search
45
37
  return [] unless result
46
38
 
47
- results = result.dig("results") || []
39
+ results = result["results"] || []
48
40
  results.map do |match|
49
41
  match.dig "page", target_type
50
42
  end.compact.uniq
@@ -53,7 +45,7 @@ module Mihari
53
45
  private
54
46
 
55
47
  def config_keys
56
- %w(urlscan_api_key)
48
+ %w[urlscan_api_key]
57
49
  end
58
50
 
59
51
  def api
@@ -68,7 +60,7 @@ module Mihari
68
60
  end
69
61
 
70
62
  def valid_target_type?
71
- %w(url domain ip).include? target_type
63
+ %w[url domain ip].include? target_type
72
64
  end
73
65
  end
74
66
  end
@@ -5,12 +5,7 @@ require "virustotal"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class VirusTotal < Base
8
- attr_reader :indicator
9
- attr_reader :type
10
-
11
- attr_reader :title
12
- attr_reader :description
13
- attr_reader :tags
8
+ attr_reader :indicator, :type, :title, :description, :tags
14
9
 
15
10
  def initialize(indicator, 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(virustotal_api_key)
28
+ %w[virustotal_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).include? type
36
+ %w[ip domain].include? type
42
37
  end
43
38
 
44
39
  def lookup
@@ -48,14 +43,14 @@ module Mihari
48
43
  when "ip"
49
44
  ip_lookup
50
45
  else
51
- raise InvalidInputError, "#{indicator}(type: #{type || 'unknown'}) is not supported." unless valid_type?
46
+ raise InvalidInputError, "#{indicator}(type: #{type || "unknown"}) is not supported." unless valid_type?
52
47
  end
53
48
  end
54
49
 
55
50
  def domain_lookup
56
51
  res = api.domain.resolutions(indicator)
57
52
 
58
- data = res.dig("data") || []
53
+ data = res["data"] || []
59
54
  data.map do |item|
60
55
  item.dig("attributes", "ip_address")
61
56
  end.compact.uniq
@@ -64,7 +59,7 @@ module Mihari
64
59
  def ip_lookup
65
60
  res = api.ip_address.resolutions(indicator)
66
61
 
67
- data = res.dig("data") || []
62
+ data = res["data"] || []
68
63
  data.map do |item|
69
64
  item.dig("attributes", "host_name")
70
65
  end.compact.uniq