mihari 3.10.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docker/Dockerfile +1 -1
  4. data/lib/mihari/analyzers/base.rb +7 -7
  5. data/lib/mihari/analyzers/binaryedge.rb +8 -5
  6. data/lib/mihari/analyzers/censys.rb +6 -3
  7. data/lib/mihari/analyzers/circl.rb +0 -3
  8. data/lib/mihari/analyzers/crtsh.rb +7 -5
  9. data/lib/mihari/analyzers/dnpedia.rb +4 -4
  10. data/lib/mihari/analyzers/dnstwister.rb +1 -4
  11. data/lib/mihari/analyzers/feed.rb +36 -0
  12. data/lib/mihari/analyzers/greynoise.rb +1 -3
  13. data/lib/mihari/analyzers/onyphe.rb +6 -3
  14. data/lib/mihari/analyzers/otx.rb +0 -3
  15. data/lib/mihari/analyzers/passivetotal.rb +8 -9
  16. data/lib/mihari/analyzers/pulsedive.rb +7 -5
  17. data/lib/mihari/analyzers/rule.rb +11 -5
  18. data/lib/mihari/analyzers/securitytrails.rb +10 -7
  19. data/lib/mihari/analyzers/shodan.rb +15 -8
  20. data/lib/mihari/analyzers/spyse.rb +10 -11
  21. data/lib/mihari/analyzers/urlscan.rb +39 -16
  22. data/lib/mihari/analyzers/virustotal.rb +8 -9
  23. data/lib/mihari/analyzers/virustotal_intelligence.rb +9 -5
  24. data/lib/mihari/analyzers/zoomeye.rb +12 -5
  25. data/lib/mihari/cli/base.rb +0 -5
  26. data/lib/mihari/cli/init.rb +0 -2
  27. data/lib/mihari/cli/main.rb +4 -6
  28. data/lib/mihari/cli/mixins/utils.rb +2 -18
  29. data/lib/mihari/commands/init.rb +0 -18
  30. data/lib/mihari/commands/search.rb +20 -15
  31. data/lib/mihari/commands/validator.rb +7 -19
  32. data/lib/mihari/commands/web.rb +0 -3
  33. data/lib/mihari/database.rb +71 -19
  34. data/lib/mihari/emitters/misp.rb +0 -1
  35. data/lib/mihari/emitters/slack.rb +3 -4
  36. data/lib/mihari/emitters/stdout.rb +0 -2
  37. data/lib/mihari/emitters/the_hive.rb +0 -1
  38. data/lib/mihari/emitters/webhook.rb +1 -5
  39. data/lib/mihari/enrichers/ipinfo.rb +2 -2
  40. data/lib/mihari/errors.rb +6 -0
  41. data/lib/mihari/feed/parser.rb +34 -0
  42. data/lib/mihari/feed/reader.rb +127 -0
  43. data/lib/mihari/mixins/autonomous_system.rb +2 -0
  44. data/lib/mihari/mixins/database.rb +14 -0
  45. data/lib/mihari/mixins/disallowed_data_value.rb +2 -3
  46. data/lib/mihari/mixins/rule.rb +36 -31
  47. data/lib/mihari/models/alert.rb +7 -8
  48. data/lib/mihari/models/artifact.rb +1 -7
  49. data/lib/mihari/models/autonomous_system.rb +0 -2
  50. data/lib/mihari/models/dns.rb +2 -5
  51. data/lib/mihari/models/geolocation.rb +0 -1
  52. data/lib/mihari/models/reverse_dns.rb +0 -3
  53. data/lib/mihari/models/rule.rb +73 -0
  54. data/lib/mihari/models/tag.rb +0 -2
  55. data/lib/mihari/models/tagging.rb +0 -2
  56. data/lib/mihari/models/whois.rb +0 -2
  57. data/lib/mihari/notifiers/exception_notifier.rb +0 -2
  58. data/lib/mihari/schemas/analyzer.rb +0 -5
  59. data/lib/mihari/schemas/macros.rb +0 -2
  60. data/lib/mihari/schemas/rule.rb +21 -7
  61. data/lib/mihari/status.rb +2 -2
  62. data/lib/mihari/structs/alert.rb +2 -3
  63. data/lib/mihari/structs/censys.rb +4 -3
  64. data/lib/mihari/structs/greynoise.rb +4 -3
  65. data/lib/mihari/structs/ipinfo.rb +1 -2
  66. data/lib/mihari/structs/onyphe.rb +6 -5
  67. data/lib/mihari/structs/rule.rb +121 -0
  68. data/lib/mihari/structs/shodan.rb +8 -7
  69. data/lib/mihari/structs/urlscan.rb +50 -0
  70. data/lib/mihari/structs/virustotal_intelligence.rb +4 -3
  71. data/lib/mihari/type_checker.rb +2 -6
  72. data/lib/mihari/types.rb +8 -1
  73. data/lib/mihari/version.rb +1 -1
  74. data/lib/mihari/web/api.rb +6 -0
  75. data/lib/mihari/web/app.rb +5 -7
  76. data/lib/mihari/web/endpoints/alerts.rb +7 -3
  77. data/lib/mihari/web/endpoints/artifacts.rb +6 -3
  78. data/lib/mihari/web/endpoints/command.rb +2 -1
  79. data/lib/mihari/web/endpoints/configs.rb +2 -1
  80. data/lib/mihari/web/endpoints/ip_addresses.rb +2 -1
  81. data/lib/mihari/web/endpoints/rules.rb +140 -0
  82. data/lib/mihari/web/endpoints/sources.rb +2 -1
  83. data/lib/mihari/web/endpoints/tags.rb +4 -2
  84. data/lib/mihari/web/entities/artifact.rb +4 -2
  85. data/lib/mihari/web/entities/rule.rb +35 -0
  86. data/lib/mihari/web/entities/whois.rb +1 -1
  87. data/lib/mihari/web/middleware/connection_adapter.rb +19 -0
  88. data/lib/mihari/web/public/index.html +1 -1
  89. data/lib/mihari/web/public/redoc-static.html +196 -184
  90. data/lib/mihari/web/public/static/js/app.49ab738a.js +21 -0
  91. data/lib/mihari/web/public/static/js/app.49ab738a.js.map +1 -0
  92. data/lib/mihari/web/public/static/js/app.5dc97aae.js +21 -0
  93. data/lib/mihari/web/public/static/js/app.5dc97aae.js.map +1 -0
  94. data/lib/mihari/web/public/static/js/app.f2b8890f.js +21 -0
  95. data/lib/mihari/web/public/static/js/app.f2b8890f.js.map +1 -0
  96. data/lib/mihari/web/public/static/js/app.fbc19869.js +21 -0
  97. data/lib/mihari/web/public/static/js/app.fbc19869.js.map +1 -0
  98. data/lib/mihari.rb +42 -34
  99. data/mihari.gemspec +21 -23
  100. data/sig/lib/mihari/analyzers/binaryedge.rbs +2 -3
  101. data/sig/lib/mihari/analyzers/censys.rbs +2 -3
  102. data/sig/lib/mihari/analyzers/circl.rbs +1 -3
  103. data/sig/lib/mihari/analyzers/crtsh.rbs +1 -3
  104. data/sig/lib/mihari/analyzers/dnpedia.rbs +1 -4
  105. data/sig/lib/mihari/analyzers/dnstwister.rbs +1 -3
  106. data/sig/lib/mihari/analyzers/feed.rbs +20 -0
  107. data/sig/lib/mihari/analyzers/onyphe.rbs +2 -3
  108. data/sig/lib/mihari/analyzers/otx.rbs +1 -3
  109. data/sig/lib/mihari/analyzers/passivetotal.rbs +3 -5
  110. data/sig/lib/mihari/analyzers/pulsedive.rbs +2 -4
  111. data/sig/lib/mihari/analyzers/securitytrails.rbs +3 -5
  112. data/sig/lib/mihari/analyzers/shodan.rbs +2 -3
  113. data/sig/lib/mihari/analyzers/spyse.rbs +4 -6
  114. data/sig/lib/mihari/analyzers/urlscan.rbs +6 -5
  115. data/sig/lib/mihari/analyzers/virustotal.rbs +4 -6
  116. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +2 -3
  117. data/sig/lib/mihari/analyzers/zoomeye.rbs +4 -4
  118. data/sig/lib/mihari/commands/init.rbs +0 -2
  119. data/sig/lib/mihari/commands/validator.rbs +0 -2
  120. data/sig/lib/mihari/emitters/slack.rbs +0 -1
  121. data/sig/lib/mihari/feed/parser.rbs +11 -0
  122. data/sig/lib/mihari/feed/reader.rbs +56 -0
  123. data/sig/lib/mihari/mixins/disallowed_data_value.rbs +0 -2
  124. data/sig/lib/mihari/mixins/rule.rbs +5 -12
  125. data/sig/lib/mihari/models/alert.rbs +1 -1
  126. data/sig/lib/mihari/models/artifact.rbs +2 -0
  127. data/sig/lib/mihari/models/rule.rbs +14 -0
  128. data/sig/lib/mihari/structs/alert.rbs +1 -1
  129. data/sig/lib/mihari/structs/greynoise.rbs +3 -3
  130. data/sig/lib/mihari/structs/rule.rbs +56 -0
  131. data/sig/lib/mihari/structs/shodan.rbs +2 -2
  132. data/sig/lib/mihari/structs/urlscan.rbs +28 -0
  133. data/sig/lib/mihari/types.rbs +4 -0
  134. data/sig/lib/mihari.rbs +0 -2
  135. metadata +102 -147
  136. data/lib/mihari/cli/analyzer.rb +0 -52
  137. data/lib/mihari/commands/binaryedge.rb +0 -21
  138. data/lib/mihari/commands/censys.rb +0 -22
  139. data/lib/mihari/commands/circl.rb +0 -21
  140. data/lib/mihari/commands/crtsh.rb +0 -22
  141. data/lib/mihari/commands/dnpedia.rb +0 -21
  142. data/lib/mihari/commands/dnstwister.rb +0 -21
  143. data/lib/mihari/commands/greynoise.rb +0 -21
  144. data/lib/mihari/commands/json.rb +0 -42
  145. data/lib/mihari/commands/onyphe.rb +0 -21
  146. data/lib/mihari/commands/otx.rb +0 -21
  147. data/lib/mihari/commands/passivetotal.rb +0 -22
  148. data/lib/mihari/commands/pulsedive.rb +0 -21
  149. data/lib/mihari/commands/securitytrails.rb +0 -22
  150. data/lib/mihari/commands/shodan.rb +0 -21
  151. data/lib/mihari/commands/spyse.rb +0 -22
  152. data/lib/mihari/commands/urlscan.rb +0 -23
  153. data/lib/mihari/commands/virustotal.rb +0 -22
  154. data/lib/mihari/commands/virustotal_intelligence.rb +0 -22
  155. data/lib/mihari/commands/zoomeye.rb +0 -22
  156. data/lib/mihari/mixins/configuration.rb +0 -100
  157. data/lib/mihari/mixins/hash.rb +0 -20
  158. data/lib/mihari/schemas/configuration.rb +0 -44
  159. data/lib/mihari/web/public/grape.rb +0 -73
  160. data/sig/lib/mihari/cli/analyzer.rbs +0 -39
  161. data/sig/lib/mihari/commands/binaryedge.rbs +0 -7
  162. data/sig/lib/mihari/commands/censys.rbs +0 -7
  163. data/sig/lib/mihari/commands/circl.rbs +0 -7
  164. data/sig/lib/mihari/commands/crtsh.rbs +0 -7
  165. data/sig/lib/mihari/commands/dnpedia.rbs +0 -7
  166. data/sig/lib/mihari/commands/dnstwister.rbs +0 -7
  167. data/sig/lib/mihari/commands/onyphe.rbs +0 -7
  168. data/sig/lib/mihari/commands/otx.rbs +0 -7
  169. data/sig/lib/mihari/commands/passivetotal.rbs +0 -7
  170. data/sig/lib/mihari/commands/pulsedive.rbs +0 -7
  171. data/sig/lib/mihari/commands/securitytrails.rbs +0 -7
  172. data/sig/lib/mihari/commands/shodan.rbs +0 -7
  173. data/sig/lib/mihari/commands/spyse.rbs +0 -7
  174. data/sig/lib/mihari/commands/urlscan.rbs +0 -7
  175. data/sig/lib/mihari/commands/virustotal.rbs +0 -7
  176. data/sig/lib/mihari/commands/zoomeye.rbs +0 -7
