mihari 7.6.2 → 7.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5fff977863ca54dae5b645e3587d8f09f0583df44fca258ac4f1c8f3455a74e
4
- data.tar.gz: 3c45cf5405737aaddbd79b42590509f155b3be13c3a0645057cc446666728655
3
+ metadata.gz: 0d9b88f5339ebe2064567127db7fac4a4e0c9ec56648c575de2c96fcb16a3af8
4
+ data.tar.gz: 15f840a496f423dff92dcc34e2f7c4ad39ed7b1a370c053a5f5cdf29c39f84a2
5
5
  SHA512:
6
- metadata.gz: d189d79161738b06e2ee970b835b953bd968c4c4500b7cf589fcf3c2ac31a49f46a9d2fcf3581818484f9daf0b72a87a1d3cd39084d39e4a9bcfdf4ab0edc1a4
7
- data.tar.gz: 463d12e871297b9d5be403139720fc51d09bd51e0c41855d47cbe889de086f2a5a2d2fe25fa592f81ddec1b6e56195b9878372c1fc8955f23357b72d09bbdce0
6
+ metadata.gz: f07c6c65db1dd47634e0f5919d1b6faa8250cc02d255226085c1fd88f6a32e78338a6ddeaa7b0f221e4593af81735635c4652cdfd46b415bfa0e5ecaf08cb0f1
7
+ data.tar.gz: 5d8411ddbee4e1e466e3521ec611ffba67e52ea3852e3cfce8f095978b60584b15cc541118064d2fd8bfa9e40c14720899efafa006f96543e7947fecbd5b76da
@@ -26,14 +26,37 @@ module Mihari
26
26
  end
27
27
  end
28
28
 
29
- desc "validate PATH", "Validate a rule"
29
+ desc "validate PATH", "Validate rule(s)"
30
30
  #
31
- # Validate format of a rule
31
+ # Validate rule(s)
32
+ #
33
+ # @param [Array<String>] paths
34
+ #
35
+ def validate(*paths)
36
+ # @type [Array<Mihari::ValidationError>]
37
+ errors = paths.flat_map { |path| Dir.glob(path) }.map do |path|
38
+ Dry::Monads::Try[ValidationError] { Mihari::Rule.from_file(path) }
39
+ end.filter_map do |result|
40
+ result.exception if result.error?
41
+ end
42
+ return if errors.empty?
43
+
44
+ errors.each do |error|
45
+ data = Entities::ErrorMessage.represent(message: error.message, detail: error.detail)
46
+ warn JSON.pretty_generate(data.as_json)
47
+ end
48
+
49
+ exit 1
50
+ end
51
+
52
+ desc "format PATH", "format a rule"
53
+ #
54
+ # Format a rule file
32
55
  #
33
56
  # @param [String] path
34
57
  #
35
- def validate(path)
36
- rule = Dry::Monads::Try[ValidationError] { Mihari::Rule.from_yaml File.read(path) }.value!
58
+ def format(path)
59
+ rule = Dry::Monads::Try[ValidationError] { Mihari::Rule.from_file(path) }.value!
37
60
  puts rule.data.to_yaml
38
61
  end
39
62
 
@@ -6,7 +6,7 @@ ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym "CPE" }
6
6
  #
7
7
  # Mihari v7 DB schema
8
8
  #
9
- class V7Schema < ActiveRecord::Migration[7.1]
9
+ class V7Schema < ActiveRecord::Migration[7.2]
10
10
  def change
11
11
  create_table :rules, id: :string, if_not_exists: true do |t|
12
12
  t.string :title, null: false
@@ -16,7 +16,9 @@ module Mihari
16
16
  def call(artifact)
17
17
  return if artifact.domain.nil?
18
18
 
19
- artifact.whois_record ||= memoized_lookup(PublicSuffix.domain(artifact.domain))
19
+ artifact.tap do |tapped|
20
+ tapped.whois_record ||= memoized_lookup(PublicSuffix.domain(artifact.domain))
21
+ end
20
22
  end
21
23
 
22
24
  private
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ostruct"
4
+
3
5
  module Mihari
4
6
  module Models
5
7
  #
@@ -158,10 +160,7 @@ module Mihari
158
160
  # @return [Boolean] true if it is unique. Otherwise false.
159
161
  #
160
162
  def unique?(base_time: nil, artifact_ttl: nil)
161
- artifact = self.class.joins(:alert).where(
162
- data:,
163
- alert: {rule_id:}
164
- ).order(created_at: :desc).first
163
+ artifact = self.class.joins(:alert).where(data:, alert: {rule_id:}).order(created_at: :desc).first
165
164
  return true if artifact.nil?
166
165
 
167
166
  # check whether the artifact is decayed or not
@@ -179,7 +178,32 @@ module Mihari
179
178
  end
180
179
 
181
180
  def enrich
