mihari 3.12.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mihari/analyzers/base.rb +6 -6
  3. data/lib/mihari/analyzers/binaryedge.rb +3 -5
  4. data/lib/mihari/analyzers/censys.rb +1 -3
  5. data/lib/mihari/analyzers/circl.rb +0 -3
  6. data/lib/mihari/analyzers/crtsh.rb +7 -5
  7. data/lib/mihari/analyzers/dnpedia.rb +4 -4
  8. data/lib/mihari/analyzers/dnstwister.rb +1 -4
  9. data/lib/mihari/analyzers/feed.rb +0 -3
  10. data/lib/mihari/analyzers/greynoise.rb +1 -3
  11. data/lib/mihari/analyzers/onyphe.rb +1 -3
  12. data/lib/mihari/analyzers/otx.rb +0 -3
  13. data/lib/mihari/analyzers/passivetotal.rb +8 -9
  14. data/lib/mihari/analyzers/pulsedive.rb +7 -5
  15. data/lib/mihari/analyzers/rule.rb +5 -6
  16. data/lib/mihari/analyzers/securitytrails.rb +10 -7
  17. data/lib/mihari/analyzers/shodan.rb +2 -4
  18. data/lib/mihari/analyzers/spyse.rb +10 -11
  19. data/lib/mihari/analyzers/urlscan.rb +5 -6
  20. data/lib/mihari/analyzers/virustotal.rb +8 -9
  21. data/lib/mihari/analyzers/virustotal_intelligence.rb +4 -5
  22. data/lib/mihari/analyzers/zoomeye.rb +4 -5
  23. data/lib/mihari/cli/base.rb +0 -5
  24. data/lib/mihari/cli/init.rb +0 -2
  25. data/lib/mihari/cli/main.rb +4 -6
  26. data/lib/mihari/cli/mixins/utils.rb +2 -18
  27. data/lib/mihari/commands/init.rb +0 -18
  28. data/lib/mihari/commands/search.rb +20 -15
  29. data/lib/mihari/commands/validator.rb +7 -19
  30. data/lib/mihari/commands/web.rb +0 -3
  31. data/lib/mihari/database.rb +67 -15
  32. data/lib/mihari/emitters/misp.rb +0 -1
  33. data/lib/mihari/emitters/slack.rb +3 -4
  34. data/lib/mihari/emitters/stdout.rb +0 -2
  35. data/lib/mihari/emitters/the_hive.rb +0 -1
  36. data/lib/mihari/emitters/webhook.rb +1 -5
  37. data/lib/mihari/enrichers/ipinfo.rb +0 -2
  38. data/lib/mihari/errors.rb +2 -0
  39. data/lib/mihari/feed/reader.rb +22 -8
  40. data/lib/mihari/mixins/database.rb +14 -0
  41. data/lib/mihari/mixins/disallowed_data_value.rb +1 -4
  42. data/lib/mihari/mixins/rule.rb +34 -31
  43. data/lib/mihari/models/alert.rb +3 -3
  44. data/lib/mihari/models/artifact.rb +0 -5
  45. data/lib/mihari/models/autonomous_system.rb +0 -2
  46. data/lib/mihari/models/dns.rb +0 -3
  47. data/lib/mihari/models/geolocation.rb +0 -1
  48. data/lib/mihari/models/reverse_dns.rb +0 -3
  49. data/lib/mihari/models/rule.rb +73 -0
  50. data/lib/mihari/models/tag.rb +0 -2
  51. data/lib/mihari/models/tagging.rb +0 -2
  52. data/lib/mihari/models/whois.rb +0 -2
  53. data/lib/mihari/notifiers/exception_notifier.rb +0 -2
  54. data/lib/mihari/schemas/analyzer.rb +0 -5
  55. data/lib/mihari/schemas/macros.rb +0 -2
  56. data/lib/mihari/schemas/rule.rb +0 -5
  57. data/lib/mihari/structs/alert.rb +0 -3
  58. data/lib/mihari/structs/censys.rb +3 -4
  59. data/lib/mihari/structs/greynoise.rb +3 -4
  60. data/lib/mihari/structs/ipinfo.rb +0 -3
  61. data/lib/mihari/structs/onyphe.rb +5 -6
  62. data/lib/mihari/structs/rule.rb +121 -0
  63. data/lib/mihari/structs/shodan.rb +3 -4
  64. data/lib/mihari/structs/urlscan.rb +0 -3
  65. data/lib/mihari/structs/virustotal_intelligence.rb +3 -4
  66. data/lib/mihari/type_checker.rb +2 -6
  67. data/lib/mihari/types.rb +0 -2
  68. data/lib/mihari/version.rb +1 -1
  69. data/lib/mihari/web/api.rb +4 -0
  70. data/lib/mihari/web/app.rb +5 -7
  71. data/lib/mihari/web/endpoints/alerts.rb +7 -3
  72. data/lib/mihari/web/endpoints/artifacts.rb +6 -3
  73. data/lib/mihari/web/endpoints/command.rb +2 -1
  74. data/lib/mihari/web/endpoints/configs.rb +2 -1
  75. data/lib/mihari/web/endpoints/ip_addresses.rb +2 -1
  76. data/lib/mihari/web/endpoints/rules.rb +140 -0
  77. data/lib/mihari/web/endpoints/sources.rb +2 -1
  78. data/lib/mihari/web/endpoints/tags.rb +4 -2
  79. data/lib/mihari/web/entities/artifact.rb +2 -0
  80. data/lib/mihari/web/entities/rule.rb +35 -0
  81. data/lib/mihari/web/middleware/connection_adapter.rb +19 -0
  82. data/lib/mihari/web/public/index.html +1 -1
  83. data/lib/mihari/web/public/redoc-static.html +35 -21
  84. data/lib/mihari/web/public/static/js/app.49ab738a.js +21 -0
  85. data/lib/mihari/web/public/static/js/app.49ab738a.js.map +1 -0
  86. data/lib/mihari.rb +40 -34
  87. data/mihari.gemspec +3 -5
  88. data/sig/lib/mihari/analyzers/binaryedge.rbs +0 -3
  89. data/sig/lib/mihari/analyzers/censys.rbs +0 -3
  90. data/sig/lib/mihari/analyzers/circl.rbs +1 -3
  91. data/sig/lib/mihari/analyzers/crtsh.rbs +1 -3
  92. data/sig/lib/mihari/analyzers/dnpedia.rbs +1 -4
  93. data/sig/lib/mihari/analyzers/dnstwister.rbs +1 -3
  94. data/sig/lib/mihari/analyzers/feed.rbs +0 -3
  95. data/sig/lib/mihari/analyzers/onyphe.rbs +0 -3
  96. data/sig/lib/mihari/analyzers/otx.rbs +1 -3
  97. data/sig/lib/mihari/analyzers/passivetotal.rbs +3 -5
  98. data/sig/lib/mihari/analyzers/pulsedive.rbs +2 -4
  99. data/sig/lib/mihari/analyzers/securitytrails.rbs +3 -5
  100. data/sig/lib/mihari/analyzers/shodan.rbs +0 -3
  101. data/sig/lib/mihari/analyzers/spyse.rbs +4 -6
  102. data/sig/lib/mihari/analyzers/urlscan.rbs +1 -3
  103. data/sig/lib/mihari/analyzers/virustotal.rbs +4 -6
  104. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +0 -3
  105. data/sig/lib/mihari/analyzers/zoomeye.rbs +2 -4
  106. data/sig/lib/mihari/commands/init.rbs +0 -2
  107. data/sig/lib/mihari/commands/validator.rbs +0 -2
  108. data/sig/lib/mihari/emitters/slack.rbs +0 -1
  109. data/sig/lib/mihari/feed/reader.rbs +1 -1
  110. data/sig/lib/mihari/mixins/disallowed_data_value.rbs +0 -2
  111. data/sig/lib/mihari/mixins/rule.rbs +5 -12
  112. data/sig/lib/mihari/models/alert.rbs +1 -1
  113. data/sig/lib/mihari/models/artifact.rbs +2 -0
  114. data/sig/lib/mihari/models/rule.rbs +14 -0
  115. data/sig/lib/mihari/structs/rule.rbs +56 -0
  116. data/sig/lib/mihari.rbs +0 -2
  117. metadata +18 -79
  118. data/lib/mihari/cli/analyzer.rb +0 -55
  119. data/lib/mihari/commands/binaryedge.rb +0 -21
  120. data/lib/mihari/commands/censys.rb +0 -22
  121. data/lib/mihari/commands/circl.rb +0 -21
  122. data/lib/mihari/commands/crtsh.rb +0 -22
  123. data/lib/mihari/commands/dnpedia.rb +0 -21
  124. data/lib/mihari/commands/dnstwister.rb +0 -21
  125. data/lib/mihari/commands/feed.rb +0 -26
  126. data/lib/mihari/commands/greynoise.rb +0 -21
  127. data/lib/mihari/commands/json.rb +0 -42
  128. data/lib/mihari/commands/onyphe.rb +0 -21
  129. data/lib/mihari/commands/otx.rb +0 -21
  130. data/lib/mihari/commands/passivetotal.rb +0 -22
  131. data/lib/mihari/commands/pulsedive.rb +0 -21
  132. data/lib/mihari/commands/securitytrails.rb +0 -22
  133. data/lib/mihari/commands/shodan.rb +0 -21
  134. data/lib/mihari/commands/spyse.rb +0 -22
  135. data/lib/mihari/commands/urlscan.rb +0 -22
  136. data/lib/mihari/commands/virustotal.rb +0 -22
  137. data/lib/mihari/commands/virustotal_intelligence.rb +0 -22
  138. data/lib/mihari/commands/zoomeye.rb +0 -22
  139. data/lib/mihari/mixins/configuration.rb +0 -100
  140. data/lib/mihari/mixins/hash.rb +0 -20
  141. data/lib/mihari/schemas/configuration.rb +0 -44
  142. data/lib/mihari/web/public/grape.rb +0 -73
  143. data/sig/lib/mihari/cli/analyzer.rbs +0 -43
  144. data/sig/lib/mihari/commands/binaryedge.rbs +0 -7
  145. data/sig/lib/mihari/commands/censys.rbs +0 -7
  146. data/sig/lib/mihari/commands/circl.rbs +0 -7
  147. data/sig/lib/mihari/commands/crtsh.rbs +0 -7
  148. data/sig/lib/mihari/commands/dnpedia.rbs +0 -7
  149. data/sig/lib/mihari/commands/dnstwister.rbs +0 -7
  150. data/sig/lib/mihari/commands/feed.rbs +0 -7
  151. data/sig/lib/mihari/commands/onyphe.rbs +0 -7
  152. data/sig/lib/mihari/commands/otx.rbs +0 -7
  153. data/sig/lib/mihari/commands/passivetotal.rbs +0 -7
  154. data/sig/lib/mihari/commands/pulsedive.rbs +0 -7
  155. data/sig/lib/mihari/commands/securitytrails.rbs +0 -7
  156. data/sig/lib/mihari/commands/shodan.rbs +0 -7
  157. data/sig/lib/mihari/commands/spyse.rbs +0 -7
  158. data/sig/lib/mihari/commands/urlscan.rbs +0 -7
  159. data/sig/lib/mihari/commands/virustotal.rbs +0 -7
  160. data/sig/lib/mihari/commands/zoomeye.rbs +0 -7
