mihari 5.4.2 → 5.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/frontend/package-lock.json +2399 -1504
  3. data/frontend/package.json +22 -22
  4. data/lib/mihari/analyzers/base.rb +25 -14
  5. data/lib/mihari/analyzers/binaryedge.rb +2 -48
  6. data/lib/mihari/analyzers/censys.rb +4 -20
  7. data/lib/mihari/analyzers/circl.rb +3 -27
  8. data/lib/mihari/analyzers/crtsh.rb +2 -17
  9. data/lib/mihari/analyzers/dnstwister.rb +2 -4
  10. data/lib/mihari/analyzers/greynoise.rb +5 -4
  11. data/lib/mihari/analyzers/hunterhow.rb +8 -23
  12. data/lib/mihari/analyzers/onyphe.rb +5 -39
  13. data/lib/mihari/analyzers/otx.rb +3 -39
  14. data/lib/mihari/analyzers/passivetotal.rb +4 -42
  15. data/lib/mihari/analyzers/pulsedive.rb +1 -1
  16. data/lib/mihari/analyzers/rule.rb +18 -13
  17. data/lib/mihari/analyzers/securitytrails.rb +4 -42
  18. data/lib/mihari/analyzers/shodan.rb +7 -39
  19. data/lib/mihari/analyzers/urlscan.rb +3 -39
  20. data/lib/mihari/analyzers/virustotal.rb +1 -1
  21. data/lib/mihari/analyzers/virustotal_intelligence.rb +2 -25
  22. data/lib/mihari/analyzers/zoomeye.rb +18 -84
  23. data/lib/mihari/clients/base.rb +9 -1
  24. data/lib/mihari/clients/binaryedge.rb +26 -4
  25. data/lib/mihari/clients/censys.rb +32 -2
  26. data/lib/mihari/clients/circl.rb +28 -1
  27. data/lib/mihari/clients/crtsh.rb +7 -2
  28. data/lib/mihari/clients/dnstwister.rb +4 -2
  29. data/lib/mihari/clients/greynoise.rb +31 -4
  30. data/lib/mihari/clients/hunterhow.rb +41 -3
  31. data/lib/mihari/clients/onyphe.rb +25 -3
  32. data/lib/mihari/clients/otx.rb +40 -0
  33. data/lib/mihari/clients/passivetotal.rb +33 -15
  34. data/lib/mihari/clients/publsedive.rb +1 -1
  35. data/lib/mihari/clients/securitytrails.rb +44 -0
  36. data/lib/mihari/clients/shodan.rb +31 -3
  37. data/lib/mihari/clients/urlscan.rb +32 -6
  38. data/lib/mihari/clients/virustotal.rb +29 -4
  39. data/lib/mihari/clients/zoomeye.rb +53 -2
  40. data/lib/mihari/commands/alert.rb +42 -13
  41. data/lib/mihari/commands/rule.rb +11 -7
  42. data/lib/mihari/commands/search.rb +54 -22
  43. data/lib/mihari/commands/web.rb +1 -1
  44. data/lib/mihari/config.rb +6 -1
  45. data/lib/mihari/emitters/base.rb +9 -3
  46. data/lib/mihari/emitters/slack.rb +1 -1
  47. data/lib/mihari/enrichers/base.rb +13 -0
  48. data/lib/mihari/enrichers/google_public_dns.rb +16 -1
  49. data/lib/mihari/enrichers/ipinfo.rb +9 -13
  50. data/lib/mihari/enrichers/shodan.rb +1 -2
  51. data/lib/mihari/enrichers/whois.rb +2 -2
  52. data/lib/mihari/errors.rb +16 -10
  53. data/lib/mihari/feed/parser.rb +2 -2
  54. data/lib/mihari/models/artifact.rb +1 -1
  55. data/lib/mihari/models/autonomous_system.rb +11 -5
  56. data/lib/mihari/models/cpe.rb +10 -4
  57. data/lib/mihari/models/dns.rb +11 -16
  58. data/lib/mihari/models/geolocation.rb +11 -5
  59. data/lib/mihari/models/port.rb +10 -4
  60. data/lib/mihari/models/reverse_dns.rb +10 -4
  61. data/lib/mihari/models/whois.rb +4 -1
  62. data/lib/mihari/schemas/analyzer.rb +1 -0
  63. data/lib/mihari/services/alert_builder.rb +43 -0
  64. data/lib/mihari/services/alert_proxy.rb +7 -25
  65. data/lib/mihari/services/alert_runner.rb +9 -0
  66. data/lib/mihari/services/rule_builder.rb +47 -0
  67. data/lib/mihari/services/rule_proxy.rb +5 -61
  68. data/lib/mihari/services/rule_runner.rb +9 -4
  69. data/lib/mihari/structs/binaryedge.rb +89 -0
  70. data/lib/mihari/structs/censys.rb +11 -11
  71. data/lib/mihari/structs/greynoise.rb +17 -8
  72. data/lib/mihari/structs/onyphe.rb +7 -7
  73. data/lib/mihari/structs/shodan.rb +7 -6
  74. data/lib/mihari/structs/urlscan.rb +4 -6
  75. data/lib/mihari/structs/virustotal_intelligence.rb +4 -6
  76. data/lib/mihari/type_checker.rb +1 -1
  77. data/lib/mihari/version.rb +1 -1
  78. data/lib/mihari/web/endpoints/alerts.rb +33 -15
  79. data/lib/mihari/web/endpoints/artifacts.rb +53 -25
  80. data/lib/mihari/web/endpoints/configs.rb +2 -2
  81. data/lib/mihari/web/endpoints/ip_addresses.rb +3 -5
  82. data/lib/mihari/web/endpoints/rules.rb +97 -71
  83. data/lib/mihari/web/endpoints/tags.rb +15 -5
  84. data/lib/mihari/web/public/assets/index-ef33a6cd.js +1738 -0
  85. data/lib/mihari/web/public/index.html +1 -1
  86. data/lib/mihari/web/public/redoc-static.html +419 -382
  87. data/lib/mihari.rb +4 -0
  88. data/mihari.gemspec +10 -9
  89. metadata +38 -21
  90. data/lib/mihari/web/public/assets/index-4d7eda9f.js +0 -1738