182
- callable_enrichers.each { |enricher| enricher.result self }
181
+ enrich_by_enrichers callable_enrichers
182
+ end
183
+
184
+ #
185
+ # @param [Array<Mihari::Enrichers::Base>] enrichers
186
+ # @param [Boolean] parallel
187
+ #
188
+ # @return [Mihari::Models::Artifact]
189
+ #
190
+ def enrich_by_enrichers(enrichers)
191
+ # NOTE: doing parallel with ActiveRecord objects is troublesome (e.g. connection issue, etc.)
192
+ # so converting the object to an OpenStruct object
193
+ s = struct
194
+ results = Parallel.map(enrichers) { |enricher| enricher.result s }
195
+ enriched = results.compact.map { |result| result.value_or(nil) }.compact
196
+
197
+ self.dns_records = enriched.map(&:dns_records).flatten.compact
198
+ self.cpes = enriched.map(&:cpes).flatten.compact
199
+ self.ports = enriched.map(&:ports).flatten.compact
200
+ self.vulnerabilities = enriched.map(&:vulnerabilities).flatten.compact
201
+
202
+ self.autonomous_system = enriched.map(&:autonomous_system).compact.first
203
+ self.geolocation = enriched.map(&:geolocation).compact.first
204
+ self.whois_record = enriched.map(&:whois_record).compact.first
205
+
206
+ self
183
207
  end
184
208
 
185
209
  #
@@ -195,6 +219,18 @@ module Mihari
195
219
  end
196
220
  end
197
221
 
222
+ def struct
223
+ OpenStruct.new(attributes).tap do |s|
224
+ s.domain = domain
225
+ s.cpes ||= []
226
+ s.dns_records ||= []
227
+ s.ports ||= []
228
+ s.reverse_dns_names ||= []
229
+ s.vulnerabilities ||= []
230
+ s.tags ||= []
231
+ end
232
+ end
233
+
198
234
  class << self
199
235
  # @!method search_by_filter(filter)
200
236
  # @param [Mihari::Structs::Filters::Search] filter
@@ -212,7 +248,7 @@ module Mihari
212
248
  #
213
249
  def callable_enrichers
214
250
  @callable_enrichers ||= Mihari.enrichers.map(&:new).select do |enricher|
215
- enricher.callable?(self)
251
+ enricher.callable? self
216
252
  end
217
253
  end
218
254
 
data/lib/mihari/rule.rb CHANGED
@@ -5,6 +5,9 @@ module Mihari
5
5
  include Concerns::FalsePositiveNormalizable
6
6
  include Concerns::FalsePositiveValidatable
7
7
 
8
+ # @return [String, nil]
9
+ attr_reader :path_or_id
10
+
8
11
  # @return [Hash]
9
12
  attr_reader :data
10
13
 
@@ -19,9 +22,11 @@ module Mihari
19
22
  #
20
23
  # @param [Hash] data
21
24
  #
22
- def initialize(**data)
25
+ # @param [Object] path_or_id
26
+ def initialize(path_or_id: nil, **data)
23
27
  super()
24
28
 
29
+ @path_or_id = path_or_id
25
30
  @data = data.deep_symbolize_keys
26
31
  @errors = nil
27
32
  @base_time = Time.now.utc
@@ -173,10 +178,7 @@ module Mihari
173
178
  #
174
179
  def enriched_artifacts
175
180
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
176
- artifact.tap do |tapped|
177
- # NOTE: To apply changes correctly, enrichers should be applied to an artifact serially
178
- enrichers.each { |enricher| enricher.result(tapped) }
179
- end
181
+ artifact.enrich_by_enrichers enrichers
180
182
  end
181
183
  end
182
184
 
@@ -251,16 +253,29 @@ module Mihari
251
253
  end
252
254
 
253
255
  class << self
256
+ #
257
+ # Load rule from YAML file
258
+ #
259
+ # @param [String] path
260
+ #
261
+ # @return [Mihari::Rule]
262
+ #
263
+ def from_file(path)
264
+ yaml = File.read(path)
265
+ from_yaml(yaml, path: path)
266
+ end
267
+
254
268
  #
255
269
  # Load rule from YAML string
256
270
  #
257
271
  # @param [String] yaml
272
+ # @param [String, nil] path
258
273
  #
259
274
  # @return [Mihari::Rule]
260
275
  #
261
- def from_yaml(yaml)
276
+ def from_yaml(yaml, path: nil)
262
277
  data = YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol])
263
- new(**data)
278
+ new(path_or_id: path, **data)
264
279
  end
265
280
 
266
281
  #
@@ -269,7 +284,7 @@ module Mihari
269
284
  # @return [Mihari::Rule]
270
285
  #
271
286
  def from_model(model)
272
- new(**model.data)
287
+ new(path_or_id: model.id, **model.data)
273
288
  end
274
289
  end
275
290
 
@@ -409,7 +424,7 @@ module Mihari
409
424
  @data = result.to_h
410
425
  @errors = result.errors
411
426
 
412
- raise ValidationError.new("Validation failed", errors) if errors?
427
+ raise ValidationError.new("#{path_or_id}: validation failed", errors) if errors?
413
428
  end
414
429
  end
415
430
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "7.6.2"
4
+ VERSION = "7.6.3"
5
5
  end