@@ -27,19 +27,6 @@ module Mihari
27
27
  %w[title description artifacts].all? { |key| json.key? key }
28
28
  end
29
29
 
30
- #
31
- # Load configuration and establish DB connection
32
- #
33
- # @return [Hash]
34
- #
35
- def load_configuration
36
- config = options["config"]
37
- return unless config
38
-
39
- Mihari.load_config_from_yaml config
40
- Database.connect
41
- end
42
-
43
30
  #
44
31
  # Run analyzer
45
32
  #
@@ -50,14 +37,12 @@ module Mihari
50
37
  # @return [nil]
51
38
  #
52
39
  def run_analyzer(analyzer_class, query:, options:)
53
- load_configuration
54
-
55
40
  # options = Thor::CoreExt::HashWithIndifferentAccess
56
41
  # ref. https://www.rubydoc.info/github/wycats/thor/Thor/CoreExt/HashWithIndifferentAccess
57
42
  # so need to covert it to a plain hash
58
43
  hash_options = options.to_hash
59
44
 
60
- hash_options = symbolize_hash(hash_options)
45
+ hash_options = hash_options.symbolize_keys
61
46
  hash_options = normalize_options(hash_options)
62
47
 
63
48
  analyzer = analyzer_class.new(query, **hash_options)
