mihari 7.1.2 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
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