@@ -2,17 +2,17 @@
2
2
 
3
3
  require "urlscan"
4
4
 
5
- SUPPORTED_DATA_TYPES = %w[url domain ip].freeze
6
-
7
5
  module Mihari
8
6
  module Analyzers
9
7
  class Urlscan < Base
10
8
  param :query
11
- option :title, default: proc { "urlscan search" }
12
- option :description, default: proc { "query = #{query}" }
13
- option :tags, default: proc { [] }
9
+
14
10
  option :allowed_data_types, default: proc { SUPPORTED_DATA_TYPES }
15
- option :use_similarity, default: proc { false }
11
+
12
+ option :interval, default: proc { 0 }
13
+
14
+ SUPPORTED_DATA_TYPES = %w[url domain ip].freeze
15
+ SIZE = 1000
16
16
 
17
17
  def initialize(*args, **kwargs)
18
18
  super
@@ -21,15 +21,15 @@ module Mihari
21
21
  end
22
22
 
23
23
  def artifacts
24
- result = search
25
- return [] unless result
26
-
27
- results = result["results"] || []
24
+ responses = search
25
+ results = responses.map(&:results).flatten
28
26
 
29
27
  allowed_data_types.map do |type|
30
- results.filter_map do |match|
31
- match.dig "page", type
32
- end.uniq
28
+ results.filter_map do |result|
29
+ page = result.page
30
+ data = page.send(type.to_sym)
31
+ data.nil? ? nil : Artifact.new(data: data, source: source, metadata: result)
32
+ end
33
33
  end.flatten