@@ -76,8 +61,7 @@ module Mihari
76
61
  # @return [Hash]
77
62
  #
78
63
  def normalize_options(options)
79
- # Delete :config because it is not intended to use for running an analyzer
80
- [:config, :ignore_old_artifacts, :ignore_threshold].each do |ignore_key|
64
+ [:ignore_old_artifacts, :ignore_threshold].each do |ignore_key|
81
65
  options.delete(ignore_key)
82
66
  end
83
67
  options
@@ -1,30 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "colorize"
4
-
5
3
  module Mihari
6
4
  module Commands
7
5
  module Initialization
8
- include Mixins::Configuration
9
6
  include Mixins::Rule
10
7
 
11
8
  def self.included(thor)
12
9
  thor.class_eval do
13
- desc "config", "Create a config file"
14
- method_option :filename, type: :string, default: "mihari.yml"
15
- def config
16
- filename = options["filename"]
17
-
18
- warning = "#{filename} exists. Do you want to overwrite it? (y/n)"
19
- if File.exist?(filename) && !(yes? warning)
20
- return
21
- end
22
-
23
- initialize_config_yaml filename
24
-
25
- puts "The config file is initialized as #{filename}.".colorize(:blue)
26
- end
27
-
28
10
  desc "rule", "Create a rule file"
