mihari 4.12.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/Steepfile +0 -1
  3. data/lib/mihari/analyzers/base.rb +18 -37
  4. data/lib/mihari/analyzers/rule.rb +23 -36
  5. data/lib/mihari/cli/main.rb +6 -11
  6. data/lib/mihari/commands/initializer.rb +47 -0
  7. data/lib/mihari/commands/searcher.rb +57 -0
  8. data/lib/mihari/commands/validator.rb +2 -2
  9. data/lib/mihari/constants.rb +3 -3
  10. data/lib/mihari/database.rb +52 -87
  11. data/lib/mihari/emitters/database.rb +16 -7
  12. data/lib/mihari/emitters/misp.rb +13 -5
  13. data/lib/mihari/emitters/slack.rb +15 -8
  14. data/lib/mihari/emitters/the_hive.rb +42 -21
  15. data/lib/mihari/emitters/webhook.rb +99 -31
  16. data/lib/mihari/entities/alert.rb +7 -5
  17. data/lib/mihari/entities/artifact.rb +20 -8
  18. data/lib/mihari/entities/config.rb +2 -6
  19. data/lib/mihari/entities/rule.rb +8 -0
  20. data/lib/mihari/http.rb +13 -13
  21. data/lib/mihari/mixins/{disallowed_data_value.rb → falsepositive.rb} +8 -8
  22. data/lib/mihari/models/alert.rb +2 -15
  23. data/lib/mihari/models/artifact.rb +28 -17
  24. data/lib/mihari/models/rule.rb +7 -13
  25. data/lib/mihari/schemas/emitter.rb +6 -8
  26. data/lib/mihari/schemas/rule.rb +11 -13
  27. data/lib/mihari/structs/config.rb +41 -0
  28. data/lib/mihari/structs/filters.rb +2 -2
  29. data/lib/mihari/structs/rule.rb +94 -85
  30. data/lib/mihari/templates/rule.yml.erb +5 -23
  31. data/lib/mihari/types.rb +1 -1
  32. data/lib/mihari/version.rb +1 -1
  33. data/lib/mihari/web/api.rb +0 -2
  34. data/lib/mihari/web/endpoints/alerts.rb +11 -3
  35. data/lib/mihari/web/endpoints/configs.rb +1 -6
  36. data/lib/mihari/web/endpoints/rules.rb +27 -15
  37. data/lib/mihari/web/public/assets/{fa-brands-400-b1d1c1b0.ttf → fa-brands-400-2ef6fdde.ttf} +0 -0
  38. data/lib/mihari/web/public/assets/fa-brands-400-f4617423.woff2 +0 -0
  39. data/lib/mihari/web/public/assets/fa-regular-400-12dea17b.ttf +0 -0
  40. data/lib/mihari/web/public/assets/fa-regular-400-7ba24c41.woff2 +0 -0
  41. data/lib/mihari/web/public/assets/fa-solid-900-67a880b4.ttf +0 -0
  42. data/lib/mihari/web/public/assets/fa-solid-900-e2c5cf54.woff2 +0 -0
  43. data/lib/mihari/web/public/assets/fa-v4compatibility-7c377405.woff2 +0 -0
  44. data/lib/mihari/web/public/assets/fa-v4compatibility-8d9500e8.ttf +0 -0
  45. data/lib/mihari/web/public/assets/{index-07aa1ba2.css → index-625e95fe.css} +3 -3
  46. data/lib/mihari/web/public/assets/index-63900d73.js +50 -0
  47. data/lib/mihari/web/public/index.html +2 -2
  48. data/lib/mihari/web/public/redoc-static.html +26 -27
  49. data/lib/mihari.rb +11 -21
  50. data/mihari.gemspec +4 -4
  51. metadata +25 -111
  52. data/lib/mihari/cli/init.rb +0 -11
  53. data/lib/mihari/cli/validator.rb +0 -11
  54. data/lib/mihari/commands/init.rb +0 -51
  55. data/lib/mihari/commands/search.rb +0 -63
  56. data/lib/mihari/emitters/http.rb +0 -127
  57. data/lib/mihari/entities/source.rb +0 -9
  58. data/lib/mihari/status.rb +0 -55
  59. data/lib/mihari/web/endpoints/sources.rb +0 -19
  60. data/lib/mihari/web/public/assets/fa-brands-400-c61287c2.woff2 +0 -0
  61. data/lib/mihari/web/public/assets/fa-regular-400-5da313b0.woff2 +0 -0
  62. data/lib/mihari/web/public/assets/fa-regular-400-d7b19fe2.ttf +0 -0
  63. data/lib/mihari/web/public/assets/fa-solid-900-8f06540f.woff2 +0 -0
  64. data/lib/mihari/web/public/assets/fa-solid-900-e4f6a7e9.ttf +0 -0
  65. data/lib/mihari/web/public/assets/fa-v4compatibility-2ddb3b41.ttf +0 -0
  66. data/lib/mihari/web/public/assets/fa-v4compatibility-f46715c9.woff2 +0 -0
  67. data/lib/mihari/web/public/assets/index-a7fe697b.js +0 -63
  68. data/sig/lib/mihari/analyzers/base.rbs +0 -90
  69. data/sig/lib/mihari/analyzers/binaryedge.rbs +0 -26
  70. data/sig/lib/mihari/analyzers/censys.rbs +0 -41
  71. data/sig/lib/mihari/analyzers/circl.rbs +0 -31
  72. data/sig/lib/mihari/analyzers/crtsh.rbs +0 -17
  73. data/sig/lib/mihari/analyzers/dnpedia.rbs +0 -15
  74. data/sig/lib/mihari/analyzers/dnstwister.rbs +0 -25
  75. data/sig/lib/mihari/analyzers/feed.rbs +0 -20
  76. data/sig/lib/mihari/analyzers/onyphe.rbs +0 -34
  77. data/sig/lib/mihari/analyzers/otx.rbs +0 -33
  78. data/sig/lib/mihari/analyzers/passivetotal.rbs +0 -35
  79. data/sig/lib/mihari/analyzers/pulsedive.rbs +0 -27
  80. data/sig/lib/mihari/analyzers/rule.rbs +0 -68
  81. data/sig/lib/mihari/analyzers/securitytrails.rbs +0 -33
  82. data/sig/lib/mihari/analyzers/shodan.rbs +0 -36
  83. data/sig/lib/mihari/analyzers/urlscan.rbs +0 -31
  84. data/sig/lib/mihari/analyzers/virustotal.rbs +0 -31
  85. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +0 -33
  86. data/sig/lib/mihari/analyzers/zoomeye.rbs +0 -35
  87. data/sig/lib/mihari/cli/base.rbs +0 -9
  88. data/sig/lib/mihari/cli/init.rbs +0 -7
  89. data/sig/lib/mihari/cli/main.rbs +0 -9
  90. data/sig/lib/mihari/cli/validator.rbs +0 -7
  91. data/sig/lib/mihari/commands/init.rbs +0 -9
  92. data/sig/lib/mihari/commands/json.rbs +0 -7
  93. data/sig/lib/mihari/commands/search.rbs +0 -35
  94. data/sig/lib/mihari/commands/validator.rbs +0 -9
  95. data/sig/lib/mihari/commands/web.rbs +0 -7
  96. data/sig/lib/mihari/constants.rbs +0 -5
  97. data/sig/lib/mihari/database.rbs +0 -25
  98. data/sig/lib/mihari/emitters/base.rbs +0 -18
  99. data/sig/lib/mihari/emitters/database.rbs +0 -9
  100. data/sig/lib/mihari/emitters/http.rbs +0 -35
  101. data/sig/lib/mihari/emitters/misp.rbs +0 -34
  102. data/sig/lib/mihari/emitters/slack.rbs +0 -73
  103. data/sig/lib/mihari/emitters/stdout.rbs +0 -9
  104. data/sig/lib/mihari/emitters/the_hive.rbs +0 -32
  105. data/sig/lib/mihari/emitters/webhook.rbs +0 -20
  106. data/sig/lib/mihari/enrichers/base.rbs +0 -12
  107. data/sig/lib/mihari/enrichers/google_public_dns.rbs +0 -18
  108. data/sig/lib/mihari/enrichers/ipinfo.rbs +0 -16
  109. data/sig/lib/mihari/errors.rbs +0 -10
  110. data/sig/lib/mihari/feed/parser.rbs +0 -11
  111. data/sig/lib/mihari/feed/reader.rbs +0 -56
  112. data/sig/lib/mihari/http.rbs +0 -64
  113. data/sig/lib/mihari/mixins/autonomous_system.rbs +0 -14
  114. data/sig/lib/mihari/mixins/configurable.rbs +0 -30
  115. data/sig/lib/mihari/mixins/configuration.rbs +0 -45
  116. data/sig/lib/mihari/mixins/disallowed_data_value.rbs +0 -23
  117. data/sig/lib/mihari/mixins/error_notification.rbs +0 -12
  118. data/sig/lib/mihari/mixins/hash.rbs +0 -14
  119. data/sig/lib/mihari/mixins/refang.rbs +0 -14
  120. data/sig/lib/mihari/mixins/retriable.rbs +0 -15
  121. data/sig/lib/mihari/models/alert.rbs +0 -18
  122. data/sig/lib/mihari/models/artifact.rbs +0 -69
  123. data/sig/lib/mihari/models/autonomous_system.rbs +0 -14
  124. data/sig/lib/mihari/models/cpe.rbs +0 -7
  125. data/sig/lib/mihari/models/dns.rbs +0 -19
  126. data/sig/lib/mihari/models/geolocation.rbs +0 -15
  127. data/sig/lib/mihari/models/port.rbs +0 -7
  128. data/sig/lib/mihari/models/reverse_dns.rbs +0 -14
  129. data/sig/lib/mihari/models/rule.rbs +0 -17
  130. data/sig/lib/mihari/models/tag.rbs +0 -5
  131. data/sig/lib/mihari/models/tagging.rbs +0 -4
  132. data/sig/lib/mihari/models/whois.rbs +0 -66
  133. data/sig/lib/mihari/status.rbs +0 -25
  134. data/sig/lib/mihari/structs/censys.rbs +0 -58
  135. data/sig/lib/mihari/structs/filters.rbs +0 -40
  136. data/sig/lib/mihari/structs/google_public_dns.rbs +0 -21
  137. data/sig/lib/mihari/structs/greynoise.rbs +0 -30
  138. data/sig/lib/mihari/structs/ipinfo.rbs +0 -17
  139. data/sig/lib/mihari/structs/onyphe.rbs +0 -25
  140. data/sig/lib/mihari/structs/rule.rbs +0 -57
  141. data/sig/lib/mihari/structs/shodan.rbs +0 -30
  142. data/sig/lib/mihari/structs/urlscan.rbs +0 -28
  143. data/sig/lib/mihari/structs/virustotal_intelligence.rbs +0 -33
  144. data/sig/lib/mihari/type_checker.rbs +0 -48
  145. data/sig/lib/mihari/types.rbs +0 -23
  146. data/sig/lib/mihari/version.rbs +0 -3
  147. data/sig/lib/mihari/web/app.rbs +0 -5
  148. data/sig/lib/mihari.rbs +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16412e44a6aa5eb9fb7022aa531b950249df76f074136e71c6621e5f2d3c7d44