34
34
  end
35
35
 
@@ -43,15 +43,38 @@ module Mihari
43
43
  @api ||= ::UrlScan::API.new(Mihari.config.urlscan_api_key)
44
44
  end
45
45
 
46
+ #
47
+ # Search with search_after option
48
+ #
49
+ # @return [Structs::Urlscan::Response]
50
+ #
51
+ def search_with_search_after(search_after: nil)
52
+ res = api.search(query, size: SIZE, search_after: search_after)
53
+ Structs::Urlscan::Response.from_dynamic! res
54
+ end
55
+
46
56
  #
47
57
  # Search
48
58
  #
49
- # @return [Array<Hash>]
59
+ # @return [Array<Structs::Urlscan::Response>]
50
60
  #
51
61
  def search
52
- return api.pro.similar(query) if use_similarity
62
+ responses = []
63
+
64
+ search_after = nil
65
+ loop do
66
+ res = search_with_search_after(search_after: search_after)
67
+ responses << res
68
+
69
+ break if res.results.length < SIZE
70
+
71
+ search_after = res.results.last.sort.join(",")
72
+
73
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
74
+ sleep interval
75
+ end
53
76
 
54
- api.search(query, size: 10_000)
77
+ responses
55
78
  end
56
79
 
57
80
  #
@@ -8,9 +8,6 @@ module Mihari
8
8
  include Mixins::Refang