29
11
  method_option :filename, type: :string, default: "rule.yml"
30
12
  def rule
@@ -3,22 +3,22 @@
3
3
  module Mihari
4
4
  module Commands
5
5
  module Search
6
+ include Mixins::Database
6
7
  include Mixins::Rule
7
8
 
8
9
  def self.included(thor)
9
10
  thor.class_eval do
10
11
  desc "search [RULE]", "Search by a rule"
11
- method_option :config, type: :string, desc: "Path to the config file"
12
- def search_by_rule(rule)
13
- # load configuration
14
- load_configuration
15
-
16
- # convert str(YAML) to hash or str(path/YAML file) to hash
17
- rule = load_rule(rule)
18
-
19
- # validate rule schema
20
- rule = validate_rule(rule)
12
+ def search_by_rule(path_or_id)
13
+ rule = load_rule(path_or_id)
14
+ # validate
15
+ begin
16
+ validate_rule! rule
17
+ rescue RuleValidationError => e
18
+ raise e
19
+ end
21
20
 
21
+ # build and run the analyzer
22
22
  analyzer = build_rule_analyzer(
23
23
  title: rule[:title],
24
24
  description: rule[:description],
@@ -26,8 +26,7 @@ module Mihari
26
26
  tags: rule[:tags],
27
27
  allowed_data_types: rule[:allowed_data_types],
28
28
  disallowed_data_values: rule[:disallowed_data_values],
29
- source: rule[:source],
30
- id: rule[:id]
29
+ id: rule.id
31
30
  )
32
31
 
33
32
  ignore_old_artifacts = rule[:ignore_old_artifacts]
@@ -35,6 +34,14 @@ module Mihari
35
34
 
36
35
  with_error_handling do
37
36
  run_rule_analyzer analyzer, ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold
37
+
38
+ # record a rule
39
+ with_db_connection do
40
+ model = rule.to_model
41
+ model.save
42
+ rescue ActiveRecord::RecordNotUnique
43
+ nil
44
+ end
38
45
  end
39
46
  end
40
47
  end
@@ -51,11 +58,10 @@ module Mihari
51
58
  # @param [Array<String>, nil] tags
52
59
  # @param [Array<String>, nil] allowed_data_types
53
60
  # @param [Array<String>, nil] disallowed_data_values
54
- # @param [String, nil] source
55
61
  #
56
62
  # @return [Mihari::Analyzers::Rule]
57
63
  #
58
- def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, source: nil, id: nil)
64
+ def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, id: nil)
59
65
  tags = [] if tags.nil?
60
66
  allowed_data_types = ALLOWED_DATA_TYPES if allowed_data_types.nil?
61
67
  disallowed_data_values = [] if disallowed_data_values.nil?
@@ -67,7 +73,6 @@ module Mihari
67
73
  queries: queries,
68
74
  allowed_data_types: allowed_data_types,
69
75
  disallowed_data_values: disallowed_data_values,
70
- source: source,
71
76
  id: id
