mihari 5.7.2 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/config.ru +2 -0
  4. data/lib/mihari/actor.rb +1 -1
  5. data/lib/mihari/analyzers/base.rb +3 -0
  6. data/lib/mihari/analyzers/dnstwister.rb +2 -4
  7. data/lib/mihari/analyzers/hunterhow.rb +1 -1
  8. data/lib/mihari/analyzers/urlscan.rb +1 -4
  9. data/lib/mihari/cli/main.rb +2 -12
  10. data/lib/mihari/commands/database.rb +0 -1
  11. data/lib/mihari/config.rb +5 -1
  12. data/lib/mihari/database.rb +9 -5
  13. data/lib/mihari/emitters/misp.rb +2 -2
  14. data/lib/mihari/emitters/slack.rb +8 -11
  15. data/lib/mihari/emitters/the_hive.rb +5 -9
  16. data/lib/mihari/enrichers/base.rb +2 -0
  17. data/lib/mihari/enrichers/google_public_dns.rb +2 -7
  18. data/lib/mihari/enrichers/ipinfo.rb +2 -3
  19. data/lib/mihari/enrichers/shodan.rb +2 -3
  20. data/lib/mihari/enrichers/whois.rb +11 -20
  21. data/lib/mihari/entities/artifact.rb +1 -0
  22. data/lib/mihari/mixins/falsepositive.rb +2 -2
  23. data/lib/mihari/mixins/refang.rb +1 -4
  24. data/lib/mihari/mixins/unwrap_error.rb +27 -0
  25. data/lib/mihari/models/alert.rb +1 -3
  26. data/lib/mihari/models/artifact.rb +18 -12
  27. data/lib/mihari/models/rule.rb +1 -2
  28. data/lib/mihari/rule.rb +14 -10
  29. data/lib/mihari/service.rb +2 -0
  30. data/lib/mihari/services/rule_builder.rb +2 -4
  31. data/lib/mihari/structs/fofa.rb +2 -0
  32. data/lib/mihari/version.rb +1 -1
  33. data/lib/mihari/web/app.rb +5 -3
  34. data/lib/mihari/web/endpoints/alerts.rb +14 -18
  35. data/lib/mihari/web/endpoints/artifacts.rb +17 -22
  36. data/lib/mihari/web/endpoints/configs.rb +0 -1
  37. data/lib/mihari/web/endpoints/ip_addresses.rb +1 -1
  38. data/lib/mihari/web/endpoints/rules.rb +27 -32
  39. data/lib/mihari/web/endpoints/tags.rb +7 -9
  40. data/lib/mihari/web/middleware/connection_adapter.rb +3 -5
  41. data/lib/mihari/web/middleware/error_notification_adapter.rb +10 -6
  42. data/lib/mihari/web/public/assets/{index-ec641cb0.js → index-216d49d1.js} +42 -42
  43. data/lib/mihari/web/public/assets/{index-56fc2187.css → index-4c8509ee.css} +1 -1
  44. data/lib/mihari/web/public/index.html +2 -2
  45. data/lib/mihari/web/public/redoc-static.html +29 -49
  46. data/lib/mihari.rb +9 -10
  47. data/mihari.gemspec +11 -13
  48. data/mkdocs.yml +1 -0
  49. data/requirements.txt +1 -1
  50. metadata +76 -34
  51. data/lib/mihari/services/rule_runner.rb +0 -19
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mihari
2
4
  #
3
5
  # Base class for services
@@ -20,10 +20,8 @@ module Mihari
20
20
  # @return [Hash]
21
21
  #
22
22
  def data
23
- if Mihari::Models::Rule.exists?(path_or_id)
24
- rule = Mihari::Models::Rule.find(path_or_id)
25
- return rule.data
26
- end
23
+ result = Try { Mihari::Models::Rule.find path_or_id }.to_result
24
+ return result.value! if result.success?
27
25
 
28
26
  raise ArgumentError, "#{path_or_id} does not exist" unless Pathname(path_or_id).exist?
29
27
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mihari
2
4
  module Structs
3
5
  module Fofa
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "5.7.2"
4
+ VERSION = "6.1.0"
5
5
  end
@@ -39,7 +39,7 @@ module Mihari
39
39
 
40
40
  def call(env)
41
41
  status, headers, body = API.call(env)
42
- return [status, headers, body] unless headers["X-Cascade"] == "pass"
42
+ return [status, headers, body] unless headers["x-cascade"] == "pass"
43
43
 
44
44
  # Check if the App wants us to pass the response along to others
45
45
  request_path = env["PATH_INFO"]
@@ -53,17 +53,19 @@ module Mihari
53
53
 
54
54
  class << self
55
55
  def instance
56
- @instance ||= Rack::Builder.new do
56
+ Rack::Builder.new do
57
57
  use Rack::Cors do