9
9
 
10
10
  param :query
11
- option :title, default: proc { "VirusTotal search" }
12
- option :description, default: proc { "query = #{query}" }
13
- option :tags, default: proc { [] }
14
11
 
15
12
  attr_reader :type
16
13
 
@@ -47,7 +44,7 @@ module Mihari
47
44
  #
48
45
  # Search
49
46
  #
50
- # @return [Array<String>]
47
+ # @return [Array<Mihari::Artifact>]
51
48
  #
52
49
  def search
53
50
  case type
@@ -63,28 +60,30 @@ module Mihari
63
60
  #
64
61
  # Domain search
65
62
  #
66
- # @return [Array<String>]
63
+ # @return [Array<Mihari::Artifact>]
67
64
  #
68
65
  def domain_search
69
66
  res = api.domain.resolutions(query)
70
67
 
71
68
  data = res["data"] || []
72
69
  data.filter_map do |item|
73
- item.dig("attributes", "ip_address")
74
- end.uniq
70
+ data = item.dig("attributes", "ip_address")
71
+ data.nil? ? nil : Artifact.new(data: data, source: source, metadata: item)
72
+ end
75
73
  end
76
74
 
77
75
  #
78
76
  # IP search
79
77
  #
80
- # @return [Array<String>]
78
+ # @return [Array<Mihari::Artifact>]
81
79
  #
82
80
  def ip_search
83
81
  res = api.ip_address.resolutions(query)
84
82
 
85
83
  data = res["data"] || []
86
84
  data.filter_map do |item|
87
- item.dig("attributes", "host_name")
85
+ data = item.dig("attributes", "host_name")
86
+ Artifact.new(data: data, source: source, metadata: item)
88
87
  end.uniq
89
88
  end
90
89
  end
@@ -6,9 +6,8 @@ module Mihari
6
6
  module Analyzers
7
7
  class VirusTotalIntelligence < Base
8
8
  param :query
9
- option :title, default: proc { "VirusTotal Intelligence search" }
10
- option :description, default: proc { "query = #{query}" }
11
- option :tags, default: proc { [] }
9
+
10
+ option :interval, default: proc { 0 }
12
11
 
13
12
  def initialize(*args, **kwargs)