4
- data.tar.gz: 403f780911934e891ef072c87a3a6ae48d31f1854c8806d4c9ece884faf9ec62
3
+ metadata.gz: b7944fcbb2ef6b1ff7fccbe5c8158bd21a186b05e8fae70a6700256dce10adbb
4
+ data.tar.gz: 36605153506952b323be6e3a7646fd4446f280943fc6c587d7885e8eec413c33
5
5
  SHA512:
6
- metadata.gz: edde8ae3fb93a7e3719788c6727782f7f9a9e2ae53eaa9d804342e545e0ca5e9ec6bb96d633f9c26a33f98e4addaf8693b6da37de8930f8770847a531665baa0
7
- data.tar.gz: afda1bef59be058cbb5972d771bf7615820cc4588cae6cc53c2f2d39333e02edce9a3165dee30caf0996195ab09565392316d116040ce5cc93dbdac28b04c239
6
+ metadata.gz: ba53c1fb987ffd933017ccc64a3a72adc032de81a377e34684c4927304d295d85531ad11e796abd3c17489411fcf15eedd494d7ff7a8b7d0c53fdeb511eb5a8d
7
+ data.tar.gz: 6dff687f32dfef7f0cc19b76cba77a6233ebbefa9b90f522f37092468370eb5abb0e7f185d86f74077944cb0c76609b01390e7878ca494a82419ac44e59d93e5
data/Steepfile CHANGED
@@ -1,5 +1,4 @@
1
1
  target :lib do
