mihari 7.1.2 → 7.2.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 +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
|