14
13
  super
@@ -19,8 +18,10 @@ module Mihari
19
18
  def artifacts
20
19
  responses = search_witgh_cursor
21
20
  responses.map do |response|
22
- response.data.map(&:value)
23
- end.flatten.compact.uniq
21
+ response.data.map do |datum|
22
+ Artifact.new(data: datum.value, source: source, metadata: datum.metadata)
23
+ end
24
+ end.flatten
24
25
  end
25
26
 
26
27
  private
@@ -54,6 +55,9 @@ module Mihari
54
55
  break if response.meta.cursor.nil?
55
56
 
56
57
  cursor = response.meta.cursor
58
+
59
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
60
+ sleep interval
57
61
  end
58
62
 
59
63
  responses
@@ -6,11 +6,11 @@ module Mihari
6
6
  module Analyzers
7
7
  class ZoomEye < Base
8
8
  param :query
9
- option :title, default: proc { "ZoomEye search" }
10
- option :description, default: proc { "query = #{query}" }
11
- option :tags, default: proc { [] }
9
+
12
10
  option :type, default: proc { "host" }
13
11
 
12
+ option :interval, default: proc { 0 }
13
+
14
14
  def artifacts
15
15
  case type
16
16
  when "host"
@@ -48,13 +48,14 @@ module Mihari
48
48
  #
49
49
  # @param [Array<Hash>] responses
50
50
  #
51
- # @return [Array<String>]
51
+ # @return [Array<Mihari::Artifact>]
52
52
  #
53
53
  def convert_responses(responses)
54
54
  responses.map do |res|
55
55
  matches = res["matches"] || []
56
56
  matches.map do |match|
57
- match["ip"]
57
+ data = match["ip"]
58
+ Artifact.new(data: data, source: source, metadata: match)
58
59
  end
59
60
  end.flatten.compact.uniq
60
61
  end
@@ -87,6 +88,9 @@ module Mihari
87
88
  total = res["total"].to_i
88
89
  responses << res
89
90
  break if total <= page * PAGE_SIZE
91
+
92
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
93
+ sleep interval
90
94
  end
91
95
  convert_responses responses.compact
92
96
  end
@@ -119,6 +123,9 @@ module Mihari
119
123
  total = res["total"].to_i
120
124
  responses << res
121
125
  break if total <= page * PAGE_SIZE
126
+
127
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
128
+ sleep interval
122
129
  end
123
130
  convert_responses responses.compact
124
131
  end
@@ -1,15 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thor"
4
-
5
- require "mihari/mixins/hash"
6
-
7
3
  require "mihari/cli/mixins/utils"
8
4
 
9
5
  module Mihari
10
6
  module CLI
11
7
  class Base < Thor
12
- include Mihari::Mixins::Hash
13
8
  include Mixins::Utils
14
9
 
15
10
  class << self
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thor"
4
-
5
3
  require "mihari/commands/init"
6
4
 
7
5
  module Mihari
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "thor"
4
+
3
5
  # Commands
4
6
  require "mihari/commands/search"
5
7
  require "mihari/commands/web"
@@ -7,7 +9,6 @@ require "mihari/commands/web"
7
9
  # CLIs
8
10
  require "mihari/cli/base"
9
11
 
10
- require "mihari/cli/analyzer"
11
12
  require "mihari/cli/init"
12
13
  require "mihari/cli/validator"
13
14
 
@@ -17,13 +18,10 @@ module Mihari
17
18
  include Mihari::Commands::Search
18
19
  include Mihari::Commands::Web
19
20
 
20
- desc "analyze", "Sub commands to run an analyzer"
21
- subcommand "analyze", Analyzer
22
-
23
- desc "init", "Sub commands to initialize config & rule"
21
+ desc "init", "Sub commands to initialize a rule"
24
22
  subcommand "init", Initialization
25
23
 
26
- desc "validate", "Sub commands to validate format of config & rule"
24
+ desc "validate", "Sub commands to validate format of a rule"
27
25
  subcommand "validate", Validator
28
26
  end
29
27
  end
@@ -27,19 +27,6 @@ module Mihari
27
27
  %w[title description artifacts].all? { |key| json.key? key }
28
28
  end
29
29
 
30
- #
31
- # Load configuration and establish DB connection
32
- #
33
- # @return [Hash]
34
- #
35
- def load_configuration
36
- config = options["config"]
37
- return unless config
38
-
39
- Mihari.load_config_from_yaml config
40
- Database.connect
41
- end
42
-
43
30
  #
