mihari 3.10.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docker/Dockerfile +1 -1
  4. data/lib/mihari/analyzers/base.rb +7 -7
  5. data/lib/mihari/analyzers/binaryedge.rb +8 -5
  6. data/lib/mihari/analyzers/censys.rb +6 -3
  7. data/lib/mihari/analyzers/circl.rb +0 -3
  8. data/lib/mihari/analyzers/crtsh.rb +7 -5
  9. data/lib/mihari/analyzers/dnpedia.rb +4 -4
  10. data/lib/mihari/analyzers/dnstwister.rb +1 -4
  11. data/lib/mihari/analyzers/feed.rb +36 -0
  12. data/lib/mihari/analyzers/greynoise.rb +1 -3
  13. data/lib/mihari/analyzers/onyphe.rb +6 -3
  14. data/lib/mihari/analyzers/otx.rb +0 -3
  15. data/lib/mihari/analyzers/passivetotal.rb +8 -9
  16. data/lib/mihari/analyzers/pulsedive.rb +7 -5
  17. data/lib/mihari/analyzers/rule.rb +11 -5
  18. data/lib/mihari/analyzers/securitytrails.rb +10 -7
  19. data/lib/mihari/analyzers/shodan.rb +15 -8
  20. data/lib/mihari/analyzers/spyse.rb +10 -11
  21. data/lib/mihari/analyzers/urlscan.rb +39 -16
  22. data/lib/mihari/analyzers/virustotal.rb +8 -9
  23. data/lib/mihari/analyzers/virustotal_intelligence.rb +9 -5
  24. data/lib/mihari/analyzers/zoomeye.rb +12 -5
  25. data/lib/mihari/cli/base.rb +0 -5
  26. data/lib/mihari/cli/init.rb +0 -2
  27. data/lib/mihari/cli/main.rb +4 -6
  28. data/lib/mihari/cli/mixins/utils.rb +2 -18
  29. data/lib/mihari/commands/init.rb +0 -18
  30. data/lib/mihari/commands/search.rb +20 -15
  31. data/lib/mihari/commands/validator.rb +7 -19
  32. data/lib/mihari/commands/web.rb +0 -3
  33. data/lib/mihari/database.rb +71 -19
  34. data/lib/mihari/emitters/misp.rb +0 -1
  35. data/lib/mihari/emitters/slack.rb +3 -4
  36. data/lib/mihari/emitters/stdout.rb +0 -2
  37. data/lib/mihari/emitters/the_hive.rb +0 -1
  38. data/lib/mihari/emitters/webhook.rb +1 -5
  39. data/lib/mihari/enrichers/ipinfo.rb +2 -2
  40. data/lib/mihari/errors.rb +6 -0
  41. data/lib/mihari/feed/parser.rb +34 -0
  42. data/lib/mihari/feed/reader.rb +127 -0
  43. data/lib/mihari/mixins/autonomous_system.rb +2 -0
  44. data/lib/mihari/mixins/database.rb +14 -0
  45. data/lib/mihari/mixins/disallowed_data_value.rb +2 -3
  46. data/lib/mihari/mixins/rule.rb +36 -31
  47. data/lib/mihari/models/alert.rb +7 -8
  48. data/lib/mihari/models/artifact.rb +1 -7
  49. data/lib/mihari/models/autonomous_system.rb +0 -2
  50. data/lib/mihari/models/dns.rb +2 -5
  51. data/lib/mihari/models/geolocation.rb +0 -1
  52. data/lib/mihari/models/reverse_dns.rb +0 -3
  53. data/lib/mihari/models/rule.rb +73 -0
  54. data/lib/mihari/models/tag.rb +0 -2
  55. data/lib/mihari/models/tagging.rb +0 -2
  56. data/lib/mihari/models/whois.rb +0 -2
  57. data/lib/mihari/notifiers/exception_notifier.rb +0 -2
  58. data/lib/mihari/schemas/analyzer.rb +0 -5
  59. data/lib/mihari/schemas/macros.rb +0 -2
  60. data/lib/mihari/schemas/rule.rb +21 -7
  61. data/lib/mihari/status.rb +2 -2
  62. data/lib/mihari/structs/alert.rb +2 -3
  63. data/lib/mihari/structs/censys.rb +4 -3
  64. data/lib/mihari/structs/greynoise.rb +4 -3
  65. data/lib/mihari/structs/ipinfo.rb +1 -2
  66. data/lib/mihari/structs/onyphe.rb +6 -5
  67. data/lib/mihari/structs/rule.rb +121 -0
  68. data/lib/mihari/structs/shodan.rb +8 -7
  69. data/lib/mihari/structs/urlscan.rb +50 -0
  70. data/lib/mihari/structs/virustotal_intelligence.rb +4 -3
  71. data/lib/mihari/type_checker.rb +2 -6
  72. data/lib/mihari/types.rb +8 -1
  73. data/lib/mihari/version.rb +1 -1
  74. data/lib/mihari/web/api.rb +6 -0
  75. data/lib/mihari/web/app.rb +5 -7
  76. data/lib/mihari/web/endpoints/alerts.rb +7 -3
  77. data/lib/mihari/web/endpoints/artifacts.rb +6 -3
  78. data/lib/mihari/web/endpoints/command.rb +2 -1
  79. data/lib/mihari/web/endpoints/configs.rb +2 -1
  80. data/lib/mihari/web/endpoints/ip_addresses.rb +2 -1
  81. data/lib/mihari/web/endpoints/rules.rb +140 -0
  82. data/lib/mihari/web/endpoints/sources.rb +2 -1
  83. data/lib/mihari/web/endpoints/tags.rb +4 -2
  84. data/lib/mihari/web/entities/artifact.rb +4 -2
  85. data/lib/mihari/web/entities/rule.rb +35 -0
  86. data/lib/mihari/web/entities/whois.rb +1 -1
  87. data/lib/mihari/web/middleware/connection_adapter.rb +19 -0
  88. data/lib/mihari/web/public/index.html +1 -1
  89. data/lib/mihari/web/public/redoc-static.html +196 -184
  90. data/lib/mihari/web/public/static/js/app.49ab738a.js +21 -0
  91. data/lib/mihari/web/public/static/js/app.49ab738a.js.map +1 -0
  92. data/lib/mihari/web/public/static/js/app.5dc97aae.js +21 -0
  93. data/lib/mihari/web/public/static/js/app.5dc97aae.js.map +1 -0
  94. data/lib/mihari/web/public/static/js/app.f2b8890f.js +21 -0
  95. data/lib/mihari/web/public/static/js/app.f2b8890f.js.map +1 -0
  96. data/lib/mihari/web/public/static/js/app.fbc19869.js +21 -0
  97. data/lib/mihari/web/public/static/js/app.fbc19869.js.map +1 -0
  98. data/lib/mihari.rb +42 -34
  99. data/mihari.gemspec +21 -23
  100. data/sig/lib/mihari/analyzers/binaryedge.rbs +2 -3
  101. data/sig/lib/mihari/analyzers/censys.rbs +2 -3
  102. data/sig/lib/mihari/analyzers/circl.rbs +1 -3
  103. data/sig/lib/mihari/analyzers/crtsh.rbs +1 -3
  104. data/sig/lib/mihari/analyzers/dnpedia.rbs +1 -4
  105. data/sig/lib/mihari/analyzers/dnstwister.rbs +1 -3
  106. data/sig/lib/mihari/analyzers/feed.rbs +20 -0
  107. data/sig/lib/mihari/analyzers/onyphe.rbs +2 -3
  108. data/sig/lib/mihari/analyzers/otx.rbs +1 -3
  109. data/sig/lib/mihari/analyzers/passivetotal.rbs +3 -5
  110. data/sig/lib/mihari/analyzers/pulsedive.rbs +2 -4
  111. data/sig/lib/mihari/analyzers/securitytrails.rbs +3 -5
  112. data/sig/lib/mihari/analyzers/shodan.rbs +2 -3
  113. data/sig/lib/mihari/analyzers/spyse.rbs +4 -6
  114. data/sig/lib/mihari/analyzers/urlscan.rbs +6 -5
  115. data/sig/lib/mihari/analyzers/virustotal.rbs +4 -6
  116. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +2 -3
  117. data/sig/lib/mihari/analyzers/zoomeye.rbs +4 -4
  118. data/sig/lib/mihari/commands/init.rbs +0 -2
  119. data/sig/lib/mihari/commands/validator.rbs +0 -2
  120. data/sig/lib/mihari/emitters/slack.rbs +0 -1
  121. data/sig/lib/mihari/feed/parser.rbs +11 -0
  122. data/sig/lib/mihari/feed/reader.rbs +56 -0
  123. data/sig/lib/mihari/mixins/disallowed_data_value.rbs +0 -2
  124. data/sig/lib/mihari/mixins/rule.rbs +5 -12
  125. data/sig/lib/mihari/models/alert.rbs +1 -1
  126. data/sig/lib/mihari/models/artifact.rbs +2 -0
  127. data/sig/lib/mihari/models/rule.rbs +14 -0
  128. data/sig/lib/mihari/structs/alert.rbs +1 -1
  129. data/sig/lib/mihari/structs/greynoise.rbs +3 -3
  130. data/sig/lib/mihari/structs/rule.rbs +56 -0
  131. data/sig/lib/mihari/structs/shodan.rbs +2 -2
  132. data/sig/lib/mihari/structs/urlscan.rbs +28 -0
  133. data/sig/lib/mihari/types.rbs +4 -0
  134. data/sig/lib/mihari.rbs +0 -2
  135. metadata +102 -147
  136. data/lib/mihari/cli/analyzer.rb +0 -52
  137. data/lib/mihari/commands/binaryedge.rb +0 -21
  138. data/lib/mihari/commands/censys.rb +0 -22
  139. data/lib/mihari/commands/circl.rb +0 -21
  140. data/lib/mihari/commands/crtsh.rb +0 -22
  141. data/lib/mihari/commands/dnpedia.rb +0 -21
  142. data/lib/mihari/commands/dnstwister.rb +0 -21
  143. data/lib/mihari/commands/greynoise.rb +0 -21
  144. data/lib/mihari/commands/json.rb +0 -42
  145. data/lib/mihari/commands/onyphe.rb +0 -21
  146. data/lib/mihari/commands/otx.rb +0 -21
  147. data/lib/mihari/commands/passivetotal.rb +0 -22
  148. data/lib/mihari/commands/pulsedive.rb +0 -21
  149. data/lib/mihari/commands/securitytrails.rb +0 -22
  150. data/lib/mihari/commands/shodan.rb +0 -21
  151. data/lib/mihari/commands/spyse.rb +0 -22
  152. data/lib/mihari/commands/urlscan.rb +0 -23
  153. data/lib/mihari/commands/virustotal.rb +0 -22
  154. data/lib/mihari/commands/virustotal_intelligence.rb +0 -22
  155. data/lib/mihari/commands/zoomeye.rb +0 -22
  156. data/lib/mihari/mixins/configuration.rb +0 -100
  157. data/lib/mihari/mixins/hash.rb +0 -20
  158. data/lib/mihari/schemas/configuration.rb +0 -44
  159. data/lib/mihari/web/public/grape.rb +0 -73
  160. data/sig/lib/mihari/cli/analyzer.rbs +0 -39
  161. data/sig/lib/mihari/commands/binaryedge.rbs +0 -7
  162. data/sig/lib/mihari/commands/censys.rbs +0 -7
  163. data/sig/lib/mihari/commands/circl.rbs +0 -7
  164. data/sig/lib/mihari/commands/crtsh.rbs +0 -7
  165. data/sig/lib/mihari/commands/dnpedia.rbs +0 -7
  166. data/sig/lib/mihari/commands/dnstwister.rbs +0 -7
  167. data/sig/lib/mihari/commands/onyphe.rbs +0 -7
  168. data/sig/lib/mihari/commands/otx.rbs +0 -7
  169. data/sig/lib/mihari/commands/passivetotal.rbs +0 -7
  170. data/sig/lib/mihari/commands/pulsedive.rbs +0 -7
  171. data/sig/lib/mihari/commands/securitytrails.rbs +0 -7
  172. data/sig/lib/mihari/commands/shodan.rbs +0 -7
  173. data/sig/lib/mihari/commands/spyse.rbs +0 -7
  174. data/sig/lib/mihari/commands/urlscan.rbs +0 -7
  175. data/sig/lib/mihari/commands/virustotal.rbs +0 -7
  176. data/sig/lib/mihari/commands/zoomeye.rbs +0 -7
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Mihari
6
4
  module Emitters