2
- signature "sig"
3
2
  check "lib"
4
3
 
5
4
  repo_path "vendor/rbs/gem_rbs_collection/gems"
@@ -5,18 +5,19 @@ module Mihari
5
5
  class Base
6
6
  extend Dry::Initializer
7
7
 
8
+ option :rule, default: proc {}
9
+
8
10
  include Mixins::AutonomousSystem
9
11
  include Mixins::Configurable
10
- include Mixins::Database
11
12
  include Mixins::Retriable
12
13
 
13
- attr_accessor :ignore_old_artifacts, :ignore_threshold
14
+ # @return [Mihari::Structs::Rule, nil]
15
+ attr_reader :rule
14
16
 
15
17
  def initialize(*args, **kwargs)
16
- super
18
+ super(*args, **kwargs)
17
19
 
18
- @ignore_old_artifacts = false
19
- @ignore_threshold = 0
20
+ @base_time = Time.now.utc
20
21
  end
21
22
 
22
23
  # @return [Array<String>, Array<Mihari::Artifact>]
@@ -24,26 +25,11 @@ module Mihari
24
25
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
25
26
  end
26
27
 
27
- # @return [String]
28
- def title
29
- self.class.to_s.split("::").last.to_s
30
- end
31
-
32
- # @return [String]
33
- def description
34
- raise NotImplementedError, "You must implement #{self.class}##{__method__}"
35
- end
36
-
37
28
  # @return [String]
