mihari 5.4.9 → 5.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/analyzers/binaryedge.md +2 -2
- data/docs/analyzers/censys.md +3 -3
- data/docs/analyzers/circl.md +3 -3
- data/docs/analyzers/crtsh.md +2 -2
- data/docs/analyzers/dnstwister.md +1 -1
- data/docs/analyzers/feed.md +7 -7
- data/docs/analyzers/greynoise.md +2 -2
- data/docs/analyzers/hunterhow.md +4 -4
- data/docs/analyzers/index.md +13 -8
- data/docs/analyzers/onyphe.md +2 -2
- data/docs/analyzers/otx.md +2 -2
- data/docs/analyzers/passivetotal.md +7 -3
- data/docs/analyzers/pulsedive.md +2 -2
- data/docs/analyzers/securitytrails.md +6 -2
- data/docs/analyzers/shodan.md +2 -2
- data/docs/analyzers/urlscan.md +2 -2
- data/docs/analyzers/virustotal.md +6 -2
- data/docs/analyzers/virustotal_intelligence.md +6 -2
- data/docs/analyzers/zoomeye.md +3 -3
- data/docs/emitters/hive.md +4 -4
- data/docs/emitters/index.md +29 -0
- data/docs/emitters/misp.md +2 -2
- data/docs/emitters/slack.md +2 -7
- data/docs/emitters/webhook.md +4 -4
- data/docs/enrichers/index.md +29 -0
- data/docs/enrichers/ipinfo.md +7 -0
- data/docs/index.md +0 -2
- data/docs/installation.md +1 -1
- data/docs/rule.md +12 -15
- data/docs/usage.md +5 -2
- data/frontend/package-lock.json +294 -2772
- data/frontend/package.json +10 -10
- data/frontend/src/components/ErrorMessage.vue +0 -1
- data/frontend/src/components/alert/Alerts.vue +0 -1
- data/frontend/src/components/alert/AlertsWithPagination.vue +0 -1
- data/frontend/src/components/alert/AlertsWrapper.vue +0 -6
- data/frontend/src/components/alert/Form.vue +1 -3
- data/frontend/src/components/artifact/Artifact.vue +0 -17
- data/frontend/src/components/artifact/ArtifactWrapper.vue +0 -2
- data/frontend/src/components/artifact/WhoisRecord.vue +0 -3
- data/frontend/src/components/config/ConfigsWrapper.vue +0 -2
- data/frontend/src/components/rule/EditRule.vue +0 -3
- data/frontend/src/components/rule/EditRuleWrapper.vue +0 -2
- data/frontend/src/components/rule/Form.vue +1 -3
- data/frontend/src/components/rule/NewRule.vue +0 -3
- data/frontend/src/components/rule/Rule.vue +1 -7
- data/frontend/src/components/rule/RuleWrapper.vue +0 -2
- data/frontend/src/components/rule/RulesWrapper.vue +0 -6
- data/frontend/src/swagger.yaml +254 -254
- data/lib/mihari/analyzers/base.rb +7 -37
- data/lib/mihari/analyzers/binaryedge.rb +5 -1
- data/lib/mihari/analyzers/censys.rb +6 -1
- data/lib/mihari/analyzers/greynoise.rb +5 -1
- data/lib/mihari/analyzers/hunterhow.rb +5 -1
- data/lib/mihari/analyzers/onyphe.rb +5 -1
- data/lib/mihari/analyzers/passivetotal.rb +9 -0
- data/lib/mihari/analyzers/pulsedive.rb +1 -1
- data/lib/mihari/analyzers/rule.rb +55 -54
- data/lib/mihari/analyzers/securitytrails.rb +9 -0
- data/lib/mihari/analyzers/shodan.rb +5 -1
- data/lib/mihari/analyzers/urlscan.rb +5 -1
- data/lib/mihari/analyzers/virustotal.rb +11 -2
- data/lib/mihari/analyzers/virustotal_intelligence.rb +21 -1
- data/lib/mihari/analyzers/zoomeye.rb +7 -3
- data/lib/mihari/base.rb +69 -0
- data/lib/mihari/cli/main.rb +36 -0
- data/lib/mihari/clients/base.rb +7 -7
- data/lib/mihari/clients/binaryedge.rb +10 -4
- data/lib/mihari/clients/censys.rb +11 -4
- data/lib/mihari/clients/greynoise.rb +10 -4
- data/lib/mihari/clients/hunterhow.rb +10 -4
- data/lib/mihari/clients/misp.rb +3 -2
- data/lib/mihari/clients/onyphe.rb +10 -4
- data/lib/mihari/clients/shodan.rb +10 -4
- data/lib/mihari/clients/the_hive.rb +3 -2
- data/lib/mihari/clients/urlscan.rb +9 -3
- data/lib/mihari/clients/virustotal.rb +10 -4
- data/lib/mihari/clients/zoomeye.rb +11 -5
- data/lib/mihari/commands/alert.rb +6 -33
- data/lib/mihari/commands/rule.rb +7 -12
- data/lib/mihari/commands/search.rb +10 -38
- data/lib/mihari/config.rb +8 -0
- data/lib/mihari/constants.rb +3 -3
- data/lib/mihari/emitters/base.rb +22 -15
- data/lib/mihari/emitters/database.rb +1 -1
- data/lib/mihari/emitters/misp.rb +7 -6
- data/lib/mihari/emitters/slack.rb +24 -6
- data/lib/mihari/emitters/the_hive.rb +8 -7
- data/lib/mihari/emitters/webhook.rb +31 -29
- data/lib/mihari/enrichers/base.rb +25 -19
- data/lib/mihari/enrichers/google_public_dns.rb +38 -38
- data/lib/mihari/enrichers/ipinfo.rb +32 -34
- data/lib/mihari/enrichers/shodan.rb +18 -26
- data/lib/mihari/enrichers/whois.rb +121 -111
- data/lib/mihari/mixins/retriable.rb +4 -2
- data/lib/mihari/models/artifact.rb +37 -23
- data/lib/mihari/models/autonomous_system.rb +3 -2
- data/lib/mihari/models/cpe.rb +3 -2
- data/lib/mihari/models/dns.rb +3 -2
- data/lib/mihari/models/geolocation.rb +3 -2
- data/lib/mihari/models/port.rb +3 -2
- data/lib/mihari/models/reverse_dns.rb +3 -2
- data/lib/mihari/models/whois.rb +4 -3
- data/lib/mihari/schemas/analyzer.rb +24 -23
- data/lib/mihari/schemas/emitter.rb +32 -25
- data/lib/mihari/schemas/enricher.rb +21 -2
- data/lib/mihari/schemas/options.rb +27 -0
- data/lib/mihari/schemas/rule.rb +8 -4
- data/lib/mihari/services/alert_runner.rb +1 -1
- data/lib/mihari/services/rule_runner.rb +1 -11
- data/lib/mihari/types.rb +1 -14
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/ip_addresses.rb +1 -1
- data/lib/mihari/web/public/assets/{index-33165282.css → index-56fc2187.css} +1 -1
- data/lib/mihari/web/public/assets/index-9cc489e6.js +1749 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +400 -400
- data/lib/mihari.rb +67 -37
- data/mihari.gemspec +3 -2
- data/mkdocs.yml +8 -6
- data/requirements.txt +1 -1
- metadata +24 -8
- data/lib/mihari/web/public/assets/index-a92abd57.js +0 -1740
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
module Analyzers
|
5
|
-
class Base
|
5
|
+
class Base < Mihari::Base
|
6
6
|
include Dry::Monads[:result, :try]
|
7
7
|
|
8
8
|
include Mixins::Configurable
|
@@ -11,37 +11,21 @@ module Mihari
|
|
11
11
|
# @return [String]
|
12
12
|
attr_reader :query
|
13
13
|
|
14
|
-
# @return [Hash]
|
15
|
-
attr_reader :options
|
16
|
-
|
17
14
|
#
|
18
15
|
# @param [String] query
|
19
16
|
# @param [Hash, nil] options
|
20
17
|
#
|
21
18
|
def initialize(query, options: nil)
|
22
|
-
|
23
|
-
@options = options || {}
|
24
|
-
end
|
25
|
-
|
26
|
-
#
|
27
|
-
# @return [Integer, nil]
|
28
|
-
#
|
29
|
-
def interval
|
30
|
-
options[:interval]
|
31
|
-
end
|
19
|
+
super(options: options)
|
32
20
|
|
33
|
-
|
34
|
-
# @return [Integer]
|
35
|
-
#
|
36
|
-
def retry_interval
|
37
|
-
options[:retry_interval] || Mihari.config.retry_interval
|
21
|
+
@query = query
|
38
22
|
end
|
39
23
|
|
40
24
|
#
|
41
25
|
# @return [Integer]
|
42
26
|
#
|
43
|
-
def
|
44
|
-
options[:
|
27
|
+
def pagination_interval
|
28
|
+
options[:pagination_interval] || Mihari.config.pagination_interval
|
45
29
|
end
|
46
30
|
|
47
31
|
#
|
@@ -61,13 +45,6 @@ module Mihari
|
|
61
45
|
Mihari.config.ignore_error
|
62
46
|
end
|
63
47
|
|
64
|
-
#
|
65
|
-
# @return [Integer, nil]
|
66
|
-
#
|
67
|
-
def timeout
|
68
|
-
options[:timeout]
|
69
|
-
end
|
70
|
-
|
71
48
|
# @return [Array<String>, Array<Mihari::Artifact>]
|
72
49
|
def artifacts
|
73
50
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
@@ -81,12 +58,12 @@ module Mihari
|
|
81
58
|
# @return [Array<Mihari::Artifact>]
|
82
59
|
#
|
83
60
|
def normalized_artifacts
|
84
|
-
retry_on_error(times: retry_times, interval: retry_interval) do
|
61
|
+
retry_on_error(times: retry_times, interval: retry_interval, exponential_backoff: retry_exponential_backoff) do
|
85
62
|
artifacts.compact.sort.map do |artifact|
|
86
63
|
# No need to set data_type manually
|
87
64
|
# It is set automatically in #initialize
|
88
65
|
artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact)
|
89
|
-
artifact.source =
|
66
|
+
artifact.source = self.class.class_key
|
90
67
|
artifact
|
91
68
|
end.select(&:valid?).uniq(&:data)
|
92
69
|
end
|
@@ -99,13 +76,6 @@ module Mihari
|
|
99
76
|
Try[StandardError] { normalized_artifacts }.to_result
|
100
77
|
end
|
101
78
|
|
102
|
-
# @return [String]
|
103
|
-
def class_name
|
104
|
-
self.class.to_s.split("::").last
|
105
|
-
end
|
106
|
-
|
107
|
-
alias source class_name
|
108
|
-
|
109
79
|
class << self
|
110
80
|
#
|
111
81
|
# Initialize an analyzer by query params
|
@@ -32,7 +32,11 @@ module Mihari
|
|
32
32
|
# @return [Mihari::Clients::BinaryEdge]
|
33
33
|
#
|
34
34
|
def client
|
35
|
-
@client ||= Clients::BinaryEdge.new(
|
35
|
+
@client ||= Clients::BinaryEdge.new(
|
36
|
+
api_key: api_key,
|
37
|
+
pagination_interval: pagination_interval,
|
38
|
+
timeout: timeout
|
39
|
+
)
|
36
40
|
end
|
37
41
|
end
|
38
42
|
end
|
@@ -52,7 +52,12 @@ module Mihari
|
|
52
52
|
# @return [Mihari::Clients::Censys]
|
53
53
|
#
|
54
54
|
def client
|
55
|
-
@client ||= Clients::Censys.new(
|
55
|
+
@client ||= Clients::Censys.new(
|
56
|
+
id: id,
|
57
|
+
secret: secret,
|
58
|
+
pagination_interval: pagination_interval,
|
59
|
+
timeout: timeout
|
60
|
+
)
|
56
61
|
end
|
57
62
|
|
58
63
|
#
|
@@ -31,7 +31,11 @@ module Mihari
|
|
31
31
|
private
|
32
32
|
|
33
33
|
def client
|
34
|
-
@client ||= Clients::GreyNoise.new(
|
34
|
+
@client ||= Clients::GreyNoise.new(
|
35
|
+
api_key: api_key,
|
36
|
+
pagination_interval: pagination_interval,
|
37
|
+
timeout: timeout
|
38
|
+
)
|
35
39
|
end
|
36
40
|
end
|
37
41
|
end
|
@@ -46,7 +46,11 @@ module Mihari
|
|
46
46
|
private
|
47
47
|
|
48
48
|
def client
|
49
|
-
@client ||= Clients::HunterHow.new(
|
49
|
+
@client ||= Clients::HunterHow.new(
|
50
|
+
api_key: api_key,
|
51
|
+
pagination_interval: pagination_interval,
|
52
|
+
timeout: timeout
|
53
|
+
)
|
50
54
|
end
|
51
55
|
end
|
52
56
|
end
|
@@ -33,7 +33,11 @@ module Mihari
|
|
33
33
|
private
|
34
34
|
|
35
35
|
def client
|
36
|
-
@client ||= Clients::Onyphe.new(
|
36
|
+
@client ||= Clients::Onyphe.new(
|
37
|
+
api_key: api_key,
|
38
|
+
pagination_interval: pagination_interval,
|
39
|
+
timeout: timeout
|
40
|
+
)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -2,50 +2,17 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
module Analyzers
|
5
|
-
ANALYZER_TO_CLASS = {
|
6
|
-
"binaryedge" => BinaryEdge,
|
7
|
-
"censys" => Censys,
|
8
|
-
"circl" => CIRCL,
|
9
|
-
"crtsh" => Crtsh,
|
10
|
-
"dnstwister" => DNSTwister,
|
11
|
-
"feed" => Feed,
|
12
|
-
"greynoise" => GreyNoise,
|
13
|
-
"hunterhow" => HunterHow,
|
14
|
-
"onyphe" => Onyphe,
|
15
|
-
"otx" => OTX,
|
16
|
-
"passivetotal" => PassiveTotal,
|
17
|
-
"pt" => PassiveTotal,
|
18
|
-
"pulsedive" => Pulsedive,
|
19
|
-
"securitytrails" => SecurityTrails,
|
20
|
-
"shodan" => Shodan,
|
21
|
-
"st" => SecurityTrails,
|
22
|
-
"urlscan" => Urlscan,
|
23
|
-
"virustotal_intelligence" => VirusTotalIntelligence,
|
24
|
-
"virustotal" => VirusTotal,
|
25
|
-
"vt_intel" => VirusTotalIntelligence,
|
26
|
-
"vt" => VirusTotal,
|
27
|
-
"zoomeye" => ZoomEye
|
28
|
-
}.freeze
|
29
|
-
|
30
|
-
EMITTER_TO_CLASS = {
|
31
|
-
"database" => Emitters::Database,
|
32
|
-
"misp" => Emitters::MISP,
|
33
|
-
"slack" => Emitters::Slack,
|
34
|
-
"the_hive" => Emitters::TheHive,
|
35
|
-
"webhook" => Emitters::Webhook
|
36
|
-
}.freeze
|
37
|
-
|
38
5
|
class Rule
|
39
6
|
include Mixins::FalsePositive
|
40
7
|
|
41
|
-
# @return [Mihari::Services::
|
8
|
+
# @return [Mihari::Services::RuleProxy]
|
42
9
|
attr_reader :rule
|
43
10
|
|
44
11
|
# @return [Time]
|
45
12
|
attr_reader :base_time
|
46
13
|
|
47
14
|
#
|
48
|
-
# @param [Mihari::Services::
|
15
|
+
# @param [Mihari::Services::RuleProxy] rule
|
49
16
|
#
|
50
17
|
def initialize(rule)
|
51
18
|
@rule = rule
|
@@ -106,7 +73,7 @@ module Mihari
|
|
106
73
|
#
|
107
74
|
def enriched_artifacts
|
108
75
|
@enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
|
109
|
-
|
76
|
+
enrichers.each { |enricher| artifact.enrich_by_enricher enricher }
|
110
77
|
artifact
|
111
78
|
end
|
112
79
|
end
|
@@ -119,8 +86,14 @@ module Mihari
|
|
119
86
|
def bulk_emit
|
120
87
|
return [] if enriched_artifacts.empty?
|
121
88
|
|
122
|
-
|
123
|
-
|
89
|
+
# NOTE: separate parallel execution and logging
|
90
|
+
# because the logger does not work along with Parallel
|
91
|
+
results = Parallel.map(valid_emitters) do |emitter|
|
92
|
+
emitter.result
|
93
|
+
end
|
94
|
+
|
95
|
+
results.zip(valid_emitters).map do |result_and_emitter|
|
96
|
+
result, emitter = result_and_emitter
|
124
97
|
|
125
98
|
Mihari.logger.info "Emission by #{emitter.class} is failed: #{result.failure}" if result.failure?
|
126
99
|
Mihari.logger.info "Emission by #{emitter.class} is succeeded" if result.success?
|
@@ -157,15 +130,14 @@ module Mihari
|
|
157
130
|
#
|
158
131
|
# Get analyzer class
|
159
132
|
#
|
160
|
-
# @param [String]
|
133
|
+
# @param [String] key
|
161
134
|
#
|
162
135
|
# @return [Class<Mihari::Analyzers::Base>] analyzer class
|
163
136
|
#
|
164
|
-
def get_analyzer_class(
|
165
|
-
|
166
|
-
return analyzer if analyzer
|
137
|
+
def get_analyzer_class(key)
|
138
|
+
raise ArgumentError, "#{key} is not supported" unless Mihari.analyzer_to_class.key?(key)
|
167
139
|
|
168
|
-
|
140
|
+
Mihari.analyzer_to_class[key]
|
169
141
|
end
|
170
142
|
|
171
143
|
#
|
@@ -182,29 +154,28 @@ module Mihari
|
|
182
154
|
#
|
183
155
|
# Get emitter class
|
184
156
|
#
|
185
|
-
# @param [String]
|
157
|
+
# @param [String] key
|
186
158
|
#
|
187
159
|
# @return [Class<Mihari::Emitters::Base>] emitter class
|
188
160
|
#
|
189
|
-
def get_emitter_class(
|
190
|
-
|
191
|
-
return emitter if emitter
|
161
|
+
def get_emitter_class(key)
|
162
|
+
raise ArgumentError, "#{key} is not supported" unless Mihari.emitter_to_class.key?(key)
|
192
163
|
|
193
|
-
|
164
|
+
Mihari.emitter_to_class[key]
|
194
165
|
end
|
195
166
|
|
196
|
-
#
|
197
|
-
# Deep copied emitters
|
198
167
|
#
|
199
168
|
# @return [Array<Mihari::Emitters::Base>]
|
200
169
|
#
|
201
170
|
def emitters
|
202
171
|
rule.emitters.map(&:deep_dup).map do |params|
|
203
172
|
name = params[:emitter]
|
204
|
-
params
|
173
|
+
options = params[:options]
|
174
|
+
|
175
|
+
%i[emitter options].each { |key| params.delete key }
|
205
176
|
|
206
177
|
klass = get_emitter_class(name)
|
207
|
-
klass.new(artifacts: enriched_artifacts, rule: rule, **params)
|
178
|
+
klass.new(artifacts: enriched_artifacts, rule: rule, options: options, **params)
|
208
179
|
end
|
209
180
|
end
|
210
181
|
|
@@ -212,7 +183,35 @@ module Mihari
|
|
212
183
|
# @return [Array<Mihari::Emitters::Base>]
|
213
184
|
#
|
214
185
|
def valid_emitters
|
215
|
-
emitters.select(&:valid?)
|
186
|
+
@valid_emitters ||= emitters.select(&:valid?)
|
187
|
+
end
|
188
|
+
|
189
|
+
#
|
190
|
+
# Get enricher class
|
191
|
+
#
|
192
|
+
# @param [String] key
|
193
|
+
#
|
194
|
+
# @return [Class<Mihari::Enrichers::Base>] enricher class
|
195
|
+
#
|
196
|
+
def get_enricher_class(key)
|
197
|
+
raise ArgumentError, "#{key} is not supported" unless Mihari.enricher_to_class.key?(key)
|
198
|
+
|
199
|
+
Mihari.enricher_to_class[key]
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
# @return [Array<Mihari::Enrichers::Base>] enrichers
|
204
|
+
#
|
205
|
+
def enrichers
|
206
|
+
@enrichers ||= rule.enrichers.map(&:deep_dup).map do |params|
|
207
|
+
name = params[:enricher]
|
208
|
+
options = params[:options]
|
209
|
+
|
210
|
+
%i[enricher options].each { |key| params.delete key }
|
211
|
+
|
212
|
+
klass = get_enricher_class(name)
|
213
|
+
klass.new(options: options, **params)
|
214
|
+
end
|
216
215
|
end
|
217
216
|
|
218
217
|
#
|
@@ -222,7 +221,9 @@ module Mihari
|
|
222
221
|
analyzers.map do |analyzer|
|
223
222
|
next if analyzer.configured?
|
224
223
|
|
225
|
-
|
224
|
+
joined = analyzer.configuration_keys.join(", ")
|
225
|
+
be = (analyzer.configuration_keys.length > 1) ? "are" : "is"
|
226
|
+
message = "#{analyzer.class.class_key} is not configured correctly. #{joined} #{be} missing."
|
226
227
|
raise ConfigurationError, message
|
227
228
|
end
|
228
229
|
end
|
@@ -34,7 +34,11 @@ module Mihari
|
|
34
34
|
# @return [Clients::Shodan]
|
35
35
|
#
|
36
36
|
def client
|
37
|
-
@client ||= Clients::Shodan.new(
|
37
|
+
@client ||= Clients::Shodan.new(
|
38
|
+
api_key: api_key,
|
39
|
+
pagination_interval: pagination_interval,
|
40
|
+
timeout: timeout
|
41
|
+
)
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|
@@ -44,7 +44,11 @@ module Mihari
|
|
44
44
|
private
|
45
45
|
|
46
46
|
def client
|
47
|
-
@client ||= Clients::UrlScan.new(
|
47
|
+
@client ||= Clients::UrlScan.new(
|
48
|
+
api_key: api_key,
|
49
|
+
pagination_interval: pagination_interval,
|
50
|
+
timeout: timeout
|
51
|
+
)
|
48
52
|
end
|
49
53
|
|
50
54
|
#
|
@@ -39,6 +39,15 @@ module Mihari
|
|
39
39
|
%w[virustotal_api_key]
|
40
40
|
end
|
41
41
|
|
42
|
+
class << self
|
43
|
+
#
|
44
|
+
# @return [Array<String>, nil]
|
45
|
+
#
|
46
|
+
def key_aliases
|
47
|
+
["vt"]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
42
51
|
private
|
43
52
|
|
44
53
|
def client
|
@@ -65,7 +74,7 @@ module Mihari
|
|
65
74
|
data = res["data"] || []
|
66
75
|
data.filter_map do |item|
|
67
76
|
data = item.dig("attributes", "ip_address")
|
68
|
-
data.nil? ? nil : Artifact.new(data: data,
|
77
|
+
data.nil? ? nil : Artifact.new(data: data, metadata: item)
|
69
78
|
end
|
70
79
|
end
|
71
80
|
|
@@ -80,7 +89,7 @@ module Mihari
|
|
80
89
|
data = res["data"] || []
|
81
90
|
data.filter_map do |item|
|
82
91
|
data = item.dig("attributes", "host_name")
|
83
|
-
Artifact.new(data: data,
|
92
|
+
Artifact.new(data: data, metadata: item)
|
84
93
|
end.uniq
|
85
94
|
end
|
86
95
|
end
|
@@ -25,6 +25,22 @@ module Mihari
|
|
25
25
|
%w[virustotal_api_key]
|
26
26
|
end
|
27
27
|
|
28
|
+
class << self
|
29
|
+
#
|
30
|
+
# @return [String]
|
31
|
+
#
|
32
|
+
def class_key
|
33
|
+
"virustotal_intelligence"
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# @return [Array<String>, nil]
|
38
|
+
#
|
39
|
+
def class_key_aliases
|
40
|
+
["vt_intel"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
28
44
|
private
|
29
45
|
|
30
46
|
#
|
@@ -33,7 +49,11 @@ module Mihari
|
|
33
49
|
# @return [::VirusTotal::API]
|
34
50
|
#
|
35
51
|
def client
|
36
|
-
@client = Clients::VirusTotal.new(
|
52
|
+
@client = Clients::VirusTotal.new(
|
53
|
+
api_key: api_key,
|
54
|
+
pagination_interval: pagination_interval,
|
55
|
+
timeout: timeout
|
56
|
+
)
|
37
57
|
end
|
38
58
|
end
|
39
59
|
end
|
@@ -53,7 +53,11 @@ module Mihari
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def client
|
56
|
-
@client ||= Clients::ZoomEye.new(
|
56
|
+
@client ||= Clients::ZoomEye.new(
|
57
|
+
api_key: api_key,
|
58
|
+
pagination_interval: pagination_interval,
|
59
|
+
timeout: timeout
|
60
|
+
)
|
57
61
|
end
|
58
62
|
|
59
63
|
#
|
@@ -69,9 +73,9 @@ module Mihari
|
|
69
73
|
data = match["ip"]
|
70
74
|
|
71
75
|
if data.is_a?(Array)
|
72
|
-
data.map { |d| Artifact.new(data: d,
|
76
|
+
data.map { |d| Artifact.new(data: d, metadata: match) }
|
73
77
|
else
|
74
|
-
Artifact.new(data: data,
|
78
|
+
Artifact.new(data: data, metadata: match)
|
75
79
|
end
|
76
80
|
end.flatten
|
77
81
|
end
|
data/lib/mihari/base.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
#
|
5
|
+
# Base class for Analyzer, Emitter and Enricher
|
6
|
+
#
|
7
|
+
class Base
|
8
|
+
# @return [Hash]
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param [Hash, nil] options
|
13
|
+
#
|
14
|
+
def initialize(*_args, options: nil, **_kwargs)
|
15
|
+
@options = options || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# @return [Integer]
|
20
|
+
#
|
21
|
+
def retry_interval
|
22
|
+
options[:retry_interval] || Mihari.config.retry_interval
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# @return [Boolean]
|
27
|
+
#
|
28
|
+
def retry_exponential_backoff
|
29
|
+
options[:retry_exponential_backoff] || Mihari.config.retry_exponential_backoff
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# @return [Integer]
|
34
|
+
#
|
35
|
+
def retry_times
|
36
|
+
options[:retry_times] || Mihari.config.retry_times
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# @return [Integer, nil]
|
41
|
+
#
|
42
|
+
def timeout
|
43
|
+
options[:timeout]
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
#
|
48
|
+
# @return [String]
|
49
|
+
#
|
50
|
+
def class_key
|
51
|
+
to_s.split("::").last
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# @return [Array<String>, nil]
|
56
|
+
#
|
57
|
+
def class_key_aliases
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# @return [Array<String>]
|
63
|
+
#
|
64
|
+
def class_keys
|
65
|
+
([class_key] + [class_key_aliases]).flatten.compact
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/mihari/cli/main.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "thor"
|
4
|
+
require "thor/hollaback"
|
4
5
|
|
5
6
|
# Commands
|
6
7
|
require "mihari/commands/alert"
|
@@ -19,10 +20,45 @@ require "mihari/cli/rule"
|
|
19
20
|
module Mihari
|
20
21
|
module CLI
|
21
22
|
class Main < Base
|
23
|
+
class_option :debug, desc: "Sets up debug mode", aliases: ["-d"], type: :boolean
|
24
|
+
class_around :safe_execute
|
25
|
+
|
22
26
|
include Mihari::Commands::Search
|
23
27
|
include Mihari::Commands::Version
|
24
28
|
include Mihari::Commands::Web
|
25
29
|
|
30
|
+
no_commands do
|
31
|
+
def unwrap_error(err)
|
32
|
+
return err unless err.is_a?(Dry::Monads::UnwrapError)
|
33
|
+
|
34
|
+
# NOTE: UnwrapError's receiver can be either of:
|
35
|
+
# - Dry::Monads::Try::Error
|
36
|
+
# - Dry::Monads::Result::Failure
|
37
|
+
receiver = err.receiver
|
38
|
+
return receiver.exception if receiver.is_a?(Dry::Monads::Try::Error)
|
39
|
+
|
40
|
+
receiver.failure
|
41
|
+
end
|
42
|
+
|
43
|
+
def safe_execute
|
44
|
+
yield
|
45
|
+
rescue StandardError => e
|
46
|
+
err = unwrap_error(e)
|
47
|
+
|
48
|
+
raise err if options["debug"]
|
49
|
+
|
50
|
+
case err
|
51
|
+
when ValidationError
|
52
|
+
warn JSON.pretty_generate(err.errors.to_h)
|
53
|
+
when StandardError
|
54
|
+
Sentry.capture_exception(err) if Sentry.initialized?
|
55
|
+
warn err
|
56
|
+
end
|
57
|
+
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
26
62
|
desc "db", "Sub commands for DB"
|
27
63
|
subcommand "db", Database
|
28
64
|
|
data/lib/mihari/clients/base.rb
CHANGED
@@ -9,8 +9,8 @@ module Mihari
|
|
9
9
|
# @return [Hash]
|
10
10
|
attr_reader :headers
|
11
11
|
|
12
|
-
# @return [Integer
|
13
|
-
attr_reader :
|
12
|
+
# @return [Integer]
|
13
|
+
attr_reader :pagination_interval
|
14
14
|
|
15
15
|
# @return [Integer, nil]
|
16
16
|
attr_reader :timeout
|
@@ -18,20 +18,20 @@ module Mihari
|
|
18
18
|
#
|
19
19
|
# @param [String] base_url
|
20
20
|
# @param [Hash] headers
|
21
|
-
# @param [Integer
|
21
|
+
# @param [Integer] interval
|
22
22
|
# @param [Integer, nil] timeout
|
23
23
|
#
|
24
|
-
def initialize(base_url, headers: {},
|
24
|
+
def initialize(base_url, headers: {}, pagination_interval: 0, timeout: nil)
|
25
25
|
@base_url = base_url
|
26
26
|
@headers = headers || {}
|
27
|
-
@
|
27
|
+
@pagination_interval = pagination_interval
|
28
28
|
@timeout = timeout
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
def
|
34
|
-
sleep
|
33
|
+
def sleep_pagination_interval
|
34
|
+
sleep pagination_interval
|
35
35
|
end
|
36
36
|
|
37
37
|
#
|