mihari 4.6.1 → 4.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mihari/analyzers/clients/otx.rb +36 -0
  3. data/lib/mihari/analyzers/otx.rb +19 -11
  4. data/lib/mihari/analyzers/rule.rb +17 -1
  5. data/lib/mihari/commands/init.rb +25 -2
  6. data/lib/mihari/commands/search.rb +2 -7
  7. data/lib/mihari/commands/validator.rb +10 -5
  8. data/lib/mihari/constants.rb +2 -0
  9. data/lib/mihari/enrichers/google_public_dns.rb +36 -0
  10. data/lib/mihari/enrichers/whois.rb +126 -0
  11. data/lib/mihari/errors.rb +2 -0
  12. data/lib/mihari/http.rb +2 -2
  13. data/lib/mihari/models/alert.rb +6 -1
  14. data/lib/mihari/models/artifact.rb +30 -0
  15. data/lib/mihari/models/dns.rb +5 -21
  16. data/lib/mihari/models/geolocation.rb +2 -4
  17. data/lib/mihari/models/port.rb +1 -1
  18. data/lib/mihari/models/rule.rb +7 -2
  19. data/lib/mihari/models/whois.rb +1 -96
  20. data/lib/mihari/schemas/enricher.rb +9 -0
  21. data/lib/mihari/schemas/rule.rb +6 -0
  22. data/lib/mihari/structs/filters.rb +71 -0
  23. data/lib/mihari/structs/google_public_dns.rb +42 -0
  24. data/lib/mihari/structs/ipinfo.rb +4 -4
  25. data/lib/mihari/structs/rule.rb +187 -137
  26. data/lib/mihari/types.rb +7 -0
  27. data/lib/mihari/version.rb +1 -1
  28. data/lib/mihari/web/endpoints/alerts.rb +1 -1
  29. data/lib/mihari/web/endpoints/rules.rb +13 -5
  30. data/lib/mihari/web/public/index.html +1 -1
  31. data/lib/mihari/web/public/redoc-static.html +796 -763
  32. data/lib/mihari/web/public/static/css/chunk-vendors.5013d549.css +7 -0
  33. data/lib/mihari/web/public/static/js/app.3ac3bd7a.js +2 -0
  34. data/lib/mihari/web/public/static/js/app.3ac3bd7a.js.map +1 -0
  35. data/lib/mihari/web/public/static/js/{chunk-vendors.dde2116c.js → chunk-vendors.37b7208e.js} +6 -6
  36. data/lib/mihari/web/public/static/js/chunk-vendors.37b7208e.js.map +1 -0
  37. data/lib/mihari.rb +4 -2
  38. data/mihari.gemspec +8 -9
  39. data/sig/lib/mihari/cli/base.rbs +0 -2
  40. data/sig/lib/mihari/enrichers/google_public_dns.rbs +18 -0
  41. data/sig/lib/mihari/models/alert.rbs +3 -3
  42. data/sig/lib/mihari/models/rule.rbs +2 -2
  43. data/sig/lib/mihari/structs/filters.rbs +40 -0
  44. data/sig/lib/mihari/structs/google_public_dns.rbs +21 -0
  45. data/sig/lib/mihari/structs/ipinfo.rbs +2 -2
  46. data/sig/lib/mihari/structs/rule.rbs +36 -43
  47. metadata +32 -45
  48. data/lib/mihari/mixins/rule.rb +0 -84
  49. data/lib/mihari/structs/alert.rb +0 -44
  50. data/lib/mihari/web/public/static/css/chunk-vendors.06251949.css +0 -7
  51. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js +0 -2
  52. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js.map +0 -1
  53. data/lib/mihari/web/public/static/js/app.823b5af7.js +0 -2
  54. data/lib/mihari/web/public/static/js/app.823b5af7.js.map +0 -1
  55. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js +0 -25
  56. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js.map +0 -1
  57. data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js.map +0 -1
  58. data/sig/lib/mihari/mixins/rule.rbs +0 -36
  59. data/sig/lib/mihari/structs/alert.rbs +0 -27
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "whois-parser"
4
-
5
3
  module Mihari
6
4
  class WhoisRecord < ActiveRecord::Base