72
77
  )
73
78
  end
@@ -4,32 +4,20 @@ module Mihari
4
4
  module Commands
5
5
  module Validator
6
6
  include Mixins::Rule
7
- include Mixins::Configuration
8
7
 
9
8
  def self.included(thor)
10
9
  thor.class_eval do
11
10
  desc "rule [PATH]", "Validate format of a rule file"
12
11
  def rule(path)
13
- # convert str(YAML) to hash or str(path/YAML file) to hash
14
12
  rule = load_rule(path)
15
13
 
16
- # validate rule schema
17
- validate_rule rule
18
-
19
- puts "Valid format. The input is parsed as the following:"
20
- puts rule.to_yaml
21
- end
22
-
23
- desc "config [PATH]", "Validate format of a config file"
24
- def config(path)
25
- # convert str(YAML) to hash or str(path/YAML file) to hash
26
- config = load_config(path)
27
-
28
- # validate config schema
29
- validate_config config
30
-
31
- puts "Valid format. The input is parsed as the following:"
32
- puts config.to_yaml
14
+ begin
15
+ validate_rule! rule
16
+ puts "Valid format. The input is parsed as the following:"
17
+ puts rule.data.to_yaml
18
+ rescue RuleValidationError
19
+ nil
20
+ end
33
21
  end
34
22
  end
35
23
  end
@@ -10,15 +10,12 @@ module Mihari
10
10
  method_option :host, type: :string, default: "localhost", desc: "Port to listen on"
11
11
  method_option :threads, type: :string, default: "0:16", desc: "min:max threads to use"
12
12
  method_option :verbose, type: :boolean, default: true, desc: "Report each request"
13
- method_option :config, type: :string, desc: "Path to the config file"
14
13
  def web
15
14
  port = options["port"]
16
15
  host = options["host"]
17
16
  threads = options["threads"]
18
17
  verbose = options["verbose"]
19
18
 
20
- load_configuration
21
-
22
19
  # set rack env as production
23
20
  ENV["RACK_ENV"] ||= "production"
24
21
 
@@ -1,6 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
3
+ def env
4
+ ENV["APP_ENV"] || ENV["RACK_ENV"]
5
+ end
6
+
7
+ def test_env?
8
+ env == "test"
9
+ end
10
+
11
+ def development_env?
12
+ env == "development"
13
+ end
4
14
 
5
15
  class InitialSchema < ActiveRecord::Migration[7.0]
6
16
  def change
@@ -85,6 +95,23 @@ class EnrichmentCreatedAtSchema < ActiveRecord::Migration[7.0]
85
95
  end
86
96
  end
87
97
 
98
+ class RuleSchema < ActiveRecord::Migration[7.0]
99
+ def change
100
+ create_table :rules, id: :string, if_not_exists: true do |t|
101
+ t.string :title, null: false
102
+ t.string :description, null: false
103
+ t.json :data, null: false
104
+ t.timestamps
105
+ end
106
+ end
107
+ end
108
+
109
+ class AddeMetadataToArtifactSchema < ActiveRecord::Migration[7.0]
110
+ def change
111
+ add_column :artifacts, :metadata, :json, if_not_exists: true
112
+ end
113
+ end
114
+
88
115
  def adapter
89
116
  return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
90
117
  return "mysql2" if Mihari.config.database.start_with?("mysql2://")
@@ -95,7 +122,34 @@ end
95
122
  module Mihari
96
123
  class Database
97
124
  class << self
125
+ include Memist::Memoizable
126
+
127
+ #
128
+ # DB migraration
129
+ #
130
+ # @param [Symbol] direction
131
+ #
132
+ def migrate(direction)
133
+ ActiveRecord::Migration.verbose = false
134
+
135
+ [
136
+ InitialSchema,
137
+ AddeSourceToArtifactSchema,
138
+ EnrichmentsSchema,
139
+ EnrichmentCreatedAtSchema,
140
+ # v4
141
+ RuleSchema,
142
+ AddeMetadataToArtifactSchema
143
+ ].each { |schema| schema.migrate direction }
144
+ end
145
+ memoize :migrate unless test_env?
146
+
147
+ #
148
+ # Establish DB connection
149
+ #
98
150
  def connect
151
+ return if ActiveRecord::Base.connected?
152
+
99
153
  case adapter