@@ -6,32 +6,61 @@ module Mihari
6
6
  class << self
7
7
  def included(thor)
8
8
  thor.class_eval do
9
+ include Dry::Monads[:result, :try]
10
+
9
11
  desc "add [PATH]", "Add an alert"
10
12
  #
11
13
  # @param [String] path
12
14
  #
13
15
  def add(path)
14
16
  Mihari::Database.with_db_connection do
15
- proxy = Mihari::Services::AlertProxy.from_path(path)
16
- proxy.validate!
17
+ builder = Mihari::Services::AlertBuilder.new(path)
18
+
19
+ run_proxy_l = ->(proxy) { run_proxy proxy }
20
+ check_nil_l = ->(alert_or_nil) { check_nil alert_or_nil }
17
21
 
18
- runner = Mihari::Services::AlertRunner.new(proxy)
22
+ result = builder.result.bind(run_proxy_l).bind(check_nil_l)
19
23
 
20
- begin
21
- alert = runner.run
22
- rescue ActiveRecord::RecordNotFound => e
23
- # if there is a ActiveRecord::RecordNotFound, output that error without the stack trace
24
- Mihari.logger.error e.to_s
24
+ if result.success?
25
+ alert = result.value!
26
+ data = Mihari::Entities::Alert.represent(alert)
27
+ puts JSON.pretty_generate(data.as_json)
25
28
  return
26
29
  end
27
30
 
28
- if alert.nil?
29
- Mihari.logger.info "There is no new artifact found"
30
- return
31
+ failure = result.failure
32
+ case failure
33
+ when ValidationError
34
+ Mihari.logger.error "Failed to parse the input as an alert:"
35
+ Mihari.logger.error JSON.pretty_generate(failure.errors.to_h)
36
+ when StandardError
37
+ raise failure
38
+ else
39
+ Mihari.logger.info failure
31
40
  end
41
+ end
42
+ end
43
+
44
+ no_commands do
45
+ #
46
+ # @param [Mihari::Services::AlertProxy] proxy
47
+ #
48
+ def run_proxy(proxy)
49
+ Dry::Monads::Try[StandardError] do
50
+ runner = Mihari::Services::AlertRunner.new(proxy)
51
+ runner.run
52
+ end.to_result
53
+ end
32
54
 
