mihari 7.1.3 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +2 -2
- data/Rakefile +8 -1
- data/lefthook.yml +4 -1
- data/lib/mihari/actor.rb +16 -0
- data/lib/mihari/analyzers/base.rb +7 -25
- data/lib/mihari/analyzers/binaryedge.rb +0 -6
- data/lib/mihari/analyzers/censys.rb +0 -9
- data/lib/mihari/analyzers/circl.rb +0 -6
- data/lib/mihari/analyzers/fofa.rb +0 -6
- data/lib/mihari/analyzers/greynoise.rb +0 -6
- data/lib/mihari/analyzers/hunterhow.rb +0 -6
- data/lib/mihari/analyzers/onyphe.rb +0 -6
- data/lib/mihari/analyzers/otx.rb +0 -6
- data/lib/mihari/analyzers/passivetotal.rb +0 -4
- data/lib/mihari/analyzers/pulsedive.rb +0 -6
- data/lib/mihari/analyzers/securitytrails.rb +0 -4
- data/lib/mihari/analyzers/shodan.rb +0 -6
- data/lib/mihari/analyzers/urlscan.rb +0 -6
- data/lib/mihari/analyzers/virustotal.rb +0 -4
- data/lib/mihari/analyzers/virustotal_intelligence.rb +7 -6
- data/lib/mihari/analyzers/zoomeye.rb +0 -6
- data/lib/mihari/commands/web.rb +1 -1
- data/lib/mihari/concerns/falsepositive_normalizable.rb +30 -0
- data/lib/mihari/concerns/falsepositive_validatable.rb +1 -17
- data/lib/mihari/config.rb +1 -1
- data/lib/mihari/database.rb +18 -1
- data/lib/mihari/emitters/database.rb +0 -6
- data/lib/mihari/emitters/misp.rb +0 -6
- data/lib/mihari/emitters/slack.rb +5 -21
- data/lib/mihari/emitters/the_hive.rb +0 -6
- data/lib/mihari/enrichers/base.rb +54 -12
- data/lib/mihari/enrichers/google_public_dns.rb +28 -7
- data/lib/mihari/enrichers/mmdb.rb +25 -7
- data/lib/mihari/enrichers/shodan.rb +35 -4
- data/lib/mihari/enrichers/whois.rb +37 -31
- data/lib/mihari/entities/artifact.rb +6 -2
- data/lib/mihari/entities/autonomous_system.rb +1 -1
- data/lib/mihari/entities/cpe.rb +1 -1
- data/lib/mihari/entities/port.rb +1 -1
- data/lib/mihari/entities/vulnerability.rb +10 -0
- data/lib/mihari/errors.rb +2 -0
- data/lib/mihari/models/alert.rb +12 -0
- data/lib/mihari/models/artifact.rb +118 -159
- data/lib/mihari/models/rule.rb +21 -0
- data/lib/mihari/models/vulnerability.rb +12 -0
- data/lib/mihari/rule.rb +44 -29
- data/lib/mihari/schemas/alert.rb +3 -3
- data/lib/mihari/schemas/analyzer.rb +27 -27
- data/lib/mihari/schemas/emitter.rb +9 -9
- data/lib/mihari/schemas/macros.rb +2 -2
- data/lib/mihari/schemas/options.rb +2 -5
- data/lib/mihari/schemas/rule.rb +19 -12
- data/lib/mihari/services/builders.rb +0 -134
- data/lib/mihari/services/enrichers.rb +3 -1
- data/lib/mihari/services/feed.rb +2 -5
- data/lib/mihari/services/getters.rb +1 -1
- data/lib/mihari/services/proxies.rb +3 -3
- data/lib/mihari/structs/censys.rb +2 -2
- data/lib/mihari/structs/greynoise.rb +1 -1
- data/lib/mihari/structs/onyphe.rb +1 -1
- data/lib/mihari/structs/shodan.rb +59 -21
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/artifacts.rb +4 -2
- data/lib/mihari/web/endpoints/rules.rb +1 -1
- data/lib/mihari/web/public/assets/{index-TOeU8PE2.js → index-JHS0L8KZ.js} +47 -47
- data/lib/mihari/web/public/assets/{index-dVaNxqTC.css → index-ReF8ffd-.css} +1 -1
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +17 -17
- data/lib/mihari.rb +3 -0
- data/mihari.gemspec +2 -2
- data/requirements.txt +1 -1
- metadata +11 -8
data/lib/mihari/models/rule.rb
CHANGED
@@ -6,6 +6,27 @@ module Mihari
|
|
6
6
|
# Rule model
|
7
7
|
#
|
8
8
|
class Rule < ActiveRecord::Base
|
9
|
+
# @!attribute [rw] id
|
10
|
+
# @return [String]
|
11
|
+
|
12
|
+
# @!attribute [rw] title
|
13
|
+
# @return [String]
|
14
|
+
|
15
|
+
# @!attribute [rw] description
|
16
|
+
# @return [String]
|
17
|
+
|
18
|
+
# @!attribute [rw] data
|
19
|
+
# @return [Hash]
|
20
|
+
|
21
|
+
# @!attribute [rw] created_at
|
22
|
+
# @return [DateTime]
|
23
|
+
|
24
|
+
# @!attribute [rw] updated_at
|
25
|
+
# @return [DateTime]
|
26
|
+
|
27
|
+
# @!attribute [r] alerts
|
28
|
+
# @return [Array<Mihari::Models::Alert>]
|
29
|
+
|
9
30
|
has_many :alerts, dependent: :destroy
|
10
31
|
has_many :taggings, dependent: :destroy
|
11
32
|
has_many :tags, through: :taggings
|
data/lib/mihari/rule.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
class Rule < Service
|
5
|
+
include Concerns::FalsePositiveNormalizable
|
5
6
|
include Concerns::FalsePositiveValidatable
|
6
7
|
|
7
8
|
# @return [Hash]
|
@@ -136,8 +137,7 @@ module Mihari
|
|
136
137
|
analyzer_results.flat_map do |result|
|
137
138
|
artifacts = result.value!
|
138
139
|
artifacts.map do |artifact|
|
139
|
-
artifact.rule_id = id
|
140
|
-
artifact
|
140
|
+
artifact.tap { |tapped| tapped.rule_id = id }
|
141
141
|
end
|
142
142
|
end
|
143
143
|
end
|
@@ -174,8 +174,10 @@ module Mihari
|
|
174
174
|
# @return [Array<Mihari::Models::Artifact>]
|
175
175
|
#
|
176
176
|
def enriched_artifacts
|
177
|
-
@enriched_artifacts ||=
|
178
|
-
|
177
|
+
@enriched_artifacts ||= unique_artifacts.map do |artifact|
|
178
|
+
serial_enrichers.each { |enricher| enricher.result(artifact) }
|
179
|
+
Parallel.each(parallel_enrichers) { |enricher| enricher.result(artifact) }
|
180
|
+
|
179
181
|
artifact
|
180
182
|
end
|
181
183
|
end
|
@@ -188,9 +190,10 @@ module Mihari
|
|
188
190
|
def bulk_emit
|
189
191
|
return [] if enriched_artifacts.empty?
|
190
192
|
|
191
|
-
|
192
|
-
emitter.result(enriched_artifacts).value_or
|
193
|
-
|
193
|
+
[].tap do |out|
|
194
|
+
out << serial_emitters.map { |emitter| emitter.result(enriched_artifacts).value_or(nil) }
|
195
|
+
out << Parallel.map(parallel_emitters) { |emitter| emitter.result(enriched_artifacts).value_or(nil) }
|
196
|
+
end.flatten.compact
|
194
197
|
end
|
195
198
|
|
196
199
|
#
|
@@ -291,11 +294,11 @@ module Mihari
|
|
291
294
|
#
|
292
295
|
# @return [Boolean]
|
293
296
|
#
|
294
|
-
def falsepositive?(
|
295
|
-
return true if falsepositives.include?(
|
297
|
+
def falsepositive?(artifact)
|
298
|
+
return true if falsepositives.include?(artifact)
|
296
299
|
|
297
300
|
regexps = falsepositives.select { |fp| fp.is_a?(Regexp) }
|
298
|
-
regexps.any? { |fp| fp.match?(
|
301
|
+
regexps.any? { |fp| fp.match?(artifact) }
|
299
302
|
end
|
300
303
|
|
301
304
|
#
|
@@ -315,12 +318,12 @@ module Mihari
|
|
315
318
|
# @return [Array<Mihari::Analyzers::Base>]
|
316
319
|
#
|
317
320
|
def analyzers
|
318
|
-
@analyzers ||= queries.map do |params|
|
319
|
-
|
320
|
-
klass = get_analyzer_class(
|
321
|
-
|
322
|
-
|
323
|
-
|
321
|
+
@analyzers ||= queries.deep_dup.map do |params|
|
322
|
+
name = params.delete(:analyzer)
|
323
|
+
klass = get_analyzer_class(name)
|
324
|
+
klass.from_params(params).tap do |analyzer|
|
325
|
+
analyzer.validate_configuration!
|
326
|
+
end
|
324
327
|
end
|
325
328
|
end
|
326
329
|
|
@@ -356,19 +359,25 @@ module Mihari
|
|
356
359
|
# @return [Array<Mihari::Emitters::Base>]
|
357
360
|
#
|
358
361
|
def emitters
|
359
|
-
@emitters ||= data[:emitters].
|
360
|
-
name = params
|
361
|
-
options = params
|
362
|
-
|
363
|
-
%i[emitter options].each { |key| params.delete key }
|
362
|
+
@emitters ||= data[:emitters].deep_dup.map do |params|
|
363
|
+
name = params.delete(:emitter)
|
364
|
+
options = params.delete(:options)
|
364
365
|
|
365
366
|
klass = get_emitter_class(name)
|
366
|
-
|
367
|
-
|
368
|
-
|
367
|
+
klass.new(rule: self, options: options, **params).tap do |emitter|
|
368
|
+
emitter.validate_configuration!
|
369
|
+
end
|
369
370
|
end
|
370
371
|
end
|
371
372
|
|
373
|
+
def parallel_emitters
|
374
|
+
emitters.select(&:parallel?)
|
375
|
+
end
|
376
|
+
|
377
|
+
def serial_emitters
|
378
|
+
emitters.reject(&:parallel?)
|
379
|
+
end
|
380
|
+
|
372
381
|
#
|
373
382
|
# Get enricher class
|
374
383
|
#
|
@@ -386,17 +395,23 @@ module Mihari
|
|
386
395
|
# @return [Array<Mihari::Enrichers::Base>] enrichers
|
387
396
|
#
|
388
397
|
def enrichers
|
389
|
-
@enrichers ||= data[:enrichers].
|
390
|
-
name = params
|
391
|
-
options = params
|
392
|
-
|
393
|
-
%i[enricher options].each { |key| params.delete key }
|
398
|
+
@enrichers ||= data[:enrichers].deep_dup.map do |params|
|
399
|
+
name = params.delete(:enricher)
|
400
|
+
options = params.delete(:options)
|
394
401
|
|
395
402
|
klass = get_enricher_class(name)
|
396
403
|
klass.new(options: options, **params)
|
397
404
|
end
|
398
405
|
end
|
399
406
|
|
407
|
+
def parallel_enrichers
|
408
|
+
enrichers.select(&:parallel?)
|
409
|
+
end
|
410
|
+
|
411
|
+
def serial_enrichers
|
412
|
+
enrichers.reject(&:parallel?)
|
413
|
+
end
|
414
|
+
|
400
415
|
#
|
401
416
|
# Validate the data format
|
402
417
|
#
|
data/lib/mihari/schemas/alert.rb
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Schemas
|
5
5
|
Alert = Dry::Schema.Params do
|
6
|
-
required(:rule_id).
|
7
|
-
required(:artifacts).
|
8
|
-
optional(:source).
|
6
|
+
required(:rule_id).filled(:string)
|
7
|
+
required(:artifacts).array { filled(:string) }
|
8
|
+
optional(:source).filled(:string)
|
9
9
|
end
|
10
10
|
|
11
11
|
#
|
@@ -20,8 +20,8 @@ module Mihari
|
|
20
20
|
key = keys.first
|
21
21
|
const_set(key.upcase, Dry::Schema.Params do
|
22
22
|
required(:analyzer).value(Types::String.enum(*keys))
|
23
|
-
required(:query).
|
24
|
-
optional(:api_key).
|
23
|
+
required(:query).filled(:string)
|
24
|
+
optional(:api_key).filled(:string)
|
25
25
|
optional(:options).hash(AnalyzerPaginationOptions)
|
26
26
|
end)
|
27
27
|
end
|
@@ -36,60 +36,60 @@ module Mihari
|
|
36
36
|
key = keys.first
|
37
37
|
const_set(key.upcase, Dry::Schema.Params do
|
38
38
|
required(:analyzer).value(Types::String.enum(*keys))
|
39
|
-
required(:query).
|
40
|
-
optional(:api_key).
|
39
|
+
required(:query).filled(:string)
|
40
|
+
optional(:api_key).filled(:string)
|
41
41
|
optional(:options).hash(AnalyzerOptions)
|
42
42
|
end)
|
43
43
|
end
|
44
44
|
|
45
45
|
DNSTwister = Dry::Schema.Params do
|
46
46
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::DNSTwister.keys))
|
47
|
-
required(:query).
|
47
|
+
required(:query).filled(:string)
|
48
48
|
optional(:options).hash(AnalyzerOptions)
|
49
49
|
end
|
50
50
|
|
51
51
|
Censys = Dry::Schema.Params do
|
52
52
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Censys.keys))
|
53
|
-
required(:query).
|
54
|
-
optional(:id).
|
55
|
-
optional(:secret).
|
53
|
+
required(:query).filled(:string)
|
54
|
+
optional(:id).filled(:string)
|
55
|
+
optional(:secret).filled(:string)
|
56
56
|
optional(:options).hash(AnalyzerPaginationOptions)
|
57
57
|
end
|
58
58
|
|
59
59
|
CIRCL = Dry::Schema.Params do
|
60
60
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::CIRCL.keys))
|
61
|
-
required(:query).
|
62
|
-
optional(:username).
|
63
|
-
optional(:password).
|
61
|
+
required(:query).filled(:string)
|
62
|
+
optional(:username).filled(:string)
|
63
|
+
optional(:password).filled(:string)
|
64
64
|
optional(:options).hash(AnalyzerOptions)
|
65
65
|
end
|
66
66
|
|
67
67
|
Fofa = Dry::Schema.Params do
|
68
68
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Fofa.keys))
|
69
|
-
required(:query).
|
70
|
-
optional(:api_key).
|
71
|
-
optional(:email).
|
69
|
+
required(:query).filled(:string)
|
70
|
+
optional(:api_key).filled(:string)
|
71
|
+
optional(:email).filled(:string)
|
72
72
|
optional(:options).hash(AnalyzerPaginationOptions)
|
73
73
|
end
|
74
74
|
|
75
75
|
PassiveTotal = Dry::Schema.Params do
|
76
76
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::PassiveTotal.keys))
|
77
|
-
required(:query).
|
78
|
-
optional(:username).
|
79
|
-
optional(:api_key).
|
77
|
+
required(:query).filled(:string)
|
78
|
+
optional(:username).filled(:string)
|
79
|
+
optional(:api_key).filled(:string)
|
80
80
|
optional(:options).hash(AnalyzerOptions)
|
81
81
|
end
|
82
82
|
|
83
83
|
ZoomEye = Dry::Schema.Params do
|
84
84
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::ZoomEye.keys))
|
85
|
-
required(:query).
|
85
|
+
required(:query).filled(:string)
|
86
86
|
required(:type).value(Types::String.enum("host", "web"))
|
87
87
|
optional(:options).hash(AnalyzerPaginationOptions)
|
88
88
|
end
|
89
89
|
|
90
90
|
Crtsh = Dry::Schema.Params do
|
91
91
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Crtsh.keys))
|
92
|
-
required(:query).
|
92
|
+
required(:query).filled(:string)
|
93
93
|
optional(:exclude_expired).value(:bool).default(true)
|
94
94
|
optional(:match).value(Types::String.enum("=", "ILIKE", "LIKE", "single", "any", "FTS")).default(nil)
|
95
95
|
optional(:options).hash(AnalyzerOptions)
|
@@ -97,22 +97,22 @@ module Mihari
|
|
97
97
|
|
98
98
|
HunterHow = Dry::Schema.Params do
|
99
99
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::HunterHow.keys))
|
100
|
-
required(:query).
|
100
|
+
required(:query).filled(:string)
|
101
101
|
required(:start_time).value(:date)
|
102
102
|
required(:end_time).value(:date)
|
103
|
-
optional(:api_key).
|
103
|
+
optional(:api_key).filled(:string)
|
104
104
|
optional(:options).hash(AnalyzerPaginationOptions)
|
105
105
|
end
|
106
106
|
|
107
107
|
Feed = Dry::Schema.Params do
|
108
108
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Feed.keys))
|
109
|
-
required(:query).
|
110
|
-
required(:selector).
|
109
|
+
required(:query).filled(:string)
|
110
|
+
required(:selector).filled(:string)
|
111
111
|
optional(:method).value(Types::HTTPRequestMethods).default("GET")
|
112
|
-
optional(:headers).
|
113
|
-
optional(:params).
|
114
|
-
optional(:form).
|
115
|
-
optional(:json).
|
112
|
+
optional(:headers).filled(:hash)
|
113
|
+
optional(:params).filled(:hash)
|
114
|
+
optional(:form).filled(:hash)
|
115
|
+
optional(:json).filled(:hash)
|
116
116
|
optional(:options).hash(AnalyzerOptions)
|
117
117
|
end
|
118
118
|
end
|
@@ -15,31 +15,31 @@ module Mihari
|
|
15
15
|
|
16
16
|
MISP = Dry::Schema.Params do
|
17
17
|
required(:emitter).value(Types::String.enum(*Mihari::Emitters::MISP.keys))
|
18
|
-
optional(:url).
|
19
|
-
optional(:api_key).
|
18
|
+
optional(:url).filled(:string)
|
19
|
+
optional(:api_key).filled(:string)
|
20
20
|
optional(:options).hash(Options)
|
21
21
|
end
|
22
22
|
|
23
23
|
TheHive = Dry::Schema.Params do
|
24
24
|
required(:emitter).value(Types::String.enum(*Mihari::Emitters::TheHive.keys))
|
25
|
-
optional(:url).
|
26
|
-
optional(:api_key).
|
25
|
+
optional(:url).filled(:string)
|
26
|
+
optional(:api_key).filled(:string)
|
27
27
|
optional(:options).hash(Options)
|
28
28
|
end
|
29
29
|
|
30
30
|
Slack = Dry::Schema.Params do
|
31
31
|
required(:emitter).value(Types::String.enum(*Mihari::Emitters::Slack.keys))
|
32
|
-
optional(:webhook_url).
|
33
|
-
optional(:channel).
|
32
|
+
optional(:webhook_url).filled(:string)
|
33
|
+
optional(:channel).filled(:string)
|
34
34
|
optional(:options).hash(Options)
|
35
35
|
end
|
36
36
|
|
37
37
|
Webhook = Dry::Schema.Params do
|
38
38
|
required(:emitter).value(Types::String.enum(*Mihari::Emitters::Webhook.keys))
|
39
|
-
required(:url).
|
39
|
+
required(:url).filled(:string)
|
40
40
|
optional(:method).value(Types::HTTPRequestMethods).default("POST")
|
41
|
-
optional(:headers).
|
42
|
-
optional(:template).
|
41
|
+
optional(:headers).filled(:hash)
|
42
|
+
optional(:template).filled(:string)
|
43
43
|
optional(:options).hash(Options)
|
44
44
|
end
|
45
45
|
end
|
@@ -8,9 +8,9 @@ module Dry
|
|
8
8
|
# (see https://github.com/dry-rb/dry-schema/issues/70)
|
9
9
|
#
|
10
10
|
class DSL
|
11
|
-
def default(
|
11
|
+
def default(artifact)
|
12
12
|
schema_dsl.before(:rule_applier) do |result|
|
13
|
-
result.update(name =>
|
13
|
+
result.update(name => artifact) if result.output && !result[name]
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -7,17 +7,14 @@ module Mihari
|
|
7
7
|
optional(:retry_interval).value(:integer).default(Mihari.config.retry_interval)
|
8
8
|
optional(:retry_exponential_backoff).value(:bool).default(Mihari.config.retry_exponential_backoff)
|
9
9
|
optional(:timeout).value(:integer)
|
10
|
+
optional(:parallel).value(:bool).default(Mihari.config.parallel)
|
10
11
|
end
|
11
12
|
|
12
13
|
IgnoreErrorOptions = Dry::Schema.Params do
|
13
14
|
optional(:ignore_error).value(:bool).default(Mihari.config.ignore_error)
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
-
optional(:parallel).value(:bool).default(Mihari.config.parallel)
|
18
|
-
end
|
19
|
-
|
20
|
-
AnalyzerOptions = Options | IgnoreErrorOptions | ParallelOptions
|
17
|
+
AnalyzerOptions = Options | IgnoreErrorOptions
|
21
18
|
|
22
19
|
PaginationOptions = Dry::Schema.Params do
|
23
20
|
optional(:pagination_interval).value(:integer).default(Mihari.config.pagination_interval)
|
data/lib/mihari/schemas/rule.rb
CHANGED
@@ -7,27 +7,27 @@ require "mihari/schemas/enricher"
|
|
7
7
|
module Mihari
|
8
8
|
module Schemas
|
9
9
|
Rule = Dry::Schema.Params do
|
10
|
-
required(:id).
|
11
|
-
required(:title).
|
12
|
-
required(:description).
|
10
|
+
required(:id).filled(:string)
|
11
|
+
required(:title).filled(:string)
|
12
|
+
required(:description).filled(:string)
|
13
13
|
|
14
|
-
optional(:
|
14
|
+
optional(:author).filled(:string)
|
15
|
+
optional(:status).filled(:string)
|
15
16
|
|
16
|
-
optional(:
|
17
|
-
optional(:references).
|
18
|
-
optional(:related).
|
19
|
-
optional(:status).value(:string)
|
17
|
+
optional(:tags).array { filled(:string) }.default([])
|
18
|
+
optional(:references).array { filled(:string) }
|
19
|
+
optional(:related).array { filled(:string) }
|
20
20
|
|
21
21
|
optional(:created_on).value(:date)
|
22
22
|
optional(:updated_on).value(:date)
|
23
23
|
|
24
24
|
required(:queries).value(:array).each { Analyzer } # rubocop:disable Lint/Void
|
25
|
-
|
26
25
|
optional(:emitters).value(:array).each { Emitter }.default(DEFAULT_EMITTERS) # rubocop:disable Lint/Void
|
27
26
|
optional(:enrichers).value(:array).each { Enricher }.default(DEFAULT_ENRICHERS) # rubocop:disable Lint/Void
|
28
27
|
|
29
|
-
optional(:data_types).
|
30
|
-
|
28
|
+
optional(:data_types).filled(array[Types::DataTypes]).default(Mihari::Types::DataTypes.values)
|
29
|
+
|
30
|
+
optional(:falsepositives).array { filled(:string) }.default([])
|
31
31
|
|
32
32
|
optional(:artifact_ttl).value(:integer)
|
33
33
|
end
|
@@ -42,7 +42,14 @@ module Mihari
|
|
42
42
|
|
43
43
|
rule(:falsepositives) do
|
44
44
|
value.each do |v|
|
45
|
-
key.failure("#{v} is not a valid format
|
45
|
+
key.failure("#{v} is not a valid format") unless valid_falsepositive?(v)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
rule(:emitters) do
|
50
|
+
emitters = value.filter_map { |v| v[:emitter] }
|
51
|
+
unless emitters.include?(Mihari::Emitters::Database.key)
|
52
|
+
key.failure("Emitter:#{Mihari::Emitters::Database.key} should be included in emitters")
|
46
53
|
end
|
47
54
|
end
|
48
55
|
end
|
@@ -20,139 +20,5 @@ module Mihari
|
|
20
20
|
Rule.from_yaml ERB.new(File.read(path_or_id)).result
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
24
|
-
#
|
25
|
-
# Autonomous system builder
|
26
|
-
#
|
27
|
-
class AutonomousSystemBuilder < Service
|
28
|
-
#
|
29
|
-
# @param [String] ip
|
30
|
-
# @param [Mihari::Enrichers::MMDB] enricher
|
31
|
-
#
|
32
|
-
# @return [Mihari::Models::AutonomousSystem, nil]
|
33
|
-
#
|
34
|
-
def call(ip, enricher: Enrichers::MMDB.new)
|
35
|
-
enricher.result(ip).fmap do |res|
|
36
|
-
Models::AutonomousSystem.new(asn: res.asn) if res.asn
|
37
|
-
end.value_or nil
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
#
|
42
|
-
# CPE builder
|
43
|
-
#
|
44
|
-
class CPEBuilder < Service
|
45
|
-
#
|
46
|
-
# Build CPEs
|
47
|
-
#
|
48
|
-
# @param [String] ip
|
49
|
-
# @param [Mihari::Enrichers::Shodan] enricher
|
50
|
-
#
|
51
|
-
# @return [Array<Mihari::Models::CPE>]
|
52
|
-
#
|
53
|
-
def call(ip, enricher: Enrichers::Shodan.new)
|
54
|
-
enricher.result(ip).fmap do |res|
|
55
|
-
(res&.cpes || []).map { |cpe| Models::CPE.new(cpe: cpe) }
|
56
|
-
end.value_or []
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
#
|
61
|
-
# DNS record builder
|
62
|
-
#
|
63
|
-
class DnsRecordBuilder < Service
|
64
|
-
#
|
65
|
-
# Build DNS records
|
66
|
-
#
|
67
|
-
# @param [String] domain
|
68
|
-
# @param [Mihari::Enrichers::Shodan] enricher
|
69
|
-
#
|
70
|
-
# @return [Array<Mihari::Models::DnsRecord>]
|
71
|
-
#
|
72
|
-
def call(domain, enricher: Enrichers::GooglePublicDNS.new)
|
73
|
-
enricher.result(domain).fmap do |res|
|
74
|
-
res.answers.map { |answer| Models::DnsRecord.new(resource: answer.resource_type, value: answer.data) }
|
75
|
-
end.value_or []
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
#
|
80
|
-
# Geolocation builder
|
81
|
-
#
|
82
|
-
class GeolocationBuilder < Service
|
83
|
-
#
|
84
|
-
# Build Geolocation
|
85
|
-
#
|
86
|
-
# @param [String] ip
|
87
|
-
# @param [Mihari::Enrichers::MMDB] enricher
|
88
|
-
#
|
89
|
-
# @return [Mihari::Models::Geolocation, nil]
|
90
|
-
#
|
91
|
-
def call(ip, enricher: Enrichers::MMDB.new)
|
92
|
-
enricher.result(ip).fmap do |res|
|
93
|
-
if res.country_code
|
94
|
-
Models::Geolocation.new(
|
95
|
-
country: NormalizeCountry(res.country_code, to: :short),
|
96
|
-
country_code: res.country_code
|
97
|
-
)
|
98
|
-
end
|
99
|
-
end.value_or nil
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
#
|
104
|
-
# Port builder
|
105
|
-
#
|
106
|
-
class PortBuilder < Service
|
107
|
-
#
|
108
|
-
# Build ports
|
109
|
-
#
|
110
|
-
# @param [String] ip
|
111
|
-
# @param [Mihari::Enrichers::Shodan] enricher
|
112
|
-
#
|
113
|
-
# @return [Array<Mihari::Models::Port>]
|
114
|
-
#
|
115
|
-
def call(ip, enricher: Enrichers::Shodan.new)
|
116
|
-
enricher.result(ip).fmap do |res|
|
117
|
-
(res&.ports || []).map { |port| Models::Port.new(port: port) }
|
118
|
-
end.value_or []
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
#
|
123
|
-
# Reverse DNS name builder
|
124
|
-
#
|
125
|
-
class ReverseDnsNameBuilder < Service
|
126
|
-
#
|
127
|
-
# Build reverse DNS names
|
128
|
-
#
|
129
|
-
# @param [String] ip
|
130
|
-
# @param [Mihari::Enrichers::Shodan] enricher
|
131
|
-
#
|
132
|
-
# @return [Array<Mihari::Models::ReverseDnsName>]
|
133
|
-
#
|
134
|
-
def call(ip, enricher: Enrichers::Shodan.new)
|
135
|
-
enricher.result(ip).fmap do |res|
|
136
|
-
(res&.hostnames || []).map { |name| Models::ReverseDnsName.new(name: name) }
|
137
|
-
end.value_or []
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
#
|
142
|
-
# Whois record builder
|
143
|
-
#
|
144
|
-
class WhoisRecordBuilder < Service
|
145
|
-
#
|
146
|
-
# Build whois record
|
147
|
-
#
|
148
|
-
# @param [String] domain
|
149
|
-
# @param [Mihari::Enrichers::Whois] enricher
|
150
|
-
#
|
151
|
-
# @return [Mihari::Models::WhoisRecord, nil]
|
152
|
-
#
|
153
|
-
def call(domain, enricher: Enrichers::Whois.new)
|
154
|
-
enricher.result(domain).value_or nil
|
155
|
-
end
|
156
|
-
end
|
157
23
|
end
|
158
24
|
end
|
data/lib/mihari/services/feed.rb
CHANGED
@@ -93,12 +93,9 @@ module Mihari
|
|
93
93
|
#
|
94
94
|
# @param [Object] read_data
|
95
95
|
def call(input_enumerator, selector)
|
96
|
-
parsed =
|
97
|
-
$SAFE = 1
|
98
|
-
input_enumerator.instance_eval(selector)
|
99
|
-
end.call
|
96
|
+
parsed = input_enumerator.instance_eval(selector)
|
100
97
|
|
101
|
-
raise TypeError unless parsed.all?(String)
|
98
|
+
raise TypeError unless parsed.is_a?(Array) || parsed.all?(String)
|
102
99
|
|
103
100
|
parsed
|
104
101
|
end
|
@@ -79,10 +79,10 @@ module Mihari
|
|
79
79
|
# @return [Mihari::Rule]
|
80
80
|
#
|
81
81
|
def rule
|
82
|
-
@rule ||=
|
82
|
+
@rule ||= lambda do
|
83
83
|
data = Mihari::Models::Rule.find(rule_id).data
|
84
|
-
|
85
|
-
end.
|
84
|
+
Rule.new(**data)
|
85
|
+
end.call
|
86
86
|
end
|
87
87
|
end
|
88
88
|
end
|
@@ -14,7 +14,7 @@ module Mihari
|
|
14
14
|
# @return [Mihari::AutonomousSystem]
|
15
15
|
#
|
16
16
|
def as
|
17
|
-
Mihari::Models::AutonomousSystem.new(
|
17
|
+
Mihari::Models::AutonomousSystem.new(number: normalize_asn(asn))
|
18
18
|
end
|
19
19
|
|
20
20
|
class << self
|
@@ -76,7 +76,7 @@ module Mihari
|
|
76
76
|
# @return [Mihari::Port]
|
77
77
|
#
|
78
78
|
def _port
|
79
|
-
Models::Port.new(
|
79
|
+
Models::Port.new(number: port)
|
80
80
|
end
|
81
81
|
|
82
82
|
class << self
|