100
154
  when "postgresql", "mysql2"
101
155
  ActiveRecord::Base.establish_connection(Mihari.config.database)
@@ -106,32 +160,30 @@ module Mihari
106
160
  )
107
161
  end
108
162
 
109
- ActiveRecord::Base.logger = Logger.new($stdout) if ENV["RACK_ENV"] == "development"
110
- ActiveRecord::Migration.verbose = false
163
+ ActiveRecord::Base.logger = Logger.new($stdout) if development_env?
111
164
 
112
- InitialSchema.migrate(:up)
113
- AddeSourceToArtifactSchema.migrate(:up)
114
- EnrichmentsSchema.migrate(:up)
115
- EnrichmentCreatedAtSchema.migrate(:up)
165
+ migrate :up
116
166
  rescue StandardError
117
167
  # Do nothing
118
168
  end
119
169
 
170
+ #
171
+ # Close DB connection(s)
172
+ #
120
173
  def close
121
- ActiveRecord::Base.clear_active_connections!
122
- ActiveRecord::Base.connection.close
174
+ return unless ActiveRecord::Base.connected?
175
+
176
+ ActiveRecord::Base.clear_all_connections!
123
177
  end
124
178
 
179
+ #
180
+ # Destory DB
181
+ #
125
182
  def destroy!
126
183
  return unless ActiveRecord::Base.connected?
127
184
 
128
- InitialSchema.migrate(:down)
129
- AddeSourceToArtifactSchema.migrate(:down)
130
- EnrichmentsSchema.migrate(:down)
131
- EnrichmentCreatedAtSchema.migrate(:down)
185
+ migrate :down
132
186
  end
133
187
  end
134
188
  end
135
189
  end
136
-
137
- Mihari::Database.connect
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "misp"
4
- require "net/ping"
5
4
 
6
5
  module Mihari
7
6
  module Emitters
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "slack-notifier"
4
3
  require "digest/sha2"
5
- require "dry-initializer"
4
+ require "slack-notifier"
6
5
 
7
6
  module Mihari
8
7
  module Emitters
9
8
  class Attachment
10
- include Mem
9
+ include Memist::Memoizable
11
10
 
12
11
  extend Dry::Initializer
13
12
 
@@ -63,7 +62,7 @@ module Mihari
63
62
  when "domain"
64
63
  "https://urlscan.io/domain/#{data}"
65
64
  when "url"
66
- uri = URI(data)
65
+ uri = Addressable::URI.parse(data)
67
66
  "https://urlscan.io/domain/#{uri.hostname}"
68
67
  end
69
68
  end
@@ -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,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "http"
4
- require "json"
5
- require "memist"
6
4
 
7
5
  module Mihari
8
6
  module Enrichers
data/lib/mihari/errors.rb CHANGED
@@ -12,4 +12,6 @@ module Mihari
12
12
  class HttpError < Error; end
13
13
 
14
14
  class FeedParseError < Error; end
15
+
16
+ class RuleValidationError < Error; end
15
17
  end
@@ -1,17 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "csv"
4
- require "json"
5
- require "net/https"
6
- require "uri"
7
4
 
8
5
  module Mihari
9
6
  module Feed
10
7
  class Reader
11
8
  attr_reader :uri, :http_request_headers, :http_request_method, :http_request_payload_type, :http_request_payload
12
9
 
13
- def initialize(url, http_request_headers: {}, http_request_method: "GET", http_request_payload_type: nil, http_request_payload: {})
14
- @uri = URI(url)
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)
15
12
  @http_request_headers = http_request_headers
16
13
  @http_request_method = http_request_method
17
14
  @http_request_payload_type = http_request_payload_type
@@ -19,13 +16,15 @@ module Mihari
19
16
  end
20
17
 
21
18
  def read
19
+ return read_file(uri.path) if uri.scheme == "file"
20
+
22
21
  return get if http_request_method == "GET"
23
22
 
24
23
  post
25
24
  end
26
25
 
27
26
  def get
28
- uri.query = URI.encode_www_form(http_request_payload)
27
+ uri.query = Addressable::URI.form_encode(http_request_payload)
29
28
  get = Net::HTTP::Get.new(uri)
30
29
 
31
30
  request(get)