44
31
  # Run analyzer
45
32
  #
@@ -50,14 +37,12 @@ module Mihari
50
37
  # @return [nil]
51
38
  #
52
39
  def run_analyzer(analyzer_class, query:, options:)
53
- load_configuration
54
-
55
40
  # options = Thor::CoreExt::HashWithIndifferentAccess
56
41
  # ref. https://www.rubydoc.info/github/wycats/thor/Thor/CoreExt/HashWithIndifferentAccess
57
42
  # so need to covert it to a plain hash
58
43
  hash_options = options.to_hash
59
44
 
60
- hash_options = symbolize_hash(hash_options)
45
+ hash_options = hash_options.symbolize_keys
61
46
  hash_options = normalize_options(hash_options)
62
47
 
63
48
  analyzer = analyzer_class.new(query, **hash_options)
@@ -76,8 +61,7 @@ module Mihari
76
61
  # @return [Hash]
77
62
  #
78
63
  def normalize_options(options)
79
- # Delete :config because it is not intended to use for running an analyzer
80
- [:config, :ignore_old_artifacts, :ignore_threshold].each do |ignore_key|
64
+ [:ignore_old_artifacts, :ignore_threshold].each do |ignore_key|
81
65
  options.delete(ignore_key)
82
66
  end
83
67
  options
@@ -1,30 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "colorize"
4
-
5
3
  module Mihari
6
4
  module Commands
7
5
  module Initialization
8
- include Mixins::Configuration
9
6
  include Mixins::Rule
10
7
 
11
8
  def self.included(thor)
12
9
  thor.class_eval do
13
- desc "config", "Create a config file"
14
- method_option :filename, type: :string, default: "mihari.yml"
15
- def config
16
- filename = options["filename"]
17
-
18
- warning = "#{filename} exists. Do you want to overwrite it? (y/n)"
19
- if File.exist?(filename) && !(yes? warning)
20
- return
21
- end
22
-
23
- initialize_config_yaml filename
24
-
25
- puts "The config file is initialized as #{filename}.".colorize(:blue)
26
- end
27
-
28
10
  desc "rule", "Create a rule file"
29
11
  method_option :filename, type: :string, default: "rule.yml"
30
12
  def rule
@@ -3,22 +3,22 @@
3
3
  module Mihari
4
4
  module Commands
5
5
  module Search
6
+ include Mixins::Database
6
7
  include Mixins::Rule
7
8
 
8
9
  def self.included(thor)
9
10
  thor.class_eval do
10
11
  desc "search [RULE]", "Search by a rule"
11
- method_option :config, type: :string, desc: "Path to the config file"
12
- def search_by_rule(rule)
13
- # load configuration
14
- load_configuration
15
-
16
- # convert str(YAML) to hash or str(path/YAML file) to hash
17
- rule = load_rule(rule)
18
-
19
- # validate rule schema
20
- rule = validate_rule(rule)
12
+ def search_by_rule(path_or_id)
13
+ rule = load_rule(path_or_id)
14
+ # validate
15
+ begin
16
+ validate_rule! rule
17
+ rescue RuleValidationError => e
18
+ raise e
19
+ end
21
20
 
21
+ # build and run the analyzer
22
22
  analyzer = build_rule_analyzer(
23
23
  title: rule[:title],
24
24
  description: rule[:description],
@@ -26,8 +26,7 @@ module Mihari
26
26
  tags: rule[:tags],
27
27
  allowed_data_types: rule[:allowed_data_types],
28
28
  disallowed_data_values: rule[:disallowed_data_values],
29
- source: rule[:source],
30
- id: rule[:id]
29
+ id: rule.id
31
30
  )
32
31
 
33
32
  ignore_old_artifacts = rule[:ignore_old_artifacts]
@@ -35,6 +34,14 @@ module Mihari
35
34
 
36
35
  with_error_handling do
37
36
  run_rule_analyzer analyzer, ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold
37
+
38
+ # record a rule
39
+ with_db_connection do
40
+ model = rule.to_model
41
+ model.save
42
+ rescue ActiveRecord::RecordNotUnique
43
+ nil
44
+ end
38
45
  end
39
46
  end
40
47
  end
@@ -51,11 +58,10 @@ module Mihari
51
58
  # @param [Array<String>, nil] tags
52
59
  # @param [Array<String>, nil] allowed_data_types
53
60
  # @param [Array<String>, nil] disallowed_data_values
54
- # @param [String, nil] source
55
61
  #