58
58
  allow do
59
59
  origins "*"
60
60
  resource "*", headers: :any, methods: %i[get post put delete options]
61
61
  end
62
62
  end
63
-
64
63
  use Middleware::ConnectionAdapter
65
64
  use Middleware::ErrorNotificationAdapter
66
65
 
66
+ use Sentry::Rack::CaptureExceptions if Sentry.initialized?
67
+ use BetterErrors::Middleware if ENV["RACK_ENV"] == "development" && defined?(BetterErrors::Middleware)
68
+
67
69
  run App.new
68
70
  end.to_app
69
71
  end
@@ -77,7 +77,6 @@ module Mihari
77
77
  desc "Search alerts", {
78
78
  is_array: true,
79
79
  success: Entities::AlertsWithPagination,
80
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
81
80
  summary: "Search alerts"
82
81
  }
83
82
  params do
@@ -103,31 +102,30 @@ module Mihari
103
102
  end
104
103
 
105
104
  desc "Delete an alert", {
106
- success: Entities::Message,
107
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
105
+ success: { code: 204, model: Entities::Message },
106
+ failure: [{ code: 404, model: Entities::Message }],
108
107
  summary: "Delete an alert"
109
108
  }
110
109
  params do
111
110
  requires :id, type: Integer
112
111
  end
113
112
  delete "/:id" do
113
+ status 204
114
+
114
115
  id = params["id"].to_i
115
116
  result = AlertDestroyer.result(id)
116
- if result.success?
117
- status 204
118
- return present({ message: "" }, with: Entities::Message)
119
- end
117
+ return present({ message: "" }, with: Entities::Message) if result.success?
120
118
 
121
- failure = result.failure
122
- case failure
119
+ case result.failure
123
120
  when ActiveRecord::RecordNotFound
124
121
  error!({ message: "ID:#{id} is not found" }, 404)
125
122
  end
126
- raise failure
123
+ raise result.failure
127
124
  end
128
125
 
129
126
  desc "Create an alert", {
130
- success: Entities::Alert,
127
+ success: { code: 201, model: Entities::Alert },
128
+ failure: [{ code: 404, model: Entities::Message }],
131
129
  summary: "Create an alert"
132
130
  }
133
131
  params do
@@ -135,18 +133,16 @@ module Mihari
135
133
  requires :artifacts, type: Array, documentation: { type: String, is_array: true, param_type: "body" }
136
134
  end
137
135
  post "/" do
136
+ status 201
137
+
138
138
  result = AlertCreator.result(params)
139
- if result.success?
140
- status 201
141
- return present(result.value!, with: Entities::Alert)
142
- end
139
+ return present(result.value!, with: Entities::Alert) if result.success?
143
140
 
144
- failure = result.failure
145
- case failure
141
+ case result.failure
146
142
  when ActiveRecord::RecordNotFound
147
143
  error!({ message: "Rule:#{params["ruleId"]} is not found" }, 404)
148
144
  end
149
- raise failure
145
+ raise result.failure
150
146
  end
151
147
  end
152
148
  end
@@ -64,7 +64,7 @@ module Mihari
64
64
  namespace :artifacts do
65
65
  desc "Get an artifact", {
66
66
  success: Entities::Artifact,
67
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
67
+ failure: [{ code: 404, model: Entities::Message }],
68
68
  summary: "Get an artifact"
69
69
  }
70
70
  params do
@@ -75,60 +75,55 @@ module Mihari
75
75
  result = ArtifactGetter.result(id)
76
76
  return present(result.value!, with: Entities::Artifact) if result.success?
77
77
 
78
- failure = result.failure
79
- case failure
78
+ case result.failure
80
79
  when ActiveRecord::RecordNotFound
81
80
  error!({ message: "ID:#{id} is not found" }, 404)
82
81
  end
83
- raise failure
82
+ raise result.failure
84
83
  end
85
84
 
86
85
  desc "Enrich an artifact", {
87
- success: Entities::Message,
88
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
86
+ success: { code: 201, model: Entities::Message },
87
+ failure: [{ code: 404, model: Entities::Message }],
89
88
  summary: "Enrich an artifact"
90
89
  }
91
90
  params do
92
91
  requires :id, type: Integer
93
92
  end
94
93
  get "/:id/enrich" do
94
+ status 201
95
+
95
96
  id = params["id"].to_i
96
97
  result = ArtifactEnricher.result(id)
97
- if result.success?
98
- status 201
99
- return present({ message: "" }, with: Entities::Message)
100
- end
98
+ return present({ message: "#{id} has been enriched" }, with: Entities::Message) if result.success?
101
99
 
102
- failure = result.failure
103
- case failure
100
+ case result.failure
104
101
  when ActiveRecord::RecordNotFound
105
102
  error!({ message: "ID:#{id} is not found" }, 404)
106
103
  end
