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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/Rakefile +15 -7
  4. data/build_frontend.sh +1 -1
  5. data/lefthook.yml +4 -1
  6. data/lib/mihari/actor.rb +21 -4
  7. data/lib/mihari/analyzers/base.rb +7 -18
  8. data/lib/mihari/analyzers/binaryedge.rb +0 -6
  9. data/lib/mihari/analyzers/censys.rb +0 -9
  10. data/lib/mihari/analyzers/circl.rb +0 -6
  11. data/lib/mihari/analyzers/fofa.rb +0 -6
  12. data/lib/mihari/analyzers/greynoise.rb +0 -6
  13. data/lib/mihari/analyzers/hunterhow.rb +0 -6
  14. data/lib/mihari/analyzers/onyphe.rb +0 -6
  15. data/lib/mihari/analyzers/otx.rb +0 -6
  16. data/lib/mihari/analyzers/passivetotal.rb +0 -4
  17. data/lib/mihari/analyzers/pulsedive.rb +0 -6
  18. data/lib/mihari/analyzers/securitytrails.rb +0 -4
  19. data/lib/mihari/analyzers/shodan.rb +0 -6
  20. data/lib/mihari/analyzers/urlscan.rb +0 -6
  21. data/lib/mihari/analyzers/virustotal.rb +0 -4
  22. data/lib/mihari/analyzers/virustotal_intelligence.rb +7 -6
  23. data/lib/mihari/analyzers/zoomeye.rb +0 -6
  24. data/lib/mihari/commands/web.rb +4 -4
  25. data/lib/mihari/concerns/falsepositive_normalizable.rb +30 -0
  26. data/lib/mihari/concerns/falsepositive_validatable.rb +1 -17
  27. data/lib/mihari/config.rb +1 -1
  28. data/lib/mihari/database.rb +18 -1
  29. data/lib/mihari/emitters/database.rb +0 -6
  30. data/lib/mihari/emitters/misp.rb +0 -6
  31. data/lib/mihari/emitters/slack.rb +5 -21
  32. data/lib/mihari/emitters/the_hive.rb +0 -6
  33. data/lib/mihari/enrichers/whois.rb +5 -7
  34. data/lib/mihari/entities/artifact.rb +6 -2
  35. data/lib/mihari/entities/autonomous_system.rb +1 -1
  36. data/lib/mihari/entities/cpe.rb +1 -1
  37. data/lib/mihari/entities/port.rb +1 -1
  38. data/lib/mihari/entities/vulnerability.rb +10 -0
  39. data/lib/mihari/errors.rb +16 -1
  40. data/lib/mihari/models/artifact.rb +65 -30
  41. data/lib/mihari/models/vulnerability.rb +12 -0
  42. data/lib/mihari/rule.rb +18 -24
  43. data/lib/mihari/schemas/rule.rb +7 -0
  44. data/lib/mihari/services/builders.rb +22 -3
  45. data/lib/mihari/services/enrichers.rb +2 -0
  46. data/lib/mihari/services/feed.rb +2 -5
  47. data/lib/mihari/services/proxies.rb +3 -3
  48. data/lib/mihari/structs/censys.rb +2 -2
  49. data/lib/mihari/structs/config.rb +3 -20
  50. data/lib/mihari/structs/greynoise.rb +1 -1
  51. data/lib/mihari/structs/onyphe.rb +1 -1
  52. data/lib/mihari/structs/shodan.rb +59 -21
  53. data/lib/mihari/version.rb +1 -1
  54. data/lib/mihari/web/endpoints/artifacts.rb +4 -2
  55. data/lib/mihari/web/endpoints/rules.rb +1 -1
  56. data/lib/mihari/web/public/assets/{index-Guw2aMpk.js → index-GWurHG1o.js} +60 -40
  57. data/lib/mihari/web/public/assets/{index-dVaNxqTC.css → index-ReF8ffd-.css} +1 -1
  58. data/lib/mihari/web/public/index.html +2 -2
  59. data/lib/mihari/web/public/redoc-static.html +385 -385
  60. data/lib/mihari.rb +3 -0
  61. metadata +11 -51
  62. data/test.json.jbuilder +0 -7
@@ -3,7 +3,7 @@
3
3
  module Mihari
4
4
  module Entities
5
5
  class Port < Grape::Entity
6
- expose :port, documentation: { type: Integer, required: true }
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 ConfigurationError < Error; end
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.asn"
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 port: "ports.port"
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Models
5
+ #
6
+ # Vulnerability model
7
+ #
8
+ class Vulnerability < ActiveRecord::Base
9
+ belongs_to :artifact
10
+ end
11
+ end
12
+ 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) do |emitter|
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
- analyzer_name = params[:analyzer]
320
- klass = get_analyzer_class(analyzer_name)
321
- analyzer = klass.from_query(params)
322
- analyzer.validate_configuration!
323
- analyzer
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].map(&:deep_dup).map do |params|
360
- name = params[:emitter]
361
- options = params[:options]
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
- emitter = klass.new(rule: self, options: options, **params)
367
- emitter.validate_configuration!
368
- emitter
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].map(&:deep_dup).map do |params|
390
- name = params[:enricher]
391
- options = params[:options]
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)
@@ -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(asn: res.asn) if res.asn
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(cpe: cpe) }
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(port: port) }
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
  #
@@ -17,6 +17,8 @@ module Mihari
17
17
  :ports
18
18
  ).find(id)
19
19
 
20
+ raise UnenrichableError.new, "#{artifact.id} is already enriched or unenrichable" unless artifact.enrichable?
21
+
20
22
  artifact.enrich_all
21
23
  artifact.save
22
24
  end
@@ -93,12 +93,9 @@ module Mihari
93
93
  #
94
94
  # @param [Object] read_data
95
95
  def call(input_enumerator, selector)
96
- parsed = proc do
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 ||= [].tap do |out|
82
+ @rule ||= lambda do
83
83
  data = Mihari::Models::Rule.find(rule_id).data
84
- out << Rule.new(**data)
85
- end.first
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(asn: normalize_asn(asn))
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(port: port)
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
- type = get_type(klass)
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
- type = get_type(klass)
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
@@ -22,7 +22,7 @@ module Mihari
22
22
  # @return [Mihari::AutonomousSystem]
23
23
  #
24
24
  def as
25
- Mihari::Models::AutonomousSystem.new(asn: normalize_asn(asn))
25
+ Mihari::Models::AutonomousSystem.new(number: normalize_asn(asn))
26
26
  end
27
27
 
28
28
  #
@@ -51,7 +51,7 @@ module Mihari
51
51
  # @return [Mihari::AutonomousSystem]
52
52
  #
53
53
  def as
54
- Mihari::Models::AutonomousSystem.new(asn: normalize_asn(asn))
54
+ Mihari::Models::AutonomousSystem.new(number: normalize_asn(asn))
55
55
  end
56
56
 
57
57
  class << self
@@ -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 _asn
85
+ def autonomous_system
78
86
  return nil if asn.nil?
79
87
 
80
- Mihari::Models::AutonomousSystem.new(asn: normalize_asn(asn))
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
- matches.select { |match| match.ip_str == ip }.map(&:metadata)
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
- matches.select { |match| match.ip_str == ip }.map(&:port)
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
- matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
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 do |port|
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
- Mihari::Models::ReverseDnsName.new(name: name)
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._asn,
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "7.1.2"
4
+ VERSION = "7.2.0"
5
5
  end
@@ -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 has been queued" : "ID:#{id}'s enrichment has been succeeded"
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