7
5
  belongs_to :artifact
@@ -17,100 +15,7 @@ module Mihari
17
15
  # @return [WhoisRecord, nil]
18
16
  #
19
17
  def build_by_domain(domain)
20
- domain = PublicSuffix.domain(domain)
21
-
22
- # check memo
23
- if @memo.key?(domain)
24
- whois_record = @memo[domain]
25
- # return clone of the record
26
- return whois_record.dup
27
- end
28
-
29
- record = Whois.whois(domain)
30
- parser = record.parser
31
-
32
- return nil if parser.available?
33
-
34
- whois_record = new(
35
- domain: domain,
36
- created_on: get_created_on(parser),
37
- updated_on: get_updated_on(parser),
38
- expires_on: get_expires_on(parser),
39
- registrar: get_registrar(parser),
40
- contacts: get_contacts(parser)
41
- )
42
- # set memo
43
- @memo[domain] = whois_record
44
- whois_record
45
- rescue Whois::Error, Whois::ParserError, Timeout::Error
46
- nil
47
- end
48
-
49
- private
50
-
51
- #
52
- # Get created_on
53
- #
54
- # @param [::Whois::Parser:] parser
55
- #
56
- # @return [Date, nil]
57
- #
58
- def get_created_on(parser)
59
- parser.created_on
60
- rescue ::Whois::AttributeNotImplemented
61
- nil
62
- end
63
-
64
- #
65
- # Get updated_on
66
- #
67
- # @param [::Whois::Parser:] parser
68
- #
69
- # @return [Date, nil]
70
- #
71
- def get_updated_on(parser)
72
- parser.updated_on
73
- rescue ::Whois::AttributeNotImplemented
74
- nil
75
- end
76
-
77
- #
78
- # Get expires_on
79
- #
80
- # @param [::Whois::Parser:] parser
81
- #
82
- # @return [Date, nil]
83
- #
84
- def get_expires_on(parser)
85
- parser.expires_on
86
- rescue ::Whois::AttributeNotImplemented
87
- nil
88
- end
89
-
90
- #
91
- # Get registrar
92
- #
93
- # @param [::Whois::Parser:] parser
94
- #
95
- # @return [Hash, nil]
96
- #
97
- def get_registrar(parser)
98
- parser.registrar&.to_h
99
- rescue ::Whois::AttributeNotImplemented
100
- nil
101
- end
102
-
103
- #
104
- # Get contacts
105
- #
106
- # @param [::Whois::Parser:] parser
107
- #
108
- # @return [Array[Hash], nil]
109
- #
110
- def get_contacts(parser)
111
- parser.contacts.map(&:to_h)
112
- rescue ::Whois::AttributeNotImplemented
113
- nil
18
+ Enrichers::Whois.query domain
114
19
  end
115
20
  end
116
21
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Schemas
5
+ Enricher = Dry::Schema.Params do
6
+ required(:enricher).value(Types::EnricherTypes)
7
+ end
8
+ end
9
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mihari/schemas/analyzer"
4
4
  require "mihari/schemas/emitter"
5
+ require "mihari/schemas/enricher"
5
6
 
6
7
  module Mihari
7
8
  module Schemas
@@ -20,6 +21,8 @@ module Mihari
20
21
 
21
22
  optional(:emitters).value(:array).each { Emitter | MISP | TheHive | Slack | HTTP }
22
23
 
24
+ optional(:enrichers).value(:array).each(Enricher)
25
+
23
26
  optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
24
27
  optional(:disallowed_data_values).value(array[:string]).default([])
25
28
 
@@ -35,6 +38,9 @@ module Mihari
35
38
  emitters = h[:emitters]
36
39
  h[:emitters] = emitters || DEFAULT_EMITTERS
37
40
 
41
+ enrichers = h[:enrichers]
42
+ h[:enrichers] = enrichers || DEFAULT_ENRICHERS
43
+
38
44
  h
39
45
  end
