mihari 7.1.2 → 7.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/Rakefile +15 -7
- data/build_frontend.sh +1 -1
- data/lefthook.yml +4 -1
- data/lib/mihari/actor.rb +21 -4
- data/lib/mihari/analyzers/base.rb +7 -18
- 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 +4 -4
- 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/whois.rb +5 -7
- 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 +16 -1
- data/lib/mihari/models/artifact.rb +65 -30
- data/lib/mihari/models/vulnerability.rb +12 -0
- data/lib/mihari/rule.rb +18 -24
- data/lib/mihari/schemas/rule.rb +7 -0
- data/lib/mihari/services/builders.rb +22 -3
- data/lib/mihari/services/enrichers.rb +2 -0
- data/lib/mihari/services/feed.rb +2 -5
- data/lib/mihari/services/proxies.rb +3 -3
- data/lib/mihari/structs/censys.rb +2 -2
- data/lib/mihari/structs/config.rb +3 -20
- 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-Guw2aMpk.js → index-GWurHG1o.js} +60 -40
- 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 +385 -385
- data/lib/mihari.rb +3 -0
- metadata +11 -51
- data/test.json.jbuilder +0 -7
data/lib/mihari/entities/port.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Entities
|
5
5
|
class Port < Grape::Entity
|
6
|
-
expose :
|
6
|
+
expose :number, documentation: { type: Integer, required: true }
|
7
7
|
expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
|
8
8
|
end
|
9
9
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Entities
|
5
|
+
class Vulnerability < Grape::Entity
|
6
|
+
expose :name, documentation: { type: String, required: true }
|
7
|
+
expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/mihari/errors.rb
CHANGED
@@ -7,7 +7,22 @@ module Mihari
|
|
7
7
|
|
8
8
|
class ValueError < Error; end
|
9
9
|
|
10
|
-
class
|
10
|
+
class UnenrichableError < Error; end
|
11
|
+
|
12
|
+
class ConfigurationError < Error
|
13
|
+
# @return [Array<String>, nil]
|
14
|
+
attr_reader :detail
|
15
|
+
|
16
|
+
#
|
17
|
+
# @param [String] msg
|
18
|
+
# @param [Array<String>, nil] detail
|
19
|
+
#
|
20
|
+
def initialize(msg, detail)
|
21
|
+
super(msg)
|
22
|
+
|
23
|
+
@detail = detail
|
24
|
+
end
|
25
|
+
end
|
11
26
|
|
12
27
|
class ResponseError < Error; end
|
13
28
|
|
@@ -28,6 +28,8 @@ module Mihari
|
|
28
28
|
has_many :dns_records, dependent: :destroy
|
29
29
|
has_many :ports, dependent: :destroy
|
30
30
|
has_many :reverse_dns_names, dependent: :destroy
|
31
|
+
has_many :vulnerabilities, dependent: :destroy
|
32
|
+
|
31
33
|
has_many :tags, through: :alert
|
32
34
|
|
33
35
|
include ActiveModel::Validations
|
@@ -38,13 +40,14 @@ module Mihari
|
|
38
40
|
attributes :id, :data, :data_type, :source, :query, :created_at, "alert.id", "rule.id", "rule.title",
|
39
41
|
"rule.description"
|
40
42
|
attributes tag: "tags.name"
|
41
|
-
attributes asn: "autonomous_system.
|
43
|
+
attributes asn: "autonomous_system.number"
|
42
44
|
attributes country_code: "geolocation.country_code"
|
43
45
|
attributes "dns_record.value": "dns_records.value"
|
44
46
|
attributes "dns_record.resource": "dns_records.resource"
|
45
47
|
attributes reverse_dns_name: "reverse_dns_names.name"
|
46
48
|
attributes cpe: "cpes.name"
|
47
|
-
attributes
|
49
|
+
attributes vuln: "vulnerabilities.name"
|
50
|
+
attributes port: "ports.number"
|
48
51
|
end
|
49
52
|
|
50
53
|
validates_with ArtifactValidator
|
@@ -54,6 +57,14 @@ module Mihari
|
|
54
57
|
# @return [String, nil]
|
55
58
|
attr_accessor :rule_id
|
56
59
|
|
60
|
+
before_destroy do
|
61
|
+
@alert = alert
|
62
|
+
end
|
63
|
+
|
64
|
+
after_destroy do
|
65
|
+
@alert.destroy unless @alert.artifacts.any?
|
66
|
+
end
|
67
|
+
|
57
68
|
#
|
58
69
|
# Check uniqueness
|
59
70
|
#
|
@@ -156,6 +167,17 @@ module Mihari
|
|
156
167
|
self.cpes = Services::CPEBuilder.call(data, enricher: enricher)
|
157
168
|
end
|
158
169
|
|
170
|
+
#
|
171
|
+
# Enrich vulnerabilities
|
172
|
+
#
|
173
|
+
# @param [Mihari::Enrichers::Shodan] enricher
|
174
|
+
#
|
175
|
+
def enrich_vulnerabilities(enricher = Enrichers::Shodan.new)
|
176
|
+
return unless can_enrich_vulnerabilities?
|
177
|
+
|
178
|
+
self.vulnerabilities = Services::VulnerabilityBuilder.call(data, enricher: enricher)
|
179
|
+
end
|
180
|
+
|
159
181
|
#
|
160
182
|
# Enrich all the enrichable relationships of the artifact
|
161
183
|
#
|
@@ -167,6 +189,7 @@ module Mihari
|
|
167
189
|
enrich_whois
|
168
190
|
enrich_ports shodan
|
169
191
|
enrich_cpes shodan
|
192
|
+
enrich_vulnerabilities shodan
|
170
193
|
end
|
171
194
|
|
172
195
|
ENRICH_METHODS_BY_ENRICHER = {
|
@@ -181,6 +204,7 @@ module Mihari
|
|
181
204
|
enrich_ports
|
182
205
|
enrich_cpes
|
183
206
|
enrich_reverse_dns
|
207
|
+
enrich_vulnerabilities
|
184
208
|
],
|
185
209
|
Enrichers::GooglePublicDNS => %i[
|
186
210
|
enrich_dns
|
@@ -197,6 +221,45 @@ module Mihari
|
|
197
221
|
methods.each { |method| send(method, enricher) if respond_to?(method) }
|
198
222
|
end
|
199
223
|
|
224
|
+
def can_enrich_whois?
|
225
|
+
%w[domain url].include?(data_type) && whois_record.nil?
|
226
|
+
end
|
227
|
+
|
228
|
+
def can_enrich_dns?
|
229
|
+
%w[domain url].include?(data_type) && dns_records.empty?
|
230
|
+
end
|
231
|
+
|
232
|
+
def can_enrich_reverse_dns?
|
233
|
+
data_type == "ip" && reverse_dns_names.empty?
|
234
|
+
end
|
235
|
+
|
236
|
+
def can_enrich_geolocation?
|
237
|
+
data_type == "ip" && geolocation.nil?
|
238
|
+
end
|
239
|
+
|
240
|
+
def can_enrich_autonomous_system?
|
241
|
+
data_type == "ip" && autonomous_system.nil?
|
242
|
+
end
|
243
|
+
|
244
|
+
def can_enrich_ports?
|
245
|
+
data_type == "ip" && ports.empty?
|
246
|
+
end
|
247
|
+
|
248
|
+
def can_enrich_cpes?
|
249
|
+
data_type == "ip" && cpes.empty?
|
250
|
+
end
|
251
|
+
|
252
|
+
def can_enrich_vulnerabilities?
|
253
|
+
data_type == "ip" && vulnerabilities.empty?
|
254
|
+
end
|
255
|
+
|
256
|
+
def enrichable?
|
257
|
+
enrich_methods = methods.map(&:to_s).select { |method| method.start_with?("can_enrich_") }
|
258
|
+
enrich_methods.map(&:to_sym).any? do |method|
|
259
|
+
send(method) if respond_to?(method)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
200
263
|
class << self
|
201
264
|
# @!method search_by_filter(filter)
|
202
265
|
# @param [Mihari::Structs::Filters::Search] filter
|
@@ -236,34 +299,6 @@ module Mihari
|
|
236
299
|
Addressable::URI.parse(data).host
|
237
300
|
end
|
238
301
|
end
|
239
|
-
|
240
|
-
def can_enrich_whois?
|
241
|
-
%w[domain url].include?(data_type) && whois_record.nil?
|
242
|
-
end
|
243
|
-
|
244
|
-
def can_enrich_dns?
|
245
|
-
%w[domain url].include?(data_type) && dns_records.empty?
|
246
|
-
end
|
247
|
-
|
248
|
-
def can_enrich_reverse_dns?
|
249
|
-
data_type == "ip" && reverse_dns_names.empty?
|
250
|
-
end
|
251
|
-
|
252
|
-
def can_enrich_geolocation?
|
253
|
-
data_type == "ip" && geolocation.nil?
|
254
|
-
end
|
255
|
-
|
256
|
-
def can_enrich_autonomous_system?
|
257
|
-
data_type == "ip" && autonomous_system.nil?
|
258
|
-
end
|
259
|
-
|
260
|
-
def can_enrich_ports?
|
261
|
-
data_type == "ip" && ports.empty?
|
262
|
-
end
|
263
|
-
|
264
|
-
def can_enrich_cpes?
|
265
|
-
data_type == "ip" && cpes.empty?
|
266
|
-
end
|
267
302
|
end
|
268
303
|
end
|
269
304
|
end
|
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
|
@@ -188,9 +188,7 @@ module Mihari
|
|
188
188
|
def bulk_emit
|
189
189
|
return [] if enriched_artifacts.empty?
|
190
190
|
|
191
|
-
Parallel.map(emitters)
|
192
|
-
emitter.result(enriched_artifacts).value_or nil
|
193
|
-
end.compact
|
191
|
+
Parallel.map(emitters) { |emitter| emitter.result(enriched_artifacts).value_or nil }.compact
|
194
192
|
end
|
195
193
|
|
196
194
|
#
|
@@ -315,12 +313,12 @@ module Mihari
|
|
315
313
|
# @return [Array<Mihari::Analyzers::Base>]
|
316
314
|
#
|
317
315
|
def analyzers
|
318
|
-
@analyzers ||= queries.map do |params|
|
319
|
-
|
320
|
-
klass = get_analyzer_class(
|
321
|
-
|
322
|
-
|
323
|
-
|
316
|
+
@analyzers ||= queries.deep_dup.map do |params|
|
317
|
+
name = params.delete(:analyzer)
|
318
|
+
klass = get_analyzer_class(name)
|
319
|
+
klass.from_params(params).tap do |analyzer|
|
320
|
+
analyzer.validate_configuration!
|
321
|
+
end
|
324
322
|
end
|
325
323
|
end
|
326
324
|
|
@@ -356,16 +354,14 @@ module Mihari
|
|
356
354
|
# @return [Array<Mihari::Emitters::Base>]
|
357
355
|
#
|
358
356
|
def emitters
|
359
|
-
@emitters ||= data[:emitters].
|
360
|
-
name = params
|
361
|
-
options = params
|
362
|
-
|
363
|
-
%i[emitter options].each { |key| params.delete key }
|
357
|
+
@emitters ||= data[:emitters].deep_dup.map do |params|
|
358
|
+
name = params.delete(:emitter)
|
359
|
+
options = params.delete(:options)
|
364
360
|
|
365
361
|
klass = get_emitter_class(name)
|
366
|
-
|
367
|
-
|
368
|
-
|
362
|
+
klass.new(rule: self, options: options, **params).tap do |emitter|
|
363
|
+
emitter.validate_configuration!
|
364
|
+
end
|
369
365
|
end
|
370
366
|
end
|
371
367
|
|
@@ -386,11 +382,9 @@ module Mihari
|
|
386
382
|
# @return [Array<Mihari::Enrichers::Base>] enrichers
|
387
383
|
#
|
388
384
|
def enrichers
|
389
|
-
@enrichers ||= data[:enrichers].
|
390
|
-
name = params
|
391
|
-
options = params
|
392
|
-
|
393
|
-
%i[enricher options].each { |key| params.delete key }
|
385
|
+
@enrichers ||= data[:enrichers].deep_dup.map do |params|
|
386
|
+
name = params.delete(:enricher)
|
387
|
+
options = params.delete(:options)
|
394
388
|
|
395
389
|
klass = get_enricher_class(name)
|
396
390
|
klass.new(options: options, **params)
|
data/lib/mihari/schemas/rule.rb
CHANGED
@@ -45,6 +45,13 @@ module Mihari
|
|
45
45
|
key.failure("#{v} is not a valid format.") unless valid_falsepositive?(v)
|
46
46
|
end
|
47
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")
|
53
|
+
end
|
54
|
+
end
|
48
55
|
end
|
49
56
|
end
|
50
57
|
end
|
@@ -33,7 +33,7 @@ module Mihari
|
|
33
33
|
#
|
34
34
|
def call(ip, enricher: Enrichers::MMDB.new)
|
35
35
|
enricher.result(ip).fmap do |res|
|
36
|
-
Models::AutonomousSystem.new(
|
36
|
+
Models::AutonomousSystem.new(number: res.asn) if res.asn
|
37
37
|
end.value_or nil
|
38
38
|
end
|
39
39
|
end
|
@@ -52,7 +52,7 @@ module Mihari
|
|
52
52
|
#
|
53
53
|
def call(ip, enricher: Enrichers::Shodan.new)
|
54
54
|
enricher.result(ip).fmap do |res|
|
55
|
-
(res&.cpes || []).map { |cpe| Models::CPE.new(
|
55
|
+
(res&.cpes || []).map { |cpe| Models::CPE.new(name: cpe) }
|
56
56
|
end.value_or []
|
57
57
|
end
|
58
58
|
end
|
@@ -114,7 +114,7 @@ module Mihari
|
|
114
114
|
#
|
115
115
|
def call(ip, enricher: Enrichers::Shodan.new)
|
116
116
|
enricher.result(ip).fmap do |res|
|
117
|
-
(res&.ports || []).map { |port| Models::Port.new(
|
117
|
+
(res&.ports || []).map { |port| Models::Port.new(number: port) }
|
118
118
|
end.value_or []
|
119
119
|
end
|
120
120
|
end
|
@@ -138,6 +138,25 @@ module Mihari
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
|
+
#
|
142
|
+
# Vulnerability builder
|
143
|
+
#
|
144
|
+
class VulnerabilityBuilder < Service
|
145
|
+
#
|
146
|
+
# Build vulnerabilities
|
147
|
+
#
|
148
|
+
# @param [String] ip
|
149
|
+
# @param [Mihari::Enrichers::Shodan] enricher
|
150
|
+
#
|
151
|
+
# @return [Array<Mihari::Models::Vulnerability>]
|
152
|
+
#
|
153
|
+
def call(ip, enricher: Enrichers::Shodan.new)
|
154
|
+
enricher.result(ip).fmap do |res|
|
155
|
+
(res&.vulns || []).map { |name| Models::Vulnerability.new(name: name) }
|
156
|
+
end.value_or []
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
141
160
|
#
|
142
161
|
# Whois record builder
|
143
162
|
#
|
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
|
@@ -20,21 +20,6 @@ module Mihari
|
|
20
20
|
attribute :items, Types.Array(Types::Hash).optional
|
21
21
|
|
22
22
|
class << self
|
23
|
-
#
|
24
|
-
# Get a type of a class
|
25
|
-
#
|
26
|
-
# @param [Class<Mihari::Analyzers::Base>, Class<Mihari::Emitters::Base>, Class<Mihari::Enrichers::Base>] klass
|
27
|
-
#
|
28
|
-
# @return [String, nil]
|
29
|
-
#
|
30
|
-
def get_type(klass)
|
31
|
-
return "analyzer" if klass.ancestors.include?(Mihari::Analyzers::Base)
|
32
|
-
return "emitter" if klass.ancestors.include?(Mihari::Emitters::Base)
|
33
|
-
return "enricher" if klass.ancestors.include?(Mihari::Enrichers::Base)
|
34
|
-
|
35
|
-
nil
|
36
|
-
end
|
37
|
-
|
38
23
|
#
|
39
24
|
# Get a dummy instance
|
40
25
|
#
|
@@ -43,8 +28,7 @@ module Mihari
|
|
43
28
|
# @return [Mihari::Analyzers::Base, Mihari::Emitter::Base, Mihari::Enricher::Base] dummy
|
44
29
|
#
|
45
30
|
def get_dummy(klass)
|
46
|
-
|
47
|
-
case type
|
31
|
+
case klass.type
|
48
32
|
when "analyzer"
|
49
33
|
klass.new("dummy")
|
50
34
|
when "emitter"
|
@@ -62,8 +46,7 @@ module Mihari
|
|
62
46
|
def from_class(klass)
|
63
47
|
return nil if klass == Mihari::Rule
|
64
48
|
|
65
|
-
|
66
|
-
return nil if type.nil?
|
49
|
+
return nil if klass.type.nil?
|
67
50
|
|
68
51
|
begin
|
69
52
|
instance = get_dummy(klass)
|
@@ -71,7 +54,7 @@ module Mihari
|
|
71
54
|
name: klass.key,
|
72
55
|
items: klass.configuration_items,
|
73
56
|
configured: instance.configured?,
|
74
|
-
type: type
|
57
|
+
type: klass.type
|
75
58
|
)
|
76
59
|
rescue ArgumentError
|
77
60
|
nil
|
@@ -67,17 +67,25 @@ module Mihari
|
|
67
67
|
# @return [Integer]
|
68
68
|
attribute :port, Types::Int
|
69
69
|
|
70
|
+
# @!attribute [r] cpe
|
71
|
+
# @return [Array<String>]
|
72
|
+
attribute :cpe, Types::Array(Types::String)
|
73
|
+
|
74
|
+
# @!attribute [r] vulns
|
75
|
+
# @return [Hash]
|
76
|
+
attribute :vulns, Types::Hash
|
77
|
+
|
70
78
|
# @!attribute [r] metadata
|
71
79
|
# @return [Hash]
|
72
80
|
attribute :metadata, Types::Hash
|
73
81
|
|
74
82
|
#
|
75
|
-
# @return [Mihari::AutonomousSystem, nil]
|
83
|
+
# @return [Mihari::Models::AutonomousSystem, nil]
|
76
84
|
#
|
77
|
-
def
|
85
|
+
def autonomous_system
|
78
86
|
return nil if asn.nil?
|
79
87
|
|
80
|
-
|
88
|
+
Models::AutonomousSystem.new(number: normalize_asn(asn))
|
81
89
|
end
|
82
90
|
|
83
91
|
class << self
|
@@ -103,6 +111,8 @@ module Mihari
|
|
103
111
|
domains: d.fetch("domains"),
|
104
112
|
ip_str: d.fetch("ip_str"),
|
105
113
|
port: d.fetch("port"),
|
114
|
+
cpe: d["cpe"] || [],
|
115
|
+
vulns: d["vulns"] || {},
|
106
116
|
metadata: d
|
107
117
|
)
|
108
118
|
end
|
@@ -110,6 +120,8 @@ module Mihari
|
|
110
120
|
end
|
111
121
|
|
112
122
|
class Response < Dry::Struct
|
123
|
+
prepend MemoWise
|
124
|
+
|
113
125
|
# @!attribute [r] matches
|
114
126
|
# @return [Array<Match>]
|
115
127
|
attribute :matches, Types.Array(Match)
|
@@ -118,6 +130,16 @@ module Mihari
|
|
118
130
|
# @return [Integer]
|
119
131
|
attribute :total, Types::Int
|
120
132
|
|
133
|
+
#
|
134
|
+
# @param [String] ip
|
135
|
+
#
|
136
|
+
# @return [Array<Mihari::Structs::Shodan::Match>]
|
137
|
+
#
|
138
|
+
def select_matches_by_ip(ip)
|
139
|
+
matches.select { |match| match.ip_str == ip }
|
140
|
+
end
|
141
|
+
memo_wise :select_matches_by_ip
|
142
|
+
|
121
143
|
#
|
122
144
|
# Collect metadata from matches
|
123
145
|
#
|
@@ -126,7 +148,7 @@ module Mihari
|
|
126
148
|
# @return [Array<Hash>]
|
127
149
|
#
|
128
150
|
def collect_metadata_by_ip(ip)
|
129
|
-
|
151
|
+
select_matches_by_ip(ip).map(&:metadata)
|
130
152
|
end
|
131
153
|
|
132
154
|
#
|
@@ -137,7 +159,7 @@ module Mihari
|
|
137
159
|
# @return [Array<String>]
|
138
160
|
#
|
139
161
|
def collect_ports_by_ip(ip)
|
140
|
-
|
162
|
+
select_matches_by_ip(ip).map(&:port)
|
141
163
|
end
|
142
164
|
|
143
165
|
#
|
@@ -148,7 +170,30 @@ module Mihari
|
|
148
170
|
# @return [Array<String>]
|
149
171
|
#
|
150
172
|
def collect_hostnames_by_ip(ip)
|
151
|
-
|
173
|
+
select_matches_by_ip(ip).map(&:hostnames).flatten.uniq
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Collect CPE from matches
|
178
|
+
#
|
179
|
+
# @param [String] ip
|
180
|
+
#
|
181
|
+
# @return [Array<String>]
|
182
|
+
#
|
183
|
+
def collect_cpes_by_ip(ip)
|
184
|
+
select_matches_by_ip(ip).map(&:cpe).flatten.uniq
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Collect vulnerabilities from matches
|
189
|
+
#
|
190
|
+
# @param [String] ip
|
191
|
+
#
|
192
|
+
# @return [Array<String>]
|
193
|
+
#
|
194
|
+
def collect_vulns_by_ip(ip)
|
195
|
+
# NOTE: vuln keys = CVE IDs
|
196
|
+
select_matches_by_ip(ip).map { |match| match.vulns.keys }.flatten.uniq
|
152
197
|
end
|
153
198
|
|
154
199
|
#
|
@@ -158,20 +203,22 @@ module Mihari
|
|
158
203
|
matches.map do |match|
|
159
204
|
metadata = collect_metadata_by_ip(match.ip_str)
|
160
205
|
|
161
|
-
ports = collect_ports_by_ip(match.ip_str).map
|
162
|
-
Mihari::Models::Port.new(port: port)
|
163
|
-
end
|
206
|
+
ports = collect_ports_by_ip(match.ip_str).map { |port| Models::Port.new(number: port) }
|
164
207
|
reverse_dns_names = collect_hostnames_by_ip(match.ip_str).map do |name|
|
165
|
-
|
208
|
+
Models::ReverseDnsName.new(name: name)
|
166
209
|
end
|
210
|
+
cpes = collect_cpes_by_ip(match.ip_str).map { |name| Models::CPE.new(name: name) }
|
211
|
+
vulnerabilities = collect_vulns_by_ip(match.ip_str).map { |name| Models::Vulnerability.new(name: name) }
|
167
212
|
|
168
213
|
Mihari::Models::Artifact.new(
|
169
214
|
data: match.ip_str,
|
170
215
|
metadata: metadata,
|
171
|
-
autonomous_system: match.
|
216
|
+
autonomous_system: match.autonomous_system,
|
172
217
|
geolocation: match.location.geolocation,
|
173
218
|
ports: ports,
|
174
|
-
reverse_dns_names: reverse_dns_names
|
219
|
+
reverse_dns_names: reverse_dns_names,
|
220
|
+
cpes: cpes,
|
221
|
+
vulnerabilities: vulnerabilities
|
175
222
|
)
|
176
223
|
end
|
177
224
|
end
|
@@ -232,15 +279,6 @@ module Mihari
|
|
232
279
|
vulns: d.fetch("vulns")
|
233
280
|
)
|
234
281
|
end
|
235
|
-
|
236
|
-
#
|
237
|
-
# @param [String] json
|
238
|
-
#
|
239
|
-
# @return [InternetDBResponse]
|
240
|
-
#
|
241
|
-
def from_json!(json)
|
242
|
-
from_dynamic!(JSON.parse(json))
|
243
|
-
end
|
244
282
|
end
|
245
283
|
end
|
246
284
|
end
|
data/lib/mihari/version.rb
CHANGED
@@ -53,7 +53,7 @@ module Mihari
|
|
53
53
|
|
54
54
|
desc "Enrich an artifact", {
|
55
55
|
success: { code: 201, model: Entities::Message },
|
56
|
-
failure: [{ code: 404, model: Entities::ErrorMessage }],
|
56
|
+
failure: [{ code: 400, model: Entities::ErrorMessage }, { code: 404, model: Entities::ErrorMessage }],
|
57
57
|
summary: "Enrich an artifact"
|
58
58
|
}
|
59
59
|
params do
|
@@ -74,10 +74,12 @@ module Mihari
|
|
74
74
|
end
|
75
75
|
end.to_result
|
76
76
|
|
77
|
-
message = queued ? "ID:#{id}'s enrichment
|
77
|
+
message = queued ? "ID:#{id}'s enrichment is queued" : "ID:#{id}'s enrichment is successful"
|
78
78
|
return present({ message: message, queued: queued }, with: Entities::QueueMessage) if result.success?
|
79
79
|
|
80
80
|
case result.failure
|
81
|
+
when UnenrichableError
|
82
|
+
error!({ message: result.failure.message }, 400)
|
81
83
|
when ActiveRecord::RecordNotFound
|
82
84
|
error!({ message: "ID:#{id} not found" }, 404)
|
83
85
|
end
|