56
62
  # @return [Mihari::Analyzers::Rule]
57
63
  #
58
- def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, source: nil, id: nil)
64
+ def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, id: nil)
59
65
  tags = [] if tags.nil?
60
66
  allowed_data_types = ALLOWED_DATA_TYPES if allowed_data_types.nil?
61
67
  disallowed_data_values = [] if disallowed_data_values.nil?
@@ -67,7 +73,6 @@ module Mihari
67
73
  queries: queries,
68
74
  allowed_data_types: allowed_data_types,
69
75
  disallowed_data_values: disallowed_data_values,
70
- source: source,
71
76
  id: id
72
77
  )
73
78
  end
@@ -4,32 +4,20 @@ module Mihari
4
4
  module Commands
5
5
  module Validator
6
6
  include Mixins::Rule
7
- include Mixins::Configuration
8
7
 
9
8
  def self.included(thor)
10
9
  thor.class_eval do
11
10
  desc "rule [PATH]", "Validate format of a rule file"
12
11
  def rule(path)
13
- # convert str(YAML) to hash or str(path/YAML file) to hash
14
12
  rule = load_rule(path)
15
13
 
16
- # validate rule schema
17
- validate_rule rule
18
-
19
- puts "Valid format. The input is parsed as the following:"
20
- puts rule.to_yaml
21
- end
22
-
23
- desc "config [PATH]", "Validate format of a config file"
24
- def config(path)
25
- # convert str(YAML) to hash or str(path/YAML file) to hash
26
- config = load_config(path)
27
-
28
- # validate config schema
29
- validate_config config
30
-
31
- puts "Valid format. The input is parsed as the following:"
32
- puts config.to_yaml
14
+ begin
15
+ validate_rule! rule
16
+ puts "Valid format. The input is parsed as the following:"
17
+ puts rule.data.to_yaml
18
+ rescue RuleValidationError
19
+ nil
20
+ end
33
21
  end
34
22
  end
35
23
  end
@@ -10,15 +10,12 @@ module Mihari
10
10
  method_option :host, type: :string, default: "localhost", desc: "Port to listen on"
11
11
  method_option :threads, type: :string, default: "0:16", desc: "min:max threads to use"
12
12
  method_option :verbose, type: :boolean, default: true, desc: "Report each request"
13
- method_option :config, type: :string, desc: "Path to the config file"
14
13
  def web
15
14
  port = options["port"]
16
15
  host = options["host"]
17
16
  threads = options["threads"]
18
17
  verbose = options["verbose"]
19
18
 
20
- load_configuration
21
-
22
19
  # set rack env as production
23
20
  ENV["RACK_ENV"] ||= "production"
24
21
 
@@ -1,8 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
3
+ def env
4
+ ENV["APP_ENV"] || ENV["RACK_ENV"]
5
+ end
6
+
7
+ def test_env?
8
+ env == "test"
9
+ end
4
10
 
5
- class InitialSchema < ActiveRecord::Migration[6.1]
11
+ def development_env?
12
+ env == "development"
13
+ end
14
+
15
+ class InitialSchema < ActiveRecord::Migration[7.0]
6
16
  def change
7
17
  create_table :tags, if_not_exists: true do |t|
8
18
  t.string :name, null: false
@@ -32,13 +42,13 @@ class InitialSchema < ActiveRecord::Migration[6.1]
32
42
  end
33
43
  end
34
44
 
35
- class AddeSourceToArtifactSchema < ActiveRecord::Migration[6.1]
45
+ class AddeSourceToArtifactSchema < ActiveRecord::Migration[7.0]
36
46
  def change
37
47
  add_column :artifacts, :source, :string, if_not_exists: true
38
48
  end
39
49
  end
40
50
 
41
- class EnrichmentsSchema < ActiveRecord::Migration[6.1]
51
+ class EnrichmentsSchema < ActiveRecord::Migration[7.0]
42
52
  def change
43
53
  create_table :autonomous_systems, if_not_exists: true do |t|
44
54
  t.integer :asn, null: false
@@ -74,7 +84,7 @@ class EnrichmentsSchema < ActiveRecord::Migration[6.1]
74
84
  end
75
85
  end
76
86
 
77
- class EnrichmentCreatedAtSchema < ActiveRecord::Migration[6.1]
87
+ class EnrichmentCreatedAtSchema < ActiveRecord::Migration[7.0]
78
88
  def change
79
89
  # Add created_at column because now it is able to enrich an atrifact after the creation