107
- raise failure
104
+ raise result.failure
108
105
  end
109
106
 
110
107
  desc "Delete an artifact", {
111
- success: Entities::Message,
112
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
108
+ success: { code: 204, model: Entities::Message },
109
+ failure: [{ code: 404, model: Entities::Message }],
113
110
  summary: "Delete an artifact"
114
111
  }
115
112
  params do
116
113
  requires :id, type: Integer
117
114
  end
118
115
  delete "/:id" do
116
+ status 204
117
+
119
118
  id = params["id"].to_i
120
119
  result = ArtifactDestroyer.result(id)
121
- if result.success?
122
- status 204
123
- return present({ message: "" }, with: Entities::Message)
124
- end
120
+ return present({ message: "" }, with: Entities::Message) if result.success?
125
121
 
126
- failure = result.failure
127
- case failure
122
+ case result.failure
128
123
  when ActiveRecord::RecordNotFound
129
124
  error!({ message: "ID:#{id} is not found" }, 404)
130
125
  end
131
- raise failure
126
+ raise result.failure
132
127
  end
133
128
  end
134
129
  end
@@ -17,7 +17,6 @@ module Mihari
17
17
  configs = (Mihari.analyzers + Mihari.emitters + Mihari.enrichers).filter_map do |klass|
18
18
  Mihari::Structs::Config.from_class(klass)
19
19
  end
20
-
21
20
  present(configs, with: Entities::Config)
22
21
  end
23
22
  end
@@ -21,7 +21,7 @@ module Mihari
21
21
  namespace :ip_addresses do
22
22
  desc "Get an IP address", {
23
23
  success: Entities::IPAddress,
24
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
24
+ failure: [{ code: 404, model: Entities::Message }],
25
25
  summary: "Get an IP address"
26
26
  }
27
27
  params do
@@ -128,7 +128,6 @@ module Mihari
128
128
  desc "Search rules", {
129
129
  is_array: true,
130
130
  success: Entities::RulesWithPagination,
131
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
132
131
  summary: "Search rules"
133
132
  }
134
133
  params do
@@ -153,7 +152,7 @@ module Mihari
153
152
 
154
153
  desc "Get a rule", {
155
154
  success: Entities::Rule,
156
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
155
+ failure: [{ code: 404, model: Entities::Message }],
157
156
  summary: "Get a rule"
158
157
  }
159
158
  params do
@@ -164,50 +163,48 @@ module Mihari
164
163
  result = RuleGetter.result(params[:id].to_s)
165
164
  return present(result.value!, with: Entities::Rule) if result.success?
166
165
 
167
- failure = result.failure
168
- case failure
166
+ case result.failure
169
167
  when ActiveRecord::RecordNotFound
170
168
  error!({ message: "ID:#{id} is not found" }, 404)
171
169
  end
172
- raise failure
170
+ raise result.failure
173
171
  end
174
172
 
175
173
  desc "Run a rule", {
176
- success: Entities::Message,
174
+ success: { code: 201, model: Entities::Message },
175
+ failure: [{ code: 404, model: Entities::Message }],
177
176
  summary: "Run a rule"
178
177
  }
179
178
  params do
180
179
  requires :id, type: String
181
180
  end
182
181
  get "/:id/run" do
182
+ status 201
183
+
183
184
  id = params[:id].to_s
184
185
  result = RuleRunner.result(id)
185
- if result.success?
186
- status 201
187
- return present({ message: "ID:#{id}} ran successfully" }, with: Entities::Message)
188
- end
186
+ return present({ message: "ID:#{id}} has been ran" }, with: Entities::Message) if result.success?
189
187
 
190
- failure = result.failure
191
- case failure
188
+ case result.failure
192
189
  when ActiveRecord::RecordNotFound
193
190
  error!({ message: "ID:#{id} is not found" }, 404)
194
191
  end
195
- raise failure
192
+ raise result.failure
196
193
  end
197
194
 
198
195
  desc "Create a rule", {
199
- success: Entities::Rule,
196
+ success: { code: 201, model: Entities::Rule },
197
+ failure: [{ code: 404, model: Entities::Message }],
200
198
  summary: "Create a rule"
201
199
  }
202
200
  params do
203
201
  requires :yaml, type: String, documentation: { param_type: "body" }
204
202
  end
205
203
  post "/" do
204
+ status 201
205
+
206
206
  result = RuleCreator.result(params[:yaml])
207
- if result.success?
208
- status 201
209
- return present(result.value!.model, with: Entities::Rule)
210
- end
207
+ return present(result.value!.model, with: Entities::Rule) if result.success?
211
208
 
212
209
  failure = result.failure
213
210
  case failure
@@ -220,7 +217,8 @@ module Mihari
220
217
  end
221
218
 