33
- data = Mihari::Entities::Alert.represent(alert)
34
- puts JSON.pretty_generate(data.as_json)
55
+ #
56
+ # @param [Mihari::Alert, nil] alert_or_nil
57
+ #
58
+ def check_nil(alert_or_nil)
59
+ if alert_or_nil.nil?
60
+ Failure "There is no new artifact found"
61
+ else
62
+ Success alert_or_nil
63
+ end
35
64
  end
36
65
  end
37
66
  end
@@ -8,6 +8,8 @@ module Mihari
8
8
  class << self
9
9
  def included(thor)
10
10
  thor.class_eval do
11
+ include Dry::Monads[:result, :try]
12
+
11
13
  desc "validate [PATH]", "Validate a rule file"
12
14
  #
13
15
  # Validate format of a rule
@@ -15,15 +17,17 @@ module Mihari
15
17
  # @param [String] path
16
18
  #
17
19
  def validate(path)
18
- rule = Services::RuleProxy.from_path_or_id(path)
19
-
20
- begin
21
- rule.validate!
20
+ res = Dry::Monads::Try[ValidationError] do
21
+ Services::RuleProxy.from_yaml(File.read(path))
22
+ end.fmap do |rule|
22
23
  Mihari.logger.info "Valid format. The input is parsed as the following:"
23
24
  Mihari.logger.info rule.data.to_yaml
24
- rescue RuleValidationError
25
- nil
26
25
  end
26
+
27
+ return unless res.error?
28
+
29
+ Mihari.logger.error "Failed to parse the input as a rule:"
30
+ Mihari.logger.error JSON.pretty_generate(res.exception.errors.to_h)
27
31
  end
28
32
 
29
33
  desc "init [PATH]", "Initialize a new rule file"
@@ -47,7 +51,7 @@ module Mihari
47
51
  # @return [Mihari::Services::Rule]
48
52
  #
49
53
  def rule_template
50
- Services::RuleProxy.from_path File.expand_path("../templates/rule.yml.erb", __dir__)
54
+ Services::RuleProxy.from_yaml File.read(File.expand_path("../templates/rule.yml.erb", __dir__))
51
55
  end
52
56
 
53
57
  #
@@ -6,7 +6,9 @@ module Mihari
6
6
  class << self
7
7
  def included(thor)
8
8
  thor.class_eval do
9
- desc "search [PATH]", "Search by a rule"
9
+ include Dry::Monads[:result, :try]
10
+
11
+ desc "search [PATH_OR_ID]", "Search by a rule"
10
12
  method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
11
13
  #
12
14
  # Search by a rule
@@ -15,39 +17,69 @@ module Mihari
15
17
  #
16
18
  def search(path_or_id)
17
19
  Mihari::Database.with_db_connection do
18
- rule = Services::RuleProxy.from_path_or_id path_or_id
20
+ builder = Services::RuleBuilder.new(path_or_id)
21
+
22
+ check_diff_l = ->(rule) { check_diff rule }
23
+ update_and_run_l = ->(runner) { update_and_run runner }
24
+ check_nil_l = ->(alert_or_nil) { check_nil alert_or_nil }
19
25
 
20
- begin
21
- rule.validate!
22
- rescue RuleValidationError
26
+ result = builder.result.bind(check_diff_l).bind(update_and_run_l).bind(check_nil_l)
27
+
28
+ if result.success?
29
+ alert = result.value!
30
+ data = Mihari::Entities::Alert.represent(alert)
31
+ puts JSON.pretty_generate(data.as_json)
23
32
  return
24
33
  end
25
34
 
35
+ failure = result.failure
36
+ case failure
37
+ when ValidationError
38
+ Mihari.logger.error "Failed to parse the input as a rule:"
39
+ Mihari.logger.error JSON.pretty_generate(failure.errors.to_h)
40
+ when StandardError
41
+ raise failure
42
+ else
43
+ Mihari.logger.info failure
44
+ end
45
+ end
46
+ end
47
+
48
+ no_commands do
49
+ #
50
+ # @param [Mihari::Services::RuleProxy] rule
51
+ #
52
+ def check_diff(rule)
26
53
  force_overwrite = options["force_overwrite"] || false
27
54
  runner = Services::RuleRunner.new(rule, force_overwrite: force_overwrite)