38
29
  def source
39
30
  self.class.to_s.split("::").last.to_s
40
31
  end
41
32
 
42
- # @return [Array<String>]
43
- def tags
44
- []
45
- end
46
-
47
33
  #
48
34
  # Set artifacts & run emitters in parallel
49
35
  #
@@ -55,16 +41,14 @@ module Mihari
55
41
  raise ConfigurationError, "#{class_name} is not configured correctly"
56
42
  end
57
43
 
58
- with_db_connection do
59
- set_enriched_artifacts
60
-
61
- responses = Parallel.map(valid_emitters) do |emitter|
62
- run_emitter emitter
63
- end
44
+ set_enriched_artifacts
64
45
 
65
- # returns Mihari::Alert created by the database emitter
66
- responses.find { |res| res.is_a?(Mihari::Alert) }
46
+ responses = Parallel.map(valid_emitters) do |emitter|
47
+ run_emitter emitter
67
48
  end
49
+
50
+ # returns Mihari::Alert created by the database emitter
51
+ responses.find { |res| res.is_a?(Mihari::Alert) }
68
52
  end
69
53
 
70
54
  #
@@ -77,13 +61,7 @@ module Mihari
77
61
  def run_emitter(emitter)
78
62
  return if enriched_artifacts.empty?
79
63
 
80
- alert_or_something = emitter.run(
81
- title: title,
82
- description: description,
83
- artifacts: enriched_artifacts,
84
- source: source,
85
- tags: tags
86
- )
64
+ alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
87
65
 
88
66
  Mihari.logger.info "Emission by #{emitter.class} is succedded"
89
67
 
@@ -112,7 +90,10 @@ module Mihari
112
90
  # No need to set data_type manually
113
91
  # It is set automatically in #initialize
114
92
  artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
115
- end.select(&:valid?).uniq(&:data)
93
+ end.select(&:valid?).uniq(&:data).map do |artifact|
94
+ artifact.rule_id = rule&.id
95
+ artifact
96
+ end
116
97
  end
117
98
 
118
99
  private
@@ -124,7 +105,7 @@ module Mihari
124
105
  #
125
106
  def unique_artifacts
126
107
  @unique_artifacts ||= normalized_artifacts.select do |artifact|
127
- artifact.unique?(ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold)
108
+ artifact.unique?(base_time: @base_time, artifact_lifetime: rule&.artifact_lifetime)
128
109
  end
129
110
  end
130
111
 
@@ -29,38 +29,21 @@ module Mihari
29
29
 
30
30
  EMITTER_TO_CLASS = {
31
31
  "database" => Emitters::Database,
32
- "http" => Emitters::HTTP,
33
32
  "misp" => Emitters::MISP,
34
33
  "slack" => Emitters::Slack,
35
34
  "the_hive" => Emitters::TheHive,
36
35
  "webhook" => Emitters::Webhook
37
36
  }.freeze
38
37
 
39
- class Rule < Base
40
- include Mixins::DisallowedDataValue
41
-
42
- option :title
43
- option :description
44
- option :queries
45
-
46
- option :id, default: proc { "" }
47
- option :tags, default: proc { [] }
48
- option :allowed_data_types, default: proc { ALLOWED_DATA_TYPES }
49
- option :disallowed_data_values, default: proc { [] }
38
+ # @return [Mihari::Structs::Rule]
39
+ attr_reader :rule
50
40
 
51
- option :emitters, optional: true
52
- option :enrichers, optional: true
53
-
54
- attr_reader :source
41
+ class Rule < Base
42
+ include Mixins::FalsePositive
55
43
 
