mihari 5.7.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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