55
+ message = "There is a diff in the rule (#{rule.id}). Are you sure you want to overwrite the rule? (y/n)"
28
56
 
29
- if runner.diff? && !force_overwrite
30
- message = "There is diff in the rule (#{rule.id}). Are you sure you want to overwrite the rule? (y/n)"
31
- return unless yes?(message)
57
+ if runner.diff? && !force_overwrite && !yes?(message)
58
+ return Failure("Stop overwriting the rule (#{rule.id})")
32
59
  end
33
60
 
34
- runner.update_or_create
35
-
36
- begin
37
- alert = runner.run
38
- rescue ConfigurationError => e
39
- # if there is a configuration error, output that error without the stack trace
40
- Mihari.logger.error e.to_s
41
- return
42
- end
61
+ Success runner
62
+ end
43
63
 
44
- if alert.nil?
45
- Mihari.logger.info "There is no new artifact found"
46
- return
64
+ #
65
+ # @param [Mihari::Alert, nil] alert_or_nil
66
+ #
67
+ def check_nil(alert_or_nil)
68
+ if alert_or_nil.nil?
69
+ Failure "There is no new artifact found"
70
+ else
71
+ Success alert_or_nil
47
72
  end
73
+ end
48
74
 
49
- data = Mihari::Entities::Alert.represent(alert)
50
- puts JSON.pretty_generate(data.as_json)
75
+ #
76
+ # @param [Mihari::Services::RuleRunner] runner
77
+ #
78
+ def update_and_run(runner)
79
+ Dry::Monads::Try[StandardError] do
80
+ runner.update_or_create
81
+ runner.run
82
+ end.to_result
51
83
  end
52
84
  end
53
85
  end
@@ -12,7 +12,7 @@ module Mihari
12
12
  method_option :threads, type: :string, default: "0:5", desc: "min:max threads to use"
13
13
  method_option :verbose, type: :boolean, default: true, desc: "Report each request"
14
14
  method_option :worker_timeout, type: :numeric, default: 60, desc: "Worker timeout value (in seconds)"
15
- method_option :hide_config_values, type: :boolean, default: false,
15
+ method_option :hide_config_values, type: :boolean, default: true,
16
16
  desc: "Whether to hide config values or not"
17
17
  method_option :open, type: :boolean, default: true, desc: "Whether to open the app in browser or not"
18
18
  method_option :rack_env, type: :string, default: "production", desc: "Rack environment"
data/lib/mihari/config.rb CHANGED
@@ -93,6 +93,9 @@ module Mihari
93
93
  # @return [Integer]
94
94
  attr_reader :pagination_limit
95
95
 
96
+ # @return [Boolean]
97
+ attr_reader :ignore_error
98
+
96
99
  def initialize
97
100
  @binaryedge_api_key = ENV.fetch("BINARYEDGE_API_KEY", nil)
98
101
 
@@ -141,12 +144,14 @@ module Mihari
141
144
 
142
145
  @sentry_dsn = ENV.fetch("SENTRY_DSN", nil)
143
146
 
144
- @hide_config_values = ENV.fetch("HIDE_CONFIG_VALUES", false)
147
+ @hide_config_values = ENV.fetch("HIDE_CONFIG_VALUES", true)
145
148
 
146
149
  @retry_times = ENV.fetch("RETRY_TIMES", 3).to_i
147
150
  @retry_interval = ENV.fetch("RETRY_INTERVAL", 5).to_i
148
151
 
149
152
  @pagination_limit = ENV.fetch("PAGINATION_LIMIT", 100).to_i
153
+
154
+ @ignore_error = ENV.fetch("IGNORE_ERROR", false)
150
155
  end
151
156
  end
152
157
  end
@@ -3,6 +3,8 @@
3
3
  module Mihari
4
4
  module Emitters
5
5
  class Base
6
+ include Dry::Monads[:result, :try]
7
+
6
8
  include Mixins::Configurable
7
9
  include Mixins::Retriable
8
10
 
@@ -34,11 +36,15 @@ module Mihari
34
36
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
35
37
  end
36
38
 
37
- def run(**params)
38
- retry_on_error { emit(**params) }
39
+ def run
40
+ retry_on_error { emit }
41
+ end
42
+
43
+ def result
44
+ Try[StandardError] { run }.to_result
39
45
  end
40
46
 