56
44
  def initialize(**kwargs)
57
45
  super(**kwargs)
58
46
 
59
- @source = id
60
-
61
- @emitters = emitters || DEFAULT_EMITTERS
62
- @enrichers = enrichers || DEFAULT_ENRICHERS
63
-
64
47
  validate_analyzer_configurations
65
48
  end
66
49
 
@@ -72,7 +55,7 @@ module Mihari
72
55
  def artifacts
73
56
  artifacts = []
74
57
 
75
- queries.each do |original_params|
58
+ rule.queries.each do |original_params|
76
59
  parmas = original_params.deep_dup
77
60
 
78
61
  analyzer_name = parmas[:analyzer]
@@ -83,8 +66,12 @@ module Mihari
83
66
  # set interval in the top level
84
67
  options = parmas[:options] || {}
85
68
  interval = options[:interval]
69
+
86
70
  parmas[:interval] = interval if interval
87
71
 
72
+ # set rule
73
+ parmas[:rule] = rule
74
+
88
75
  analyzer = klass.new(query, **parmas)
89
76
 
90
77
  # Use #normalized_artifacts method to get atrifacts as Array<Mihari::Artifact>
@@ -106,9 +93,9 @@ module Mihari
106
93
  #
107
94
  def normalized_artifacts
108
95
  @normalized_artifacts ||= artifacts.uniq(&:data).select(&:valid?).select do |artifact|
109
- allowed_data_types.include? artifact.data_type
96
+ rule.data_types.include? artifact.data_type
110
97
  end.reject do |artifact|
111
- disallowed_data_value? artifact.data
98
+ falsepositive? artifact.data
112
99
  end
113
100
  end
114
101
 
@@ -119,7 +106,7 @@ module Mihari
119
106
  #
120
107
  def enriched_artifacts
121
108
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
122
- enrichers.each do |enricher|
109
+ rule.enrichers.each do |enricher|
123
110
  artifact.enrich_by_enricher(enricher[:enricher])
124
111
  end
125
112
 
@@ -132,22 +119,22 @@ module Mihari
132
119
  #
133
120
  # @return [Array<Regexp, String>]
134
121
  #
135
- def normalized_disallowed_data_values
136
- @normalized_disallowed_data_values ||= disallowed_data_values.map { |v| normalize_disallowed_data_value v }
122
+ def normalized_falsepositives
123
+ @normalized_falsepositives ||= rule.falsepositives.map { |v| normalize_falsepositive v }
137
124
  end
138
125
 
139
126
  #
140
- # Check whether a value is a disallowed data value or not
127
+ # Check whether a value is a falsepositive value or not
141
128
  #
142
129
  # @return [Boolean]
143
130
  #
144
- def disallowed_data_value?(value)
145
- return true if normalized_disallowed_data_values.include?(value)
131
+ def falsepositive?(value)
132
+ return true if normalized_falsepositives.include?(value)
146
133
 
147
- normalized_disallowed_data_values.select do |disallowed_data_value|
148
- disallowed_data_value.is_a?(Regexp)
149
- end.any? do |disallowed_data_value|
150
- disallowed_data_value.match?(value)
134
+ normalized_falsepositives.select do |falsepositive|
135
+ falsepositive.is_a?(Regexp)
136
+ end.any? do |falseposistive|
137
+ falseposistive.match?(value)
151
138
  end
152
139
  end
153
140
 
@@ -168,7 +155,7 @@ module Mihari
168
155
  end
169
156
 
170
157
  def valid_emitters
171
- @valid_emitters ||= emitters.filter_map do |original_params|
158
+ @valid_emitters ||= rule.emitters.filter_map do |original_params|
172
159
  params = original_params.deep_dup
173
160
 
174
161
  name = params[:emitter]
@@ -199,7 +186,7 @@ module Mihari
199
186
  # Validate configuration of analyzers
200
187
  #
201
188
  def validate_analyzer_configurations
202
- queries.each do |params|
189
+ rule.queries.each do |params|
203
190
  analyzer_name = params[:analyzer]
204
191
  klass = get_analyzer_class(analyzer_name)
205
192
 
@@ -3,28 +3,23 @@
3
3
  require "thor"
4
4
 
5
5
  # Commands
6
- require "mihari/commands/search"
6
+ require "mihari/commands/initializer"
7
+ require "mihari/commands/searcher"
8
+ require "mihari/commands/validator"
7
9
  require "mihari/commands/version"
8
10
  require "mihari/commands/web"
9
11
 