40
46
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Structs
5
+ module Filters
6
+ module Alert
7
+ class SearchFilter < Dry::Struct
8
+ attribute? :artifact_data, Types::String.optional
9
+ attribute? :description, Types::String.optional
10
+ attribute? :source, Types::String.optional
11
+ attribute? :tag_name, Types::String.optional
12
+ attribute? :title, Types::String.optional
13
+ attribute? :from_at, Types::DateTime.optional
14
+ attribute? :to_at, Types::DateTime.optional
15
+ attribute? :asn, Types::Int.optional
16
+ attribute? :dns_record, Types::String.optional
17
+ attribute? :reverse_dns_name, Types::String.optional
18
+
19
+ def valid_artifact_filters?
20
+ !(artifact_data || asn || dns_record || reverse_dns_name).nil?
21
+ end
22
+ end
23
+
24
+ class SearchFilterWithPagination < SearchFilter
25
+ attribute? :page, Types::Int.default(1)
26
+ attribute? :limit, Types::Int.default(10)
27
+
28
+ def without_pagination
29
+ SearchFilter.new(
30
+ artifact_data: artifact_data,
31
+ description: description,
32
+ from_at: from_at,
33
+ source: source,
34
+ tag_name: tag_name,
35
+ title: title,
36
+ to_at: to_at,
37
+ asn: asn,
38
+ dns_record: dns_record,
39
+ reverse_dns_name: reverse_dns_name
40
+ )
41
+ end
42
+ end
43
+ end
44
+
45
+ module Rule
46
+ class SearchFilter < Dry::Struct
47
+ attribute? :description, Types::String.optional
48
+ attribute? :tag_name, Types::String.optional
49
+ attribute? :title, Types::String.optional
50
+ attribute? :from_at, Types::DateTime.optional
51
+ attribute? :to_at, Types::DateTime.optional
52
+ end
53
+
54
+ class SearchFilterWithPagination < SearchFilter
55
+ attribute? :page, Types::Int.default(1)
56
+ attribute? :limit, Types::Int.default(10)
57
+
58
+ def without_pagination
59
+ SearchFilter.new(
60
+ description: description,
61
+ from_at: from_at,
62
+ tag_name: tag_name,
63
+ title: title,
64
+ to_at: to_at
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Structs
5
+ module GooglePublicDNS
6
+ INT_TYPE_TO_TYPE = {
7
+ 1 => "A",
8
+ 2 => "NS",
9
+ 5 => "CNAME",
10
+ 16 => "TXT",
11
+ 28 => "AAAA"
12
+ }
13
+
14
+ class Answer < Dry::Struct
15
+ attribute :name, Types::String
16
+ attribute :data, Types::String
17
+ attribute :resource_type, Types::String
18
+
19
+ def self.from_dynamic!(d)
20
+ d = Types::Hash[d]
21
+ resource_type = INT_TYPE_TO_TYPE[d.fetch("type")]
22
+ new(
23
+ name: d.fetch("name"),
24
+ data: d.fetch("data"),
25
+ resource_type: resource_type
26
+ )
27
+ end
28
+ end
29
+
30
+ class Response < Dry::Struct
31
+ attribute :answers, Types.Array(Answer)
32
+
33
+ def self.from_dynamic!(d)
34
+ d = Types::Hash[d]
35
+ new(
36
+ answers: d.fetch("Answer", []).map { |x| Answer.from_dynamic!(x) }
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -6,8 +6,8 @@ module Mihari
6
6
  class Response < Dry::Struct
7
7
  attribute :ip, Types::String
8
8
  attribute :hostname, Types::String.optional
9
- attribute :loc, Types::String
10
- attribute :country_code, Types::String
9
+ attribute :loc, Types::String.optional
10
+ attribute :country_code, Types::String.optional
11
11
  attribute :asn, Types::Integer.optional
12
12
 
13
13
  class << self
@@ -23,9 +23,9 @@ module Mihari
23
23
 
24
24
  new(
25
25
  ip: d.fetch("ip"),
26
- loc: d.fetch("loc"),
26
+ loc: d["loc"],
27
27
  hostname: d["hostname"],
28
- country_code: d.fetch("country"),
28
+ country_code: d["country"],
29
29
  asn: asn
30
30
  )
31
31
  end
@@ -1,186 +1,236 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
4
+ require "erb"
5
+ require "pathname"
6
+ require "yaml"
7
+
3
8
  module Mihari
4
9
  module Structs
5
- module Rule
6
- class SearchFilter < Dry::Struct
7
- attribute? :description, Types::String.optional
8
- attribute? :tag_name, Types::String.optional
9
- attribute? :title, Types::String.optional
10
- attribute? :from_at, Types::DateTime.optional
11
- attribute? :to_at, Types::DateTime.optional
10
+ class Rule
11
+ # @return [Hash]
12
+ attr_reader :data
13
+
14
+ # @return [String]
15
+ attr_reader :yaml
16
+
17
+ # @return [Array, nil]
18
+ attr_reader :errors
19
+
20
+ # @return [String]
21
+ attr_writer :id
22
+
23
+ def initialize(data, yaml)
24
+ @data = data.deep_symbolize_keys
25
+ @yaml = yaml
26
+
27
+ @errors = nil
28
+ @no_method_error = nil
29
+
30
+ validate
12
31
  end
13
32
 
14
- class SearchFilterWithPagination < SearchFilter
15
- attribute? :page, Types::Int.default(1)
16
- attribute? :limit, Types::Int.default(10)
17
-
18
- def without_pagination
19
- SearchFilter.new(
20
- description: description,
21
- from_at: from_at,
22
- tag_name: tag_name,
23
- title: title,
24
- to_at: to_at
25
- )
33
+ #
34
+ # @return [Boolean]
35
+ #
36
+ def errors?
37
+ return false if @errors.nil?
38
+
39
+ !@errors.empty?
40
+ end
41
+
42
+ #
43
+ # @return [Array<String>]
44
+ #
45
+ def error_messages
46
+ return [] if @errors.nil?
47
+
48
+ @errors.messages.filter_map do |message|
49
+ path = message.path.map(&:to_s).join
50
+ "#{path} #{message.text}"
51
+ rescue NoMethodError
52
+ nil
26
53
  end
27
54
  end
28
55
 
29
- class Rule
30
- # @return [Hash]
31
- attr_reader :data
56
+ def validate
57
+ begin
58
+ contract = Schemas::RuleContract.new
59
+ result = contract.call(data)
60
+ rescue NoMethodError => e
61
+ @no_method_error = e
62
+ return
63
+ end
64
+
65
+ @data = result.to_h
66
+ @errors = result.errors
67
+ end
32
68
 
33
- # @return [String]
34
- attr_reader :yaml
69
+ def validate!
70
+ raise RuleValidationError, "Data should be a hash" unless data.is_a?(Hash)
71
+ raise RuleValidationError, error_messages.join("\n") if errors?
72
+ raise RuleValidationError, "Something wrong with queries, emitters or enrichers." unless @no_method_error.nil?
73
+ rescue RuleValidationError => e
74
+ message = "Failed to parse the input as a rule"
75
+ message += ": #{e.message}" unless e.message.empty?
76
+ Mihari.logger.error message
35
77
 
36
- # @return [Array, nil]
37
- attr_reader :errors
78
+ raise e
79
+ end
38
80
 
39
- # @return [String]
40
- attr_writer :id
81
+ def [](key)
82
+ data[key.to_sym]
83
+ end
41
84
 
42
- def initialize(data, yaml)
43
- @data = data.deep_symbolize_keys
44
- @yaml = yaml
85
+ #
86
+ # @return [String]
87
+ #
88
+ def id
89
+ @id ||= data[:id] || UUIDTools::UUID.md5_create(UUIDTools::UUID_URL_NAMESPACE, data.to_yaml).to_s
90
+ end
45
91
 
46
- @errors = nil
47
- @no_method_error = nil
92
+ #
93
+ # @return [String]
94
+ #
95
+ def title
96
+ @title ||= data[:title]
97
+ end
48
98
 
49
- validate
50
- end
99
+ #
100
+ # @return [String]
101
+ #
102
+ def description
103
+ @description ||= data[:description]
104
+ end
51
105
 
52
- #
53
- # @return [Boolean]
54
- #
55
- def errors?
56
- return false if @errors.nil?
106
+ #
107
+ # @return [Mihari::Rule]
108
+ #
109
+ def to_model
110
+ rule = Mihari::Rule.find(id)
111
+
112
+ rule.title = title
113
+ rule.description = description
114
+ rule.data = data
115
+ rule.yaml = yaml
116
+
117
+ rule
118
+ rescue ActiveRecord::RecordNotFound
119
+ Mihari::Rule.new(
120
+ id: id,
121
+ title: title,
122
+ description: description,
123
+ data: data,
124
+ yaml: yaml
125
+ )
126
+ end
57
127
 
58
- !@errors.empty?
59
- end
128
+ #
129
+ # @return [Mihari::Analyzers::Rule]
130
+ #
131
+ def to_analyzer
132
+ analyzer = Mihari::Analyzers::Rule.new(
133
+ title: self[:title],
134
+ description: self[:description],
135
+ tags: self[:tags],
136
+ queries: self[:queries],
137
+ allowed_data_types: self[:allowed_data_types],
138
+ disallowed_data_values: self[:disallowed_data_values],
139
+ emitters: self[:emitters],
140
+ enrichers: self[:enrichers],
141
+ id: id
142
+ )
143
+ analyzer.ignore_old_artifacts = self[:ignore_old_artifacts]
144
+ analyzer.ignore_threshold = self[:ignore_threshold]
145
+
146
+ analyzer
147
+ end
148
+
149
+ class << self
150
+ include Mixins::Database
60
151
 
61
152
  #
62
- # @return [Array<String>]
153
+ # @param [Mihari::Rule] model
63
154
  #
64
- def error_messages
65
- return [] if @errors.nil?
155
+ # @return [Mihari::Structs::Rule]
156
+ #
157
+ def from_model(model)
158
+ data = model.data.deep_symbolize_keys
159
+ # set ID if YAML data do not have ID
160
+ data[:id] = model.id unless data.key?(:id)
66
161
 
67
- @errors.messages.map do |message|
68
- path = message.path.map(&:to_s).join
69
- "#{path} #{message.text}"
70
- end
162
+ Structs::Rule.new(data, model.yaml)
71
163
  end
72
164
 
73
- def validate
74
- begin
75
- contract = Schemas::RuleContract.new
76
- result = contract.call(data)
77
- rescue NoMethodError => e
78
- @no_method_error = e
79
- return
80
- end
165
+ #
166
+ # @param [String] yaml
167
+ #
168
+ # @return [Mihari::Structs::Rule]
169
+ # @param [String, nil] id
170
+ #
171
+ def from_yaml(yaml, id: nil)
172
+ data = load_erb_yaml(yaml)
173
+ # set ID if id is given & YAML data do not have ID
174
+ data[:id] = id if !id.nil? && !data.key?(:id)
81
175
 
82
- @data = result.to_h
83
- @errors = result.errors
176
+ Structs::Rule.new(data, yaml)
84
177
  end
85
178
 
86
- def validate!
87
- raise RuleValidationError, "Data should be a hash" unless data.is_a?(Hash)
179
+ #
180
+ # @param [String] path_or_id Path to YAML file or YAML string or ID of a rule in the database
181
+ #
182
+ # @return [Mihari::Structs::Rule]
183
+ #
184
+ def from_path_or_id(path_or_id)
185
+ yaml = nil
88
186
 
89
- raise RuleValidationError, error_messages.join("\n") if errors?
187
+ yaml = load_yaml_from_file(path_or_id) if File.exist?(path_or_id)
188
+ yaml = load_yaml_from_db(path_or_id) if yaml.nil?
90
189
 
91
- raise RuleValidationError, "Something wrong with queries or emitters." unless @no_method_error.nil?
190
+ Structs::Rule.from_yaml yaml
92
191
  end
93
192
 
94
- def [](key)
95
- data[key.to_sym]
96
- end
193
+ private
97
194
 
98
195
  #
99
- # @return [String]
196
+ # Load ERR templated YAML
100
197
  #
101
- def id
102
- @id ||= data[:id] || UUIDTools::UUID.md5_create(UUIDTools::UUID_URL_NAMESPACE, data.to_yaml).to_s
103
- end
104
-
198
+ # @param [String] yaml
105
199
  #
106
- # @return [String]
200
+ # @return [Hash]
107
201
  #
108
- def title
109
- @title ||= data[:title]
202
+ def load_erb_yaml(yaml)
203
+ YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol], symbolize_names: true)
204
+ rescue Psych::SyntaxError => e
205
+ raise YAMLSyntaxError, e.message
110
206
  end