41
- def emit(*)
47
+ def emit
42
48
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
43
49
  end
44
50
  end
@@ -114,7 +114,7 @@ module Mihari
114
114
 
115
115
  # @return [String]
116
116
  def defanged_data
117
- @defanged_data ||= data.to_s.gsub(/\./, "[.]")
117
+ @defanged_data ||= data.to_s.gsub(".", "[.]")
118
118
  end
119
119
  end
120
120
 
@@ -6,10 +6,23 @@ module Mihari
6
6
  include Mixins::Configurable
7
7
 
8
8
  class << self
9
+ include Dry::Monads[:result, :try]
10
+
9
11
  def inherited(child)
10
12
  super
11
13
  Mihari.enrichers << child
12
14
  end
15
+
16
+ def query_result(value)
17
+ Try[StandardError] { query(value) }.to_result
18
+ end
19
+
20
+ #
21
+ # @param [String] value
22
+ #
23
+ def query(value)
24
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
25
+ end
13
26
  end
14
27
 
15
28
  # @return [Boolean]
@@ -11,15 +11,30 @@ module Mihari
11
11
  end
12
12
 
13
13
  class << self
14
+ include Dry::Monads[:result]
15
+
14
16
  #
15
17
  # Query Google Public DNS
16
18
  #
17
19
  # @param [String] name
20
+ #
21
+ # @return [Array<Mihari::Structs::Shodan::GooglePublicDNS::Response>]
22
+ #
23
+ def query(name)
24
+ %w[A AAAA CNAME TXT NS].filter_map do |resource_type|
25
+ query_by_type(name, resource_type)
26
+ end
27
+ end
28
+
29
+ #
30
+ # Query Google Public DNS by resource type
31
+ #
32
+ # @param [String] name
18
33
  # @param [String] resource_type
19
34
  #
20
35
  # @return [Mihari::Structs::Shodan::GooglePublicDNS::Response, nil]
21
36
  #
22
- def query(name, resource_type)
37
+ def query_by_type(name, resource_type)
23
38
  url = "https://dns.google/resolve"
24
39
  params = { name: name, type: resource_type }
25
40
  res = HTTP.get(url, params: params)
@@ -17,6 +17,7 @@ module Mihari
17
17
  end
18
18
 
19
19
  class << self
20
+ include Dry::Monads[:result]
20
21
  include Memist::Memoizable
21
22
 
22
23
  #
@@ -28,20 +29,15 @@ module Mihari
28
29
  #
29
30
  def query(ip)
30
31
  headers = {}
32
+
31
33
  token = Mihari.config.ipinfo_api_key
32
- unless token.nil?
33
- headers[:authorization] = "Bearer #{token}"
34
- end
35
-
36
- begin
37
- url = "https://ipinfo.io/#{ip}/json"
38
- res = HTTP.get(url, headers: headers)
39
- data = JSON.parse(res.body.to_s)
40
-
41
- Structs::IPInfo::Response.from_dynamic! data
42
- rescue HTTPError
43
- nil
44
- end
34
+ headers[:authorization] = "Bearer #{token}" unless token.nil?
35
+
36
+ url = "https://ipinfo.io/#{ip}/json"
37
+ res = HTTP.get(url, headers: headers)
38
+ data = JSON.parse(res.body.to_s)
39
+
40
+ Structs::IPInfo::Response.from_dynamic! data
45
41
  end
46
42
  memoize :query
47
43
  end
@@ -11,6 +11,7 @@ module Mihari
11
11
  end
12
12
 
13
13
  class << self
14
+ include Dry::Monads[:result]
14
15
  include Memist::Memoizable
15
16
 
16
17
  #
@@ -26,8 +27,6 @@ module Mihari
26
27
  data = JSON.parse(res.body.to_s)
27
28
 
28
29
  Structs::Shodan::InternetDBResponse.from_dynamic! data
29
- rescue HTTPError
30
- nil
31
30
  end
32
31
  memoize :query
33
32
  end
@@ -14,6 +14,8 @@ module Mihari
14
14
  end
15
15
 
16
16
  class << self
17
+ include Dry::Monads[:result]
18
+
17
19
  #
18
20
  # Query IAIA Whois API
19
21
  #
@@ -47,8 +49,6 @@ module Mihari
47
49
  # set memo