10
12
  # CLIs
11
13
  require "mihari/cli/base"
12
14
 
13
- require "mihari/cli/init"
14
- require "mihari/cli/validator"
15
-
16
15
  module Mihari
17
16
  module CLI
18
17
  class Main < Base
19
- include Mihari::Commands::Search
18
+ include Mihari::Commands::Searcher
20
19
  include Mihari::Commands::Version
21
20
  include Mihari::Commands::Web
22
-
23
- desc "init", "Sub commands to initialize a rule"
24
- subcommand "init", Initialization
25
-
26
- desc "validate", "Sub commands to validate format of a rule"
27
- subcommand "validate", Validator
21
+ include Mihari::Commands::Validator
22
+ include Mihari::Commands::Initializer
28
23
  end
29
24
  end
30
25
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Mihari
6
+ module Commands
7
+ module Initializer
8
+ def self.included(thor)
9
+ thor.class_eval do
10
+ desc "init", "Initialize a new rule"
11
+ method_option :path, type: :string, default: "./rule.yml"
12
+ def init
13
+ path = options["path"]
14
+
15
+ warning = "#{path} exists. Do you want to overwrite it? (y/n)"
16
+ return if Pathname(path).exist? && !(yes? warning)
17
+
18
+ initialize_rule path
19
+
20
+ Mihari.logger.info "A new rule is initialized as #{path}."
21
+ end
22
+
23
+ no_commands do
24
+ #
25
+ # @return [Mihari::Structs::Rule]
26
+ #
27
+ def rule_template
28
+ Structs::Rule.from_path File.expand_path("../templates/rule.yml.erb", __dir__)
29
+ end
30
+
31
+ #
32
+ # Create a new rule
33
+ #
34
+ # @param [String] path
35
+ # @param [Dry::Files] files
36
+ #
37
+ # @return [nil]
38
+ #
39
+ def initialize_rule(path, files = Dry::Files.new)
40
+ files.write(path, rule_template.yaml)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ module Searcher
6
+ include Mixins::Database
7
+ include Mixins::ErrorNotification
8
+
9
+ def self.included(thor)
10
+ thor.class_eval do
11
+ desc "search [PATH]", "Search by a rule"
12
+ method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
13
+ def search(path_or_id)
14
+ with_db_connection do
15
+ rule = Structs::Rule.from_path_or_id path_or_id
16
+
17
+ # validate
18
+ begin
19
+ rule.validate!
20
+ rescue RuleValidationError
21
+ return
22
+ end
23
+
24
+ force_overwrite = options["force_overwrite"] || false
25
+
26
+ begin
27
+ rule_model = Mihari::Rule.find(rule.id)
28
+ has_change = rule_model.data != rule.data.deep_stringify_keys
29
+ has_change_and_not_force_overwrite = has_change & !force_overwrite
30
+
31
+ if has_change_and_not_force_overwrite && !yes?("This operation will overwrite the rule in the database (Rule ID: #{rule.id}). Are you sure you want to update the rule? (y/n)")
32
+ return
33
+ end
34
+
35
+ # update the rule
36
+ rule.model.save
37
+ rescue ActiveRecord::RecordNotFound
38
+ # create a new rule
39
+ rule.model.save
40
+ end
41
+
42
+ with_error_notification do
43
+ alert = rule.analyzer.run
44
+ if alert
45
+ data = Mihari::Entities::Alert.represent(alert)
46
+ puts JSON.pretty_generate(data.as_json)
47
+ else
48
+ Mihari.logger.info "There is no new alert created in the database"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -5,7 +5,7 @@ module Mihari
5
5
  module Validator
6
6
  def self.included(thor)
7
7
  thor.class_eval do
8
- desc "rule [PATH]", "Validate rule file format"
8
+ desc "validate [PATH]", "Validate a rule file"
9
9
  #
10
10
  # Validate format of a rule
11
11
  #
@@ -13,7 +13,7 @@ module Mihari
13
13
  #
14
14
  # @return [nil]
15
15
  #
16
- def rule(path)
16
+ def validate(path)
17
17
  rule = Structs::Rule.from_path_or_id(path)
18
18
 
19
19
  begin
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- ALLOWED_DATA_TYPES = ["hash", "ip", "domain", "url", "mail"].freeze
4
+ DEFAULT_DATA_TYPES = %w[hash ip domain url mail].freeze
5
5
 