80
90
  add_column :autonomous_systems, :created_at, :datetime, if_not_exists: true
@@ -85,6 +95,23 @@ class EnrichmentCreatedAtSchema < ActiveRecord::Migration[6.1]
85
95
  end
86
96
  end
87
97
 
98
+ class RuleSchema < ActiveRecord::Migration[7.0]
99
+ def change
100
+ create_table :rules, id: :string, if_not_exists: true do |t|
101
+ t.string :title, null: false
102
+ t.string :description, null: false
103
+ t.json :data, null: false
104
+ t.timestamps
105
+ end
106
+ end
107
+ end
108
+
109
+ class AddeMetadataToArtifactSchema < ActiveRecord::Migration[7.0]
110
+ def change
111
+ add_column :artifacts, :metadata, :json, if_not_exists: true
112
+ end
113
+ end
114
+
88
115
  def adapter
89
116
  return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
90
117
  return "mysql2" if Mihari.config.database.start_with?("mysql2://")
@@ -95,7 +122,34 @@ end
95
122
  module Mihari
96
123
  class Database
97
124
  class << self
125
+ include Memist::Memoizable
126
+
127
+ #
128
+ # DB migraration
129
+ #
130
+ # @param [Symbol] direction
131
+ #
132
+ def migrate(direction)
133
+ ActiveRecord::Migration.verbose = false
134
+
135
+ [
136
+ InitialSchema,
137
+ AddeSourceToArtifactSchema,
138
+ EnrichmentsSchema,
139
+ EnrichmentCreatedAtSchema,
140
+ # v4
141
+ RuleSchema,
142
+ AddeMetadataToArtifactSchema
143
+ ].each { |schema| schema.migrate direction }
144
+ end
145
+ memoize :migrate unless test_env?
146
+
147
+ #
148
+ # Establish DB connection
149
+ #
98
150
  def connect
151
+ return if ActiveRecord::Base.connected?
152
+
99
153
  case adapter
100
154
  when "postgresql", "mysql2"
101
155
  ActiveRecord::Base.establish_connection(Mihari.config.database)
@@ -106,32 +160,30 @@ module Mihari
106
160
  )
107
161
  end
108
162
 
109
- ActiveRecord::Base.logger = Logger.new($stdout) if ENV["RACK_ENV"] == "development"
110
- ActiveRecord::Migration.verbose = false
163
+ ActiveRecord::Base.logger = Logger.new($stdout) if development_env?
111
164
 
112
- InitialSchema.migrate(:up)
113
- AddeSourceToArtifactSchema.migrate(:up)
114
- EnrichmentsSchema.migrate(:up)
115
- EnrichmentCreatedAtSchema.migrate(:up)
165
+ migrate :up
116
166
  rescue StandardError
117
167
  # Do nothing
118
168
  end
119
169
 
170
+ #
171
+ # Close DB connection(s)
172
+ #
120
173
  def close
121
- ActiveRecord::Base.clear_active_connections!
122
- ActiveRecord::Base.connection.close
174
+ return unless ActiveRecord::Base.connected?
175
+
176
+ ActiveRecord::Base.clear_all_connections!
123
177
  end
124
178
 
179
+ #
180
+ # Destory DB
181
+ #
125
182
  def destroy!
126
183
  return unless ActiveRecord::Base.connected?
127
184
 
128
- InitialSchema.migrate(:down)
129
- AddeSourceToArtifactSchema.migrate(:down)
130
- EnrichmentsSchema.migrate(:down)
131
- EnrichmentCreatedAtSchema.migrate(:down)
185
+ migrate :down
132
186
  end
133
187
  end
134
188
  end
135
189
  end
136
-
137
- Mihari::Database.connect
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "misp"
4
- require "net/ping"
5
4
 
6
5
  module Mihari
7
6
  module Emitters
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "slack-notifier"
4
3
  require "digest/sha2"
5
- require "dry-initializer"
4
+ require "slack-notifier"
6
5
 
7
6
  module Mihari
8
7
  module Emitters
9
8
  class Attachment
10
- include Mem
9
+ include Memist::Memoizable
11
10
 
12
11
  extend Dry::Initializer
13
12
 
@@ -63,7 +62,7 @@ module Mihari
63
62
  when "domain"
64
63
  "https://urlscan.io/domain/#{data}"
65
64
  when "url"
66
- uri = URI(data)
65
+ uri = Addressable::URI.parse(data)
67
66
  "https://urlscan.io/domain/#{uri.hostname}"
68
67
  end
69
68
  end