48
50
  @memo[domain] = whois_record
49
51
  whois_record
50
- rescue ::Whois::Error, ::Whois::ParserError, Timeout::Error
51
- nil
52
52
  end
53
53
 
54
54
  def reset_cache
data/lib/mihari/errors.rb CHANGED
@@ -3,22 +3,14 @@
3
3
  module Mihari
4
4
  class Error < StandardError; end
5
5
 
6
- class InvalidInputError < Error; end
6
+ class ValueError < Error; end
7
7
 
8
- class InvalidArtifactFormatError < Error; end
8
+ class TypeError < Error; end
9
9
 
10
10
  class RetryableError < Error; end
11
11
 
12
12
  class FileNotFoundError < Error; end
13
13
 
14
- class FeedParseError < Error; end
15
-
16
- class RuleValidationError < Error; end
17
-
18
- class AlertValidationError < Error; end
19
-
20
- class YAMLSyntaxError < Error; end
21
-
22
14
  class ConfigurationError < Error; end
23
15
 
24
16
  # errors for HTTP interactions
@@ -49,4 +41,18 @@ module Mihari
49
41
  @body = body
50
42
  end
51
43
  end
44
+
45
+ class ValidationError < Error
46
+ attr_reader :errors
47
+
48
+ #
49
+ # @param [String] msg
50
+ # @param [Dry::Schema::MessageSet] errors
51
+ #
52
+ def initialize(msg, errors)
53
+ super(msg)
54
+
55
+ @errors = errors
56
+ end
57
+ end
52
58
  end
@@ -25,8 +25,8 @@ module Mihari
25
25
  def parse(selector)
26
26
  parsed = data.instance_eval(selector)
27
27
 
28
- raise FeedParseError unless parsed.is_a?(Array) || parsed.is_a?(Enumerator)
29
- raise FeedParseError unless parsed.all?(String)
28
+ raise TypeError unless parsed.is_a?(Array) || parsed.is_a?(Enumerator)
29
+ raise TypeError unless parsed.all?(String)
30
30
 
31
31
  parsed.to_a
32
32
  end
@@ -35,7 +35,7 @@ module Mihari
35
35
  attrs = args.first || kwargs
36
36
  data_ = attrs[:data]
37
37
 
38
- raise InvalidArtifactFormatError if data_.is_a?(Array) || data_.is_a?(Hash)
38
+ raise TypeError if data_.is_a?(Array) || data_.is_a?(Hash)
39
39
 
40
40
  super(*args, **kwargs)
41
41
 
@@ -5,6 +5,8 @@ module Mihari
5
5
  belongs_to :artifact
6
6
 
7
7
  class << self
8
+ include Dry::Monads[:result]
9
+
8
10
  #
9
11
  # Build AS
10
12
  #
@@ -13,11 +15,15 @@ module Mihari
13
15
  # @return [Mihari::AutonomousSystem, nil]
14
16
  #
15
17
  def build_by_ip(ip)
16
- res = Enrichers::IPInfo.query(ip)
17
-
18
- return nil if res.nil? || res.asn.nil?
19
-
20
- new(asn: res.asn)
18
+ result = Enrichers::IPInfo.query_result(ip).bind do |res|
19
+ value = res&.asn
20
+ if value.nil?
21
+ Success nil
22
+ else
23
+ Success new(asn: value)
24
+ end
25
+ end
26
+ result.value_or nil
21
27
  end
22
28
  end
23
29
  end
@@ -5,6 +5,8 @@ module Mihari
5
5
  belongs_to :artifact
6
6
 
7
7
  class << self
8
+ include Dry::Monads[:result]
9
+
8
10
  #
9
11
  # Build CPEs
10
12
  #
@@ -13,10 +15,14 @@ module Mihari
13
15
  # @return [Array<Mihari::CPE>]
14
16
  #
15
17
  def build_by_ip(ip)
16
- res = Enrichers::Shodan.query(ip)
17
- return [] if res.nil?
18
-
19
- res.cpes.map { |cpe| new(cpe: cpe) }
18
+ result = Enrichers::Shodan.query_result(ip).bind do |res|
19
+ if res.nil?
20
+ Success []
21
+ else
22
+ Success(res.cpes.map { |cpe| new(cpe: cpe) })
23
+ end
24
+ end
25
+ result.value_or []
20
26
  end