6
- DEFAULT_EMITTERS = ["database", "misp", "slack", "the_hive", "webhook"].map { |name| { emitter: name } }.freeze
6
+ DEFAULT_EMITTERS = %w[database misp slack the_hive].map { |name| { emitter: name } }.freeze
7
7
 
8
- DEFAULT_ENRICHERS = ["whois", "ipinfo", "shodan", "google_public_dns"].map { |name| { enricher: name } }.freeze
8
+ DEFAULT_ENRICHERS = %w[whois ipinfo shodan google_public_dns].map { |name| { enricher: name } }.freeze
9
9
  end
@@ -17,53 +17,44 @@ def development_env?
17
17
  env == "development"
18
18
  end
19
19
 
20
- class InitialSchema < ActiveRecord::Migration[7.0]
20
+ class V5Schema < ActiveRecord::Migration[7.0]
21
21
  def change
22
- create_table :tags, if_not_exists: true do |t|
23
- t.string :name, null: false
22
+ create_table :rules, id: :string, if_not_exists: true do |t|
23
+ t.string :title, null: false
24
+ t.string :description, null: false
25
+ t.json :data, null: false
26
+ t.timestamps
24
27
  end
25
28
 
26
29
  create_table :alerts, if_not_exists: true do |t|
27
- t.string :title, null: false
28
- t.string :description, null: true
29
- t.string :source, null: false
30
30
  t.timestamps
31
+
32
+ t.belongs_to :rule, foreign_key: true, type: :string, null: false
31
33
  end
32
34
 
33
35
  create_table :artifacts, if_not_exists: true do |t|
34
36
  t.string :data, null: false
35
37
  t.string :data_type, null: false
36
- t.belongs_to :alert, foreign_key: true
38
+ t.string :source
39
+ t.json :metadata
37
40
  t.timestamps
38
- end
39
41
 
40
- create_table :taggings, if_not_exists: true do |t|
41
- t.integer :tag_id
42
- t.integer :alert_id
42
+ t.belongs_to :alert, foreign_key: true, null: false
43
43
  end
44
44
 
45
- add_index :taggings, :tag_id, if_not_exists: true
46
- add_index :taggings, [:tag_id, :alert_id], unique: true, if_not_exists: true
47
- end
48
- end
49
-
50
- class AddeSourceToArtifactSchema < ActiveRecord::Migration[7.0]
51
- def change
52
- add_column :artifacts, :source, :string, if_not_exists: true
53
- end
54
- end
55
-
56
- class EnrichmentsSchema < ActiveRecord::Migration[7.0]
57
- def change
58
45
  create_table :autonomous_systems, if_not_exists: true do |t|
59
46
  t.integer :asn, null: false
60
- t.belongs_to :artifact, foreign_key: true
47
+ t.datetime :created_at
48
+
49
+ t.belongs_to :artifact, foreign_key: true, null: false
61
50
  end
62
51
 
63
52
  create_table :geolocations, if_not_exists: true do |t|
64
53
  t.string :country, null: false
65
54
  t.string :country_code, null: false
66
- t.belongs_to :artifact, foreign_key: true
55
+ t.datetime :created_at
56
+
57
+ t.belongs_to :artifact, foreign_key: true, null: false
67
58
  end
68
59
 
69
60
  create_table :whois_records, if_not_exists: true do |t|
@@ -73,73 +64,59 @@ class EnrichmentsSchema < ActiveRecord::Migration[7.0]
73
64
  t.date :expires_on
74
65
  t.json :registrar
75
66
  t.json :contacts
76
- t.belongs_to :artifact, foreign_key: true
67
+ t.datetime :created_at
68
+
69
+ t.belongs_to :artifact, foreign_key: true, null: false
77
70
  end
78
71
 
79
72
  create_table :dns_records, if_not_exists: true do |t|
80
73
  t.string :resource, null: false
81
74
  t.string :value, null: false
82
- t.belongs_to :artifact, foreign_key: true
75
+ t.datetime :created_at
76
+
77
+ t.belongs_to :artifact, foreign_key: true, null: false
83
78
  end
84
79
 
85
80
  create_table :reverse_dns_names, if_not_exists: true do |t|
86
81
  t.string :name, null: false
87
- t.belongs_to :artifact, foreign_key: true
88
- end
89
- end
90
- end
82
+ t.datetime :created_at
91
83
 
