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 +4 -4
- data/lib/mihari/commands/rule.rb +27 -4
- data/lib/mihari/database.rb +1 -1
- data/lib/mihari/enrichers/whois.rb +3 -1
- data/lib/mihari/models/artifact.rb +42 -6
- data/lib/mihari/rule.rb +24 -9
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/index-BgJUBUyh.css +1 -0
- data/lib/mihari/web/public/assets/index-FQP-p7mX.js +1563 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +388 -388
- data/mihari.gemspec +14 -14
- data/requirements.txt +1 -1
- metadata +42 -43
- data/build_frontend.sh +0 -11
- data/lib/mihari/web/public/assets/index-CuFhw5g8.css +0 -1
- data/lib/mihari/web/public/assets/index-Dgz0wXc1.js +0 -1764
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d9b88f5339ebe2064567127db7fac4a4e0c9ec56648c575de2c96fcb16a3af8
|
4
|
+
data.tar.gz: 15f840a496f423dff92dcc34e2f7c4ad39ed7b1a370c053a5f5cdf29c39f84a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f07c6c65db1dd47634e0f5919d1b6faa8250cc02d255226085c1fd88f6a32e78338a6ddeaa7b0f221e4593af81735635c4652cdfd46b415bfa0e5ecaf08cb0f1
|
7
|
+
data.tar.gz: 5d8411ddbee4e1e466e3521ec611ffba67e52ea3852e3cfce8f095978b60584b15cc541118064d2fd8bfa9e40c14720899efafa006f96543e7947fecbd5b76da
|
data/lib/mihari/commands/rule.rb
CHANGED
@@ -26,14 +26,37 @@ module Mihari
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
desc "validate PATH", "Validate
|
29
|
+
desc "validate PATH", "Validate rule(s)"
|
30
30
|
#
|
31
|
-
# Validate
|
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
|
36
|
-
rule = Dry::Monads::Try[ValidationError] { Mihari::Rule.
|
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
|
|
data/lib/mihari/database.rb
CHANGED
@@ -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.
|
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.
|
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
|
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?
|
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
|
-
|
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.
|
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("
|
427
|
+
raise ValidationError.new("#{path_or_id}: validation failed", errors) if errors?
|
413
428
|
end
|
414
429
|
end
|
415
430
|
end
|
data/lib/mihari/version.rb
CHANGED