21
27
  end
22
28
  end
@@ -5,6 +5,8 @@ module Mihari
5
5
  belongs_to :artifact
6
6
 
7
7
  class << self
8
+ include Dry::Monads[:result]
9
+
8
10
  #
9
11
  # Build DNS records
10
12
  #
@@ -13,23 +15,16 @@ module Mihari
13
15
  # @return [Array<Mihari::DnsRecord>]
14
16
  #
15
17
  def build_by_domain(domain)
16
- resource_types = %w[A AAAA CNAME TXT NS]
17
- resource_types.map do |resource_type|
18
- get_values domain, resource_type
19
- rescue Resolv::ResolvError
20
- nil
21
- end.flatten.compact
22
- end
23
-
24
- private
25
-
26
- def get_values(domain, resource_type)
27
- response = Enrichers::GooglePublicDNS.query(domain, resource_type)
28
- answers = response.answers || []
29
-
30
- answers.filter_map do |answer|
31
- new(resource: answer.resource_type, value: answer.data)
18
+ result = Enrichers::GooglePublicDNS.query_result(domain).bind do |responses|
19
+ Success(
20
+ responses.map do |res|
21
+ res.answers.map do |answer|
22
+ new(resource: answer.resource_type, value: answer.data)
23
+ end
24
+ end.flatten
25
+ )
32
26
  end
27
+ result.value_or []
33
28
  end
34
29
  end
35
30
  end
@@ -7,6 +7,8 @@ module Mihari
7
7
  belongs_to :artifact
8
8
 
9
9
  class << self
10
+ include Dry::Monads[:result]
11
+
10
12
  #
11
13
  # Build Geolocation
12
14
  #
@@ -15,11 +17,15 @@ module Mihari
15
17
  # @return [Mihari::Geolocation, nil]
16
18
  #
17
19
  def build_by_ip(ip)
18
- res = Enrichers::IPInfo.query(ip)
19
-
20
- return nil if res&.country_code.nil?
21
-
22
- new(country: NormalizeCountry(res.country_code, to: :short), country_code: res.country_code)
20
+ result = Enrichers::IPInfo.query_result(ip).bind do |res|
21
+ value = res&.country_code
22
+ if value.nil?
23
+ Success nil
24
+ else
25
+ Success new(country: NormalizeCountry(value, to: :short), country_code: value)
26
+ end
27
+ end
28
+ result.value_or nil
23
29
  end
24
30
  end
25
31
  end
@@ -5,6 +5,8 @@ module Mihari
5
5
  belongs_to :artifact
6
6
 
7
7
  class << self
8
+ include Dry::Monads[:result]
9
+
8
10
  #
9
11
  # Build ports
10
12
  #
@@ -13,10 +15,14 @@ module Mihari
13
15
  # @return [Array<Mihari::Port>]
14
16
  #
15
17
  def build_by_ip(ip)
16
- res = Enrichers::Shodan.query(ip)
17
- return [] if res.nil?
18
-
19
- res.ports.map { |port| new(port: port) }
18
+ result = Enrichers::Shodan.query_result(ip).bind do |res|
19
+ if res.nil?
20
+ Success []
21
+ else
22
+ Success(res.ports.map { |port| new(port: port) })
23
+ end
24
+ end
25
+ result.value_or []
20
26
  end
21
27
  end
22
28
  end
@@ -5,6 +5,8 @@ module Mihari
5
5
  belongs_to :artifact
6
6
 
7
7
  class << self
8
+ include Dry::Monads[:result]
9
+
8
10
  #
9
11
  # Build reverse DNS names
10
12
  #
@@ -13,10 +15,14 @@ module Mihari
13
15
  # @return [Array<Mihari::ReverseDnsName>]
14
16
  #
15
17
  def build_by_ip(ip)
16
- res = Enrichers::Shodan.query(ip)
17
- return [] if res.nil?
18
-
19
- res.hostnames.map { |name| new(name: name) }
18
+ result = Enrichers::Shodan.query_result(ip).bind do |res|
19
+ if res.nil?
20
+ Success []
21
+ else
22
+ Success(res.hostnames.map { |name| new(name: name) })
23
+ end
24
+ end
25
+ result.value_or []
20
26
  end
21
27
  end
22
28
  end