92
- class EnrichmentCreatedAtSchema < ActiveRecord::Migration[7.0]
93
- def change
94
- # Add created_at column because now it is able to enrich an atrifact after the creation
95
- add_column :autonomous_systems, :created_at, :datetime, if_not_exists: true
96
- add_column :geolocations, :created_at, :datetime, if_not_exists: true
97
- add_column :whois_records, :created_at, :datetime, if_not_exists: true
98
- add_column :dns_records, :created_at, :datetime, if_not_exists: true
99
- add_column :reverse_dns_names, :created_at, :datetime, if_not_exists: true
100
- end
101
- end
102
-
103
- class RuleSchema < ActiveRecord::Migration[7.0]
104
- def change
105
- create_table :rules, id: :string, if_not_exists: true do |t|
106
- t.string :title, null: false
107
- t.string :description, null: false
108
- t.json :data, null: false
109
- t.timestamps
84
+ t.belongs_to :artifact, foreign_key: true, null: false
110
85
  end
111
- end
112
- end
113
-
114
- class AddeMetadataToArtifactSchema < ActiveRecord::Migration[7.0]
115
- def change
116
- add_column :artifacts, :metadata, :json, if_not_exists: true
117
- end
118
- end
119
86
 
120
- class AddYAMLToRulesSchema < ActiveRecord::Migration[7.0]
121
- def change
122
- add_column :rules, :yaml, :text, if_not_exists: true
123
- end
124
- end
125
-
126
- class EnrichmentsV45Schema < ActiveRecord::Migration[7.0]
127
- def change
128
87
  create_table :cpes, if_not_exists: true do |t|
129
88
  t.string :cpe, null: false
130
- t.belongs_to :artifact, foreign_key: true
89
+ t.datetime :created_at
90
+
91
+ t.belongs_to :artifact, foreign_key: true, null: false
131
92
  end
132
93
 
133
94
  create_table :ports, if_not_exists: true do |t|
134
95
  t.integer :port, null: false
135
- t.belongs_to :artifact, foreign_key: true
96
+ t.datetime :created_at
97
+
98
+ t.belongs_to :artifact, foreign_key: true, null: false
99
+ end
100
+
101
+ create_table :tags, if_not_exists: true do |t|
102
+ t.string :name, null: false
103
+ t.datetime :created_at
136
104
  end
105
+
106
+ create_table :taggings, if_not_exists: true do |t|
107
+ t.integer :tag_id
108
+ t.integer :alert_id
109
+ t.datetime :created_at
110
+ end
111
+
112
+ add_index :taggings, :tag_id, if_not_exists: true
113
+ add_index :taggings, %i[tag_id alert_id], unique: true, if_not_exists: true
137
114
  end
138
115
  end
139
116
 
140
117
  def adapter
141
- return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
142
- return "mysql2" if Mihari.config.database.start_with?("mysql2://")
118
+ return "postgresql" if %w[postgresql postgres].include?(Mihari.config.database_url.scheme)
119
+ return "mysql2" if Mihari.config.database_url.scheme == "mysql2"
143
120
 
144
121
  "sqlite3"
145
122
  end
@@ -157,19 +134,7 @@ module Mihari
157
134
  def migrate(direction)
158
135
  ActiveRecord::Migration.verbose = false
159
136
 
160
- [
161
- InitialSchema,
162
- AddeSourceToArtifactSchema,
163
- EnrichmentsSchema,
164
- EnrichmentCreatedAtSchema,
165
- # v4.0
166
- RuleSchema,
167
- AddeMetadataToArtifactSchema,
168
- # v4.4
169
- AddYAMLToRulesSchema,
170
- # v4.5
171
- EnrichmentsV45Schema
172
- ].each { |schema| schema.migrate direction }
137
+ [V5Schema].each { |schema| schema.migrate direction }
173
138
  end
174
139
  memoize :migrate unless test_env?
175
140
 
@@ -181,19 +146,19 @@ module Mihari
181
146
 
182
147
  case adapter
183
148
  when "postgresql", "mysql2"
184
- ActiveRecord::Base.establish_connection(Mihari.config.database)
149
+ ActiveRecord::Base.establish_connection(Mihari.config.database_url.to_s)
185
150
  else
186
151
  ActiveRecord::Base.establish_connection(
187
152
  adapter: adapter,
188
- database: Mihari.config.database
153
+ database: Mihari.config.database_url.path[1..]
189
154
  )
190
155
  end
191
156
 
192
157
  ActiveRecord::Base.logger = Logger.new($stdout) if development_env?
193
158
 
194
159
  migrate :up
195
- rescue StandardError
196
- # Do nothing
160
+ rescue StandardError => e
161
+ Mihari.logger.error e
197
162
  end
198
163
 
199
164
  #