111
207
 
112
208
  #
113
- # @return [String]
209
+ # Load YAML string from path
114
210
  #
115
- def description
116
- @description ||= data[:description]
117
- end
118
-
211
+ # @param [String] path
119
212
  #
120
- # @return [Mihari::Rule]
213
+ # @return [String, nil]
121
214
  #
122
- def to_model
123
- rule = Mihari::Rule.find(id)
215
+ def load_yaml_from_file(path)
216
+ return nil unless Pathname(path).exist?
124
217
 
125
- rule.title = title
126
- rule.description = description
127
- rule.data = data
128
- rule.yaml = yaml
129
-
130
- rule
131
- rescue ActiveRecord::RecordNotFound
132
- Mihari::Rule.new(
133
- id: id,
134
- title: title,
135
- description: description,
136
- data: data,
137
- yaml: yaml
138
- )
218
+ File.read path
139
219
  end
140
220
 
141
221
  #
142
- # @return [Mihari::Analyzers::Rule]
222
+ # Load YAML string from the database
143
223
  #
144
- def to_analyzer
145
- analyzer = Mihari::Analyzers::Rule.new(
146
- title: self[:title],
147
- description: self[:description],
148
- tags: self[:tags],
149
- queries: self[:queries],
150
- allowed_data_types: self[:allowed_data_types],
151
- disallowed_data_values: self[:disallowed_data_values],
152
- emitters: self[:emitters],
153
- id: id
154
- )
155
- analyzer.ignore_old_artifacts = self[:ignore_old_artifacts]
156
- analyzer.ignore_threshold = self[:ignore_threshold]
157
-
158
- analyzer
159
- end
160
-
161
- class << self
162
- include Mixins::Rule
163
-
164
- #
165
- # @param [Mihari::Rule] model
166
- #
167
- # @return [Mihari::Structs::Rule::Rule]
168
- #
169
- def from_model(model)
170
- data = model.data.deep_symbolize_keys
171
- data[:id] = model.id unless data.key?(:id)
172
-
173
- Structs::Rule::Rule.new(data, model.yaml)
174
- end
175
-
176
- #
177
- # @param [String] yaml
178
- #
179
- # @return [Mihari::Structs::Rule::Rule]
180
- #
181
- def from_yaml(yaml)
182
- data = load_erb_yaml(yaml)
183
- Structs::Rule::Rule.new(data, yaml)
224
+ # @param [String] id <description>
225
+ #
226
+ # @return [Hash]
227
+ #
228
+ def load_yaml_from_db(id)
229
+ with_db_connection do
230
+ rule = Mihari::Rule.find(id)
231
+ rule.yaml || rule.symbolized_data.to_yaml
232
+ rescue ActiveRecord::RecordNotFound
233
+ raise ArgumentError, "ID:#{id} is not found in the database"
184
234
  end
185
235
  end
186
236
  end
data/lib/mihari/types.rb CHANGED
@@ -21,5 +21,12 @@ module Mihari
21
21
  "database",
22
22
  "webhook"
23
23
  )
24
+
25
+ EnricherTypes = Types::String.enum(
26
+ "whois",
27
+ "ipinfo",
28
+ "shodan",
29
+ "google_public_dns"
30
+ )
24
31
  end
25
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "4.6.1"
4
+ VERSION = "4.7.2"
5
5
  end
@@ -43,7 +43,7 @@ module Mihari
43
43
  # symbolize hash keys
44
44
  filter = filter.to_h.symbolize_keys
45
45
 
46
- search_filter_with_pagenation = Structs::Alert::SearchFilterWithPagination.new(**filter)
46
+ search_filter_with_pagenation = Structs::Filters::Alert::SearchFilterWithPagination.new(**filter)
47
47
  alerts = Mihari::Alert.search(search_filter_with_pagenation)
48
48
  total = Mihari::Alert.count(search_filter_with_pagenation.without_pagination)
49
49