@@ -99,13 +98,28 @@ module Mihari
99
98
  body = response.body
100
99
 
101
100
  content_type = response["Content-Type"].to_s
102
- data = if content_type.include?("application/json")
101
+ if content_type.include?("application/json")
103
102
  convert_as_json(body)
104
103
  else
105
104
  convert_as_csv(body)
106
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)
107
118
 
108
- data
119
+ if path.end_with?(".json")
120
+ convert_as_json text
121
+ else
122
+ convert_as_csv text
109
123
  end
110
124
  end
111
125
  end
@@ -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,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mem"
4
-
5
3
  module Mihari
6
4
  module Mixins
7
5
  module DisallowedDataValue
8
- include Mem
6
+ include Memist::Memoizable
9
7
 
10
8
  #
11
9
  # Normalize a value as a disallowed data value
@@ -21,7 +19,6 @@ module Mihari
21
19
  value_without_slashes = value[1..-2]
22
20
  Regexp.compile value_without_slashes.to_s
23
21
  end
24
-
25
22
  memoize :normalize_disallowed_data_value
26
23
 
27
24
  #
@@ -1,53 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "date"
4
- require "pathname"
5
- require "yaml"
6
4
  require "erb"
5
+ require "pathname"
7
6
 
8
7
  module Mihari
9
8
  module Mixins
10
9
  module Rule
10
+ include Mixins::Database
11
+
11
12
  #
12
13
  # Load rule into hash
13
14
  #
14
- # @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
15
16
  #
16
- # @return [Hash]
17
+ # @return [Mihari::Structs::Rule::Rule]
17
18
  #
18
- 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)
19
29
  return YAML.safe_load(File.read(path), permitted_classes: [Date], symbolize_names: true) if Pathname(path).exist?
20
30
 
21
31
  YAML.safe_load(path, symbolize_names: true)
22
32
  end
23
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
+
24
43
  #
25
- # Validate rule schema and return a normalized rule
26
- #
27
- # @param [Hash] rule
44
+ # Validate a rule
28
45
  #
29
- # @return [Hash]
46
+ # @param [Mihari::Structs::Rule::Rule] rule
30
47
  #
31
- def validate_rule(rule)
32
- error_message = "Failed to parse the input as a rule!"
33
-
34
- contract = Schemas::RuleContract.new
35
- begin
36
- result = contract.call(rule)
37
- unless result.errors.empty?
38
- messages = result.errors.messages.map do |message|
39
- path = message.path.map(&:to_s).join
40
- "#{path} #{message.text}"
41
- end
42
- puts error_message.colorize(:red)
43
- raise ArgumentError, messages.join("\n")
44
- end
45
- rescue NoMethodError
46
- puts error_message.colorize(:red)
47
- raise ArgumentError, "Invalid rule schema"
48
- end
49
-
50
- 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
51
54
  end
52
55
 
53
56
  #
@@ -62,8 +65,8 @@ module Mihari
62
65
  data = template.result
63
66
 
64
67
  # validate the template of rule for just in case
65
- rule = YAML.safe_load(data, permitted_classes: [Date], symbolize_names: true)
66
- validate_rule rule
68
+ hashed_data = YAML.safe_load(data, permitted_classes: [Date], symbolize_names: true)
69
+ Structs::Rule::Rule.new(hashed_data)
67
70
 
68
71
  data
69
72
  end
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
-
5
3
  module Mihari
6
4
  class Alert < ActiveRecord::Base
7
5
  has_many :taggings, dependent: :destroy
8
6
  has_many :artifacts, dependent: :destroy
9
7
  has_many :tags, through: :taggings
10
8
 
9
+ belongs_to :rule
10
+
11
11
  class << self
12
12
  #
13
13
  # Search alerts
14
14
  #
15
15
  # @param [Structs::Alert::SearchFilterWithPagination] filter
16
16
  #
17
- # @return [Array<Hash>]
17
+ # @return [Array<Alert>]
18
18
  #
19
19
  def search(filter)
20
20
  limit = filter.limit.to_i
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
- require "active_support/core_ext/integer/time"
5
- require "active_support/core_ext/numeric/time"
6
- require "addressable/uri"
7
-
8
3
  class ArtifactValidator < ActiveModel::Validator
9
4
  def validate(record)
10
5
  return if record.data_type
@@ -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