222
219
  desc "Update a rule", {
223
- success: Entities::Rule,
220
+ success: { code: 201, model: Entities::Rule },
221
+ failure: [{ code: 404, model: Entities::Message }],
224
222
  summary: "Update a rule"
225
223
  }
226
224
  params do
@@ -228,12 +226,11 @@ module Mihari
228
226
  requires :yaml, type: String, documentation: { param_type: "body" }
229
227
  end
230
228
  put "/" do
229
+ status 201
230
+
231
231
  id = params[:id].to_s
232
232
  result = RuleUpdater.result(id: id, yaml: params[:yaml].to_s)
233
- if result.success?
234
- status 201
235
- return present(result.value!.model, with: Entities::Rule)
236
- end
233
+ return present(result.value!.model, with: Entities::Rule) if result.success?
237
234
 
238
235
  failure = result.failure
239
236
  case failure
@@ -248,27 +245,25 @@ module Mihari
248
245
  end
249
246
 
250
247
  desc "Delete a rule", {
251
- success: Entities::Message,
252
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
248
+ success: { code: 204, model: Entities::Message },
249
+ failure: [{ code: 404, model: Entities::Message }],
253
250
  summary: "Delete a rule"
254
251
  }
255
252
  params do
256
253
  requires :id, type: String
257
254
  end
258
255
  delete "/:id" do
256
+ status 204
257
+
259
258
  id = params[:id].to_s
260
259
  result = RuleDestroyer.result(id)
261
- if result.success?
262
- status 204
263
- return present({ message: "ID:#{id} is deleted" }, with: Entities::Message)
264
- end
260
+ return present({ message: "ID:#{id} is deleted" }, with: Entities::Message) if result.success?
265
261
 
266
- failure = result.failure
267
- case failure
262
+ case result.failure
268
263
  when ActiveRecord::RecordNotFound
269
264
  error!({ message: "ID:#{id} is not found" }, 404)
270
265
  end
271
- raise failure
266
+ raise result.failure
272
267
  end
273
268
  end
274
269
  end
@@ -28,27 +28,25 @@ module Mihari
28
28
  end
29
29
 
30
30
  desc "Delete a tag", {
31
- success: Entities::Message,
32
- failure: [{ code: 404, message: "Not found", model: Entities::Message }],
31
+ success: { code: 204, model: Entities::Message },
32
+ failure: [{ code: 404, model: Entities::Message }],
33
33
  summary: "Delete a tag"
34
34
  }
35
35
  params do
36
36
  requires :id, type: Integer
37
37
  end
38
38
  delete "/:id" do
39
+ status 204
40
+
39
41
  id = params[:id].to_i
40
42
  result = TagDestroyer.result(id)
41
- if result.success?
42
- status 204
43
- return present({ message: "" }, with: Entities::Message)
44
- end
43
+ return present({ message: "" }, with: Entities::Message) if result.success?
45
44
 
46
- failure = result.failure
47
- case failure
45
+ case result.failure
48
46
  when ActiveRecord::RecordNotFound
49
47
  error!({ message: "ID:#{id} is not found" }, 404)
50
48
  end
51
- raise failure
49
+ raise result.failure
52
50
  end
53
51
  end
54
52
  end
@@ -7,16 +7,14 @@ module Mihari
7
7
  # DB connection adapter for Rack app
8
8
  #
9
9
  class ConnectionAdapter
10
+ attr_reader :app
11
+
10
12
  def initialize(app)
11
13
  @app = app
12
14
  end
13
15
 
14
16
  def call(env)
15
- Mihari::Database.with_db_connection do
16
- status, headers, body = @app.call(env)
17
-
18
- [status, headers, body]
19
- end
17
+ Mihari::Database.with_db_connection { app.call env }
20
18
  end
21
19
  end
22
20
  end
@@ -7,6 +7,10 @@ module Mihari
7
7
  # Error notification adapter for Rack app
8
8
  #
9
9
  class ErrorNotificationAdapter
10
+ include Mihari::Mixins::UnwrapError
11
+
12
+ attr_reader :app
13
+
10
14
  def initialize(app)
11
15
  @app = app
12
16
  end
@@ -14,16 +18,16 @@ module Mihari
14
18
  def with_error_notification
15
19
  yield
16
20
  rescue StandardError => e
17
- Mihari.logger.error e
21
+ unwrapped = unwrap_error(e)
22
+
23
+ Mihari.logger.error unwrapped
24
+ Sentry.capture_exception(unwrapped) if Sentry.initialized?
18
25
 
19
- Sentry.capture_exception(e) if Sentry.initialized?
26
+ raise unwrapped
20
27
  end
21
28
 
22
29
  def call(env)
23
- with_error_notification do
24
- status, headers, body = @app.call(env)
25
- [status, headers, body]
26
- end
30
+ with_error_notification { app.call(env) }
27
31
  end
28
32
  end
29
33
  end