7
5
  class StandardOutput < Base
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hachi"
4
- require "net/ping"
5
4
 
6
5
  module Mihari
7
6
  module Emitters
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "net/http"
5
- require "uri"
6
-
7
3
  module Mihari
8
4
  module Emitters
9
5
  class Webhook < Base
@@ -15,7 +11,7 @@ module Mihari
15
11
  def emit(title:, description:, artifacts:, source:, tags:)
16
12
  return if artifacts.empty?
17
13
 
18
- uri = URI(Mihari.config.webhook_url)
14
+ uri = Addressable::URI.parse(Mihari.config.webhook_url)
19
15
  data = {
20
16
  title: title,
21
17
  description: description,
@@ -1,6 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "http"
2
- require "json"
3
- require "memist"
4
4
 
5
5
  module Mihari
6
6
  module Enrichers
data/lib/mihari/errors.rb CHANGED
@@ -8,4 +8,10 @@ module Mihari
8
8
  class RetryableError < Error; end
9
9
 
10
10
  class FileNotFoundError < Error; end
11
+
12
+ class HttpError < Error; end
13
+
14
+ class FeedParseError < Error; end
15
+
16
+ class RuleValidationError < Error; end
11
17
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jr/cli/core_ext"
4
+
5
+ module Mihari
6
+ module Feed
7
+ class Parser
8
+ attr_reader :data
9
+
10
+ #
11
+ # @param [Array<Hash>, Array<Array<String>>] data
12
+ #
13
+ def initialize(data)
14
+ @data = data
15
+ end
16
+
17
+ #
18
+ # Parse data by selector
19
+ #
20
+ # @param [String] selector
21
+ #
22
+ # @return [Array<String>]
23
+ #
24
+ def parse(selector)
25
+ parsed = data.instance_eval(selector)
26
+
27
+ raise FeedParseError unless parsed.is_a?(Array) || parsed.is_a?(Enumerator)
28
+ raise FeedParseError unless parsed.all?(String)
29
+
30
+ parsed.to_a
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Mihari
6
+ module Feed
7
+ class Reader
8
+ attr_reader :uri, :http_request_headers, :http_request_method, :http_request_payload_type, :http_request_payload
9
+
10
+ def initialize(uri, http_request_headers: {}, http_request_method: "GET", http_request_payload_type: nil, http_request_payload: {})
11
+ @uri = Addressable::URI.parse(uri)
12
+ @http_request_headers = http_request_headers
13
+ @http_request_method = http_request_method
14
+ @http_request_payload_type = http_request_payload_type
15
+ @http_request_payload = http_request_payload
16
+ end
17
+
18
+ def read
19
+ return read_file(uri.path) if uri.scheme == "file"
20
+
21
+ return get if http_request_method == "GET"
22
+
23
+ post
24
+ end
25
+
26
+ def get
27
+ uri.query = Addressable::URI.form_encode(http_request_payload)
28
+ get = Net::HTTP::Get.new(uri)
29
+
30
+ request(get)
31
+ end
32
+
33
+ def post
34
+ post = Net::HTTP::Post.new(uri)
35
+
36
+ case http_request_payload_type
37
+ when "application/json"
38
+ post.body = JSON.generate(http_request_payload)
39
+ when "application/x-www-form-urlencoded"
40
+ post.set_form_data(http_request_payload)
41
+ end
42
+
43
+ request(post)
44
+ end
45
+
46
+ #
47
+ # Convert text as JSON
48
+ #
49
+ # @param [String] text
50
+ #
51
+ # @return [Array<Hash>]
52
+ #
53
+ def convert_as_json(text)
54
+ data = JSON.parse(text, symbolize_names: true)
55
+ return data if data.is_a?(Array)
56
+
57
+ [data]
58
+ end
59
+
60
+ #
61
+ # Convert text as CSV
62
+ #
63
+ # @param [String] text
64
+ #
65
+ # @return [Array<Hash>]
66
+ #
67
+ def convert_as_csv(text)
68
+ text_without_comments = text.lines.reject { |line| line.start_with? "#" }.join("\n")
69
+
70
+ CSV.new(text_without_comments).to_a.reject(&:empty?)
71
+ end
72
+
73
+ def https_options
74
+ return { use_ssl: true } if uri.scheme == "https"
75
+
76
+ {}
77
+ end
78
+
79
+ #
80
+ # Make a HTTP request
81
+ #
82
+ # @param [Net::HTTPRequest] req
83
+ #
84
+ # @return [Array<Hash>]
85
+ #
86
+ def request(req)
87
+ Net::HTTP.start(uri.host, uri.port, https_options) do |http|
88
+ # set headers
89
+ http_request_headers.each do |k, v|
90
+ req[k] = v
91
+ end
92
+
93
+ response = http.request(req)
94
+
95
+ code = response.code.to_i
96
+ raise HttpError, "Unsupported response code returned: #{code}" if code != 200
97
+
98
+ body = response.body
99
+
100
+ content_type = response["Content-Type"].to_s
101
+ if content_type.include?("application/json")
102
+ convert_as_json(body)
103
+ else
104
+ convert_as_csv(body)
105
+ end
106
+ end
107
+ end
108
+
109
+ #
110
+ # Read & convert a file
111
+ #
112
+ # @param [String] path
113
+ #
114
+ # @return [Array<Hash>]
115
+ #
116
+ def read_file(path)
117
+ text = File.read(path)
118
+
119
+ if path.end_with?(".json")
120
+ convert_as_json text
121
+ else
122
+ convert_as_csv text
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mihari
2
4
  module Mixins
3
5
  module AutonomousSystem
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Mixins
5
+ module Database
6
+ def with_db_connection
7
+ Mihari::Database.connect
8
+ yield
9
+ ensure
10
+ Mihari::Database.close
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,9 +1,9 @@
1
- require "mem"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
4
  module Mixins
5
5
  module DisallowedDataValue
6
- include Mem
6
+ include Memist::Memoizable
7
7
 
8
8
  #
9
9
  # Normalize a value as a disallowed data value
@@ -19,7 +19,6 @@ module Mihari
19
19
  value_without_slashes = value[1..-2]
20
20
  Regexp.compile value_without_slashes.to_s
21
21
  end
22
-
23
22
  memoize :normalize_disallowed_data_value
24
23
 
25
24
  #
@@ -1,51 +1,56 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "date"
2
- require "pathname"
3
- require "yaml"
4
4
  require "erb"
5
+ require "pathname"
5
6
 
6
7
  module Mihari
7
8
  module Mixins
8
9
  module Rule
10
+ include Mixins::Database
11
+
9
12
  #
10
13
  # Load rule into hash
11
14
  #
12
- # @param [String] path Path to YAML file or YAML string
15
+ # @param [String] path_or_id Path to YAML file or YAML string or ID of a rule in the database
13
16
  #
14
- # @return [Hash]
17
+ # @return [Mihari::Structs::Rule::Rule]
15
18
  #
16
- def load_rule(path)
19
+ def load_rule(path_or_id)
20
+ data = nil
21
+
22
+ data = load_data_from_file(path_or_id) if File.exist?(path_or_id)
23
+ data = load_data_from_db(path_or_id) if data.nil?
24
+
25
+ Structs::Rule::Rule.new(data)
26
+ end
27
+
28
+ def load_data_from_file(path)
17
29
  return YAML.safe_load(File.read(path), permitted_classes: [Date], symbolize_names: true) if Pathname(path).exist?
18
30
 
19
31
  YAML.safe_load(path, symbolize_names: true)
20
32
  end
21
33
 
34
+ def load_data_from_db(id)
35
+ with_db_connection do
36
+ rule = Mihari::Rule.find(id)
37
+ rule.data
38
+ rescue ActiveRecord::RecordNotFound
39
+ raise ArgumentError, "ID:#{id} is not found in the database"
40
+ end
41
+ end
42
+
22
43
  #
23
- # Validate rule schema and return a normalized rule
24
- #
25
- # @param [Hash] rule
44
+ # Validate a rule
26
45
  #
27
- # @return [Hash]
46
+ # @param [Mihari::Structs::Rule::Rule] rule
28
47
  #
29
- def validate_rule(rule)
30
- error_message = "Failed to parse the input as a rule!"
31
-
32
- contract = Schemas::RuleContract.new
33
- begin
34
- result = contract.call(rule)
35
- unless result.errors.empty?
36
- messages = result.errors.messages.map do |message|
37
- path = message.path.map(&:to_s).join
38
- "#{path} #{message.text}"
39
- end
40
- puts error_message.colorize(:red)
41
- raise ArgumentError, messages.join("\n")
42
- end
43
- rescue NoMethodError
44
- puts error_message.colorize(:red)
45
- raise ArgumentError, "Invalid rule schema"
46
- end
47
-
48
- result.to_h
48
+ def validate_rule!(rule)
49
+ rule.validate!
50
+ rescue RuleValidationError => e
51
+ error_message = "Failed to parse the input as a rule:\n#{e.message}"
52
+ puts error_message.colorize(:red)
53
+ raise e
49
54
  end
50
55
 
51
56
  #
@@ -60,8 +65,8 @@ module Mihari
60
65
  data = template.result
61
66
 
62
67
  # validate the template of rule for just in case
63
- rule = YAML.safe_load(data, permitted_classes: [Date], symbolize_names: true)
64
- validate_rule rule
68
+ hashed_data = YAML.safe_load(data, permitted_classes: [Date], symbolize_names: true)
69
+ Structs::Rule::Rule.new(hashed_data)
65
70
 
66
71
  data
67
72
  end
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
- require "active_record/filter"
5
-
6
3
  module Mihari
7
4
  class Alert < ActiveRecord::Base
8
5
  has_many :taggings, dependent: :destroy
9
6
  has_many :artifacts, dependent: :destroy
10
7
  has_many :tags, through: :taggings
11
8
 
9
+ belongs_to :rule
10
+
12
11
  class << self
13
12
  #
14
13
  # Search alerts
15
14
  #
16
15
  # @param [Structs::Alert::SearchFilterWithPagination] filter
17
16
  #
18
- # @return [Array<Hash>]
17
+ # @return [Array<Alert>]
19
18
  #
20
19
  def search(filter)
21
20
  limit = filter.limit.to_i
@@ -55,7 +54,7 @@ module Mihari
55
54
  artifact = artifact.where(dns_records: { value: filter.dns_record }) if filter.dns_record
56
55
  artifact = artifact.where(reverse_dns_names: { name: filter.reverse_dns_name }) if filter.reverse_dns_name
57
56
  # get artifact ids if there is any valid filter for artifact
58
- if filter.has_valid_artifact_filters
57
+ if filter.valid_artifact_filters?
59
58
  artifact_ids = artifact.pluck(:id)
60
59
  # set invalid ID if nothing is matched with the filters
61
60
  artifact_ids = [-1] if artifact_ids.empty?
@@ -70,10 +69,10 @@ module Mihari
70
69
  relation = relation.where(source: filter.source) if filter.source
71
70
  relation = relation.where(title: filter.title) if filter.title
72
71
 
73
- relation = relation.filter(description: { like: "%#{filter.description}%" }) if filter.description
72
+ relation = relation.where("description LIKE ?", "%#{filter.description}%") if filter.description
74
73
 
75
- relation = relation.filter(created_at: { gte: filter.from_at }) if filter.from_at
76
- relation = relation.filter(created_at: { lte: filter.to_at }) if filter.to_at
74
+ relation = relation.where("alerts.created_at >= ?", filter.from_at) if filter.from_at
75
+ relation = relation.where("alerts.created_at <= ?", filter.to_at) if filter.to_at
77
76
 
78
77
  relation
79
78
  end
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
- require "active_record/filter"
5
- require "active_support/core_ext/integer/time"
6
- require "active_support/core_ext/numeric/time"
7
- require "uri"
8
-
9
3
  class ArtifactValidator < ActiveModel::Validator
10
4
  def validate(record)
11
5
  return if record.data_type
@@ -119,7 +113,7 @@ module Mihari
119
113
  def normalize_as_domain(url_or_domain)
120
114
  return url_or_domain if data_type == "domain"
121
115
 
122
- URI.parse(url_or_domain).host
116
+ Addressable::URI.parse(url_or_domain).host
123
117
  end
124
118
 
125
119
  def can_enrich_whois?
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
-
5
3
  module Mihari
6
4
  class AutonomousSystem < ActiveRecord::Base
7
5
  belongs_to :artifact
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
- require "resolv"
5
-
6
3
  module Mihari
7
4
  class DnsRecord < ActiveRecord::Base
8
5
  belongs_to :artifact
@@ -37,7 +34,7 @@ module Mihari
37
34
  resources = Resolv::DNS.new.getresources(domain, resource_type)
38
35
  resource_name = resource_type.to_s.split("::").last
39
36
 
40
- resources.map do |resource|
37
+ resources.filter_map do |resource|
41
38
  # A, AAAA
42
39
  if resource.respond_to?(:address)
43
40
  new(resource: resource_name, value: resource.address.to_s)
@@ -48,7 +45,7 @@ module Mihari
48
45
  elsif resource.respond_to?(:data)
49
46
  new(resource: resource_name, value: resource.data.to_s)
50
47
  end
51
- end.compact
48
+ end
52
49
  end
53
50
  end
54
51
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
3
  require "normalize_country"
5
4
 
6
5
  module Mihari
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
- require "resolv"
5
-
6
3
  module Mihari
7
4
  class ReverseDnsName < ActiveRecord::Base
8
5
  belongs_to :artifact
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ class Rule < ActiveRecord::Base
5
+ has_many :alerts, foreign_key: :source
6
+
7
+ #
8
+ # Returns a hash representative
9
+ #
10
+ # @return [Hash]
11
+ #
12
+ def to_h
13
+ symbolized_data = data.deep_symbolize_keys
14
+ h = { id: id, created_at: created_at, yaml: symbolized_data.to_yaml }
15
+ h.merge symbolized_data
16
+ end
17
+
18
+ class << self
19
+ #
20
+ # Search rules
21
+ #
22
+ # @param [Structs::Rule::SearchFilterWithPagination] filter
23
+ #
24
+ # @return [Array<Rule>]
25
+ #
26
+ def search(filter)
27
+ limit = filter.limit.to_i
28
+ raise ArgumentError, "limit should be bigger than zero" unless limit.positive?
29
+
30
+ page = filter.page.to_i
31
+ raise ArgumentError, "page should be bigger than zero" unless page.positive?
32
+
33
+ offset = (page - 1) * limit
34
+
35
+ relation = build_relation(filter.without_pagination)
36
+
37
+ # TODO: improve queires
38
+ rule_ids = relation.limit(limit).offset(offset).order(created_at: :desc).pluck(:id).uniq
39
+ where(id: [rule_ids]).order(created_at: :desc)
40
+ end
41
+
42
+ #
43
+ # Count alerts
44
+ #
45
+ # @param [Structs::Rule::SearchFilterWithPagination] filter
46
+ #
47
+ # @return [Integer]
48
+ #
49
+ def count(filter)
50
+ relation = build_relation(filter)
51
+ relation.distinct("rules.id").count
52
+ end
53
+
54
+ private
55
+
56
+ def build_relation(filter)
57
+ relation = self
58
+ relation = relation.includes(alerts: :tags)
59
+
60
+ relation = relation.where(alerts: { tags: { name: filter.tag_name } }) if filter.tag_name
61
+
62
+ relation = relation.where(title: filter.title) if filter.title
63
+
64
+ relation = relation.where("rules.description LIKE ?", "%#{filter.description}%") if filter.description
65
+
66
+ relation = relation.where("rules.created_at >= ?", filter.from_at) if filter.from_at
67
+ relation = relation.where("rules.created_at <= ?", filter.to_at) if filter.to_at
68
+
69
+ relation
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
-
5
3
  module Mihari
6
4
  class Tag < ActiveRecord::Base
7
5
  has_many :taggings, dependent: :destroy
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
-
5
3
  module Mihari
6
4
  class Tagging < ActiveRecord::Base
7
5
  belongs_to :alert
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
3
  require "whois-parser"
5
- require "public_suffix"
6
4
 
7
5
  module Mihari
8
6
  class WhoisRecord < ActiveRecord::Base
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Mihari
6
4
  module Notifiers
7
5
  class ExceptionNotifier
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/schema"
4
- require "dry/validation"
5
-
6
- require "mihari/schemas/macros"
7
-
8
3
  module Mihari
9
4
  module Schemas
10
5
  AnalyzerRun = Dry::Schema.Params do
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/types"
4
-
5
3
  module Dry
6
4
  module Schema
7
5
  module Macros
@@ -1,39 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/schema"
4
- require "dry/validation"
5
-
6
- require "mihari/schemas/macros"
7
-
8
3
  module Mihari
9
4
  module Schemas
5
+ AnalyzerOptions = Dry::Schema.Params do
6
+ optional(:interval).value(:integer)
7
+ end
8
+
10
9
  Analyzer = Dry::Schema.Params do
11
10
  required(:analyzer).value(Types::AnalyzerTypes)
12
11
  required(:query).value(:string)
12
+ optional(:options).hash(AnalyzerOptions)
13
13
  end
14
14
 
15
15
  Spyse = Dry::Schema.Params do
16
16
  required(:analyzer).value(Types::String.enum("spyse"))
17
17
  required(:query).value(:string)
18
18
  required(:type).value(Types::String.enum("ip", "domain"))
19
+ optional(:options).hash(AnalyzerOptions)
19
20
  end
20
21
 
21
22
  ZoomEye = Dry::Schema.Params do
22
23
  required(:analyzer).value(Types::String.enum("zoomeye"))
23
24
  required(:query).value(:string)
24
25
  required(:type).value(Types::String.enum("host", "web"))
26
+ optional(:options).hash(AnalyzerOptions)
25
27
  end
26
28
 
27
29
  Crtsh = Dry::Schema.Params do
28
30
  required(:analyzer).value(Types::String.enum("crtsh"))
29
31
  required(:query).value(:string)
30
32
  optional(:exclude_expired).value(:bool).default(true)
33
+ optional(:options).hash(AnalyzerOptions)
31
34
  end
32
35
 
33
36
  Urlscan = Dry::Schema.Params do
34
37
  required(:analyzer).value(Types::String.enum("urlscan"))
35
38
  required(:query).value(:string)
36
- optional(:use_similarity).value(:bool).default(true)
39
+ optional(:options).hash(AnalyzerOptions)
40
+ end
41
+
42
+ Feed = Dry::Schema.Params do
43
+ required(:analyzer).value(Types::String.enum("feed"))
44
+ required(:query).value(:string)
45
+ required(:http_request_method).value(Types::FeedHttpRequestMethods).default("GET")
46
+ required(:http_request_headers).value(:hash).default({})
47
+ required(:http_request_payload).value(:hash).default({})
48
+ required(:selector).value(:string)
49
+ optional(:http_request_payload_type).value(Types::FeedHttpRequestPayloadTypes)
50
+ optional(:options).hash(AnalyzerOptions)
37
51
  end
38
52
 
39
53
  Rule = Dry::Schema.Params do
@@ -47,7 +61,7 @@ module Mihari
47
61
  optional(:created_on).value(:date)
48
62
  optional(:updated_on).value(:date)
49
63
 
50
- required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh }
64
+ required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh | Feed }
51
65
 
52
66
  optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
53
67
  optional(:disallowed_data_values).value(array[:string]).default([])