mihari 7.0.3 → 7.0.5
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.
- checksums.yaml +4 -4
- data/lib/mihari/analyzers/base.rb +9 -1
- data/lib/mihari/cli/application.rb +9 -9
- data/lib/mihari/clients/fofa.rb +4 -1
- data/lib/mihari/commands/rule.rb +0 -2
- data/lib/mihari/concerns/error_unwrappable.rb +3 -2
- data/lib/mihari/concerns/retriable.rb +24 -11
- data/lib/mihari/errors.rb +34 -8
- data/lib/mihari/http.rb +0 -5
- data/lib/mihari/rule.rb +27 -1
- data/lib/mihari/structs/fofa.rb +5 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/ip_addresses.rb +9 -5
- data/lib/mihari/web/endpoints/rules.rb +6 -5
- data/lib/mihari/web/middleware/capture_exceptions.rb +0 -2
- data/lib/mihari/web/public/assets/{index-cQUcyII5.js → index-geliIfjB.js} +48 -48
- data/lib/mihari/web/public/index.html +1 -1
- data/mihari.gemspec +5 -4
- data/mkdocs.yml +1 -1
- metadata +33 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aca048c797a73db1c57f0a5ad5f29066fbfba69451c71df1a26c3ea57853a1e3
|
4
|
+
data.tar.gz: 5ce2d298f325cc9d0ae50a98a6c18a64ec11cc06386504535c8ca647c7c68ad0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac1d6ab49351a2c9c60e703a5fe5fe0f94c84c4294271f297d529a781d3a87a37b515d090a78676bcaf3da225f03617f8900966d108e31557e86319a2407b00b
|
7
|
+
data.tar.gz: f5aad0f8648783f4d3c3cc42a087f646358e5e93586cb394a2644e8f6c32a296b3668ea7758348c0dd55d4868527b4aa6a3d774ee113d5a4a14c39ae3ecd2857
|
@@ -92,7 +92,15 @@ module Mihari
|
|
92
92
|
|
93
93
|
return res.recover { [] } if ignore_error?
|
94
94
|
|
95
|
-
res.to_result
|
95
|
+
result = res.to_result
|
96
|
+
return result if result.success?
|
97
|
+
|
98
|
+
# Wrap failure with AnalyzerError to explicitly name a failed analyzer
|
99
|
+
Failure AnalyzerError.new(
|
100
|
+
result.failure.message,
|
101
|
+
self.class.class_key,
|
102
|
+
cause: result.failure
|
103
|
+
)
|
96
104
|
end
|
97
105
|
|
98
106
|
class << self
|
@@ -41,17 +41,17 @@ module Mihari
|
|
41
41
|
def safe_execute
|
42
42
|
yield
|
43
43
|
rescue StandardError => e
|
44
|
-
|
44
|
+
error = unwrap_error(e)
|
45
45
|
|
46
|
-
raise
|
46
|
+
raise error if options["debug"]
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
data = Entities::ErrorMessage.represent(
|
49
|
+
message: error.message,
|
50
|
+
detail: error.respond_to?(:detail) ? error.detail : nil
|
51
|
+
)
|
52
|
+
warn JSON.pretty_generate(data.as_json)
|
53
|
+
|
54
|
+
Sentry.capture_exception(error) if Sentry.initialized? && !error.is_a?(ValidationError)
|
55
55
|
|
56
56
|
exit 1
|
57
57
|
end
|
data/lib/mihari/clients/fofa.rb
CHANGED
@@ -52,7 +52,10 @@ module Mihari
|
|
52
52
|
def search(query, page:, size: PAGE_SIZE)
|
53
53
|
qbase64 = Base64.urlsafe_encode64(query)
|
54
54
|
params = { qbase64: qbase64, size: size, page: page, email: email, key: api_key }.compact
|
55
|
-
Structs::Fofa::Response.from_dynamic!
|
55
|
+
res = Structs::Fofa::Response.from_dynamic!(get_json("/api/v1/search/all", params: params))
|
56
|
+
raise ResponseError, res.errmsg if res.error
|
57
|
+
|
58
|
+
res
|
56
59
|
end
|
57
60
|
|
58
61
|
#
|
data/lib/mihari/commands/rule.rb
CHANGED
@@ -17,9 +17,10 @@ module Mihari
|
|
17
17
|
receiver = err.receiver
|
18
18
|
case receiver
|
19
19
|
when Dry::Monads::Try::Error
|
20
|
-
|
20
|
+
# Error may be wrapped like Matryoshka
|
21
|
+
unwrap_error receiver.exception
|
21
22
|
when Dry::Monads::Failure
|
22
|
-
receiver.failure
|
23
|
+
unwrap_error receiver.failure
|
23
24
|
else
|
24
25
|
err
|
25
26
|
end
|
@@ -8,35 +8,48 @@ module Mihari
|
|
8
8
|
module Retriable
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
|
-
|
12
|
-
Errno::ECONNRESET,
|
13
|
-
Errno::ECONNABORTED,
|
14
|
-
Errno::EPIPE,
|
11
|
+
RETRIABLE_ERRORS = [
|
15
12
|
OpenSSL::SSL::SSLError,
|
16
13
|
Timeout::Error,
|
17
|
-
|
18
|
-
|
19
|
-
TimeoutError
|
20
|
-
StatusCodeError
|
14
|
+
::HTTP::ConnectionError,
|
15
|
+
::HTTP::ResponseError,
|
16
|
+
::HTTP::TimeoutError
|
21
17
|
].freeze
|
22
18
|
|
19
|
+
DEFAULT_CONDITION = lambda do |error|
|
20
|
+
return true if RETRIABLE_ERRORS.any? { |klass| error.is_a? klass }
|
21
|
+
|
22
|
+
case error
|
23
|
+
when StatusCodeError
|
24
|
+
error.status_code != 404
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
23
30
|
#
|
24
31
|
# Retry on error
|
25
32
|
#
|
26
33
|
# @param [Integer] times
|
27
34
|
# @param [Integer] interval
|
28
35
|
# @param [Boolean] exponential_backoff
|
29
|
-
# @param [
|
36
|
+
# @param [Proc] condition
|
30
37
|
#
|
31
|
-
|
38
|
+
# @param [Object] on
|
39
|
+
def retry_on_error(times: 3, interval: 5, exponential_backoff: true, condition: DEFAULT_CONDITION)
|
32
40
|
try = 0
|
33
41
|
begin
|
34
42
|
try += 1
|
35
43
|
yield
|
36
|
-
rescue
|
44
|
+
rescue StandardError => e
|
45
|
+
# Raise error if it's not a retriable error
|
46
|
+
raise e unless condition.call(e)
|
47
|
+
|
37
48
|
sleep_seconds = exponential_backoff ? interval * (2**(try - 1)) : interval
|
38
49
|
sleep sleep_seconds
|
39
50
|
retry if try < times
|
51
|
+
|
52
|
+
# Raise error if retry times exceed a given times
|
40
53
|
raise e
|
41
54
|
end
|
42
55
|
end
|
data/lib/mihari/errors.rb
CHANGED
@@ -1,27 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "http"
|
4
|
+
|
3
5
|
module Mihari
|
4
6
|
class Error < StandardError; end
|
5
7
|
|
6
8
|
class ValueError < Error; end
|
7
9
|
|
8
|
-
class RetryableError < Error; end
|
9
|
-
|
10
10
|
class ConfigurationError < Error; end
|
11
11
|
|
12
|
-
class
|
12
|
+
class ResponseError < Error; end
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
class AnalyzerError < Error
|
15
|
+
# @return [StandardException, nil]
|
16
|
+
attr_reader :cause
|
16
17
|
|
17
|
-
|
18
|
+
#
|
19
|
+
# @param [String] msg
|
20
|
+
# @param [String] analyzer
|
21
|
+
# @param [StandardException, nil] cause
|
22
|
+
#
|
23
|
+
def initialize(msg, analyzer, cause: nil)
|
24
|
+
super("#{msg} (from #{analyzer})")
|
25
|
+
|
26
|
+
@cause = cause
|
27
|
+
set_backtrace(cause.backtrace) if cause
|
28
|
+
end
|
18
29
|
|
19
|
-
|
30
|
+
def detail
|
31
|
+
return nil unless cause.respond_to?(:detail)
|
32
|
+
|
33
|
+
cause.detail
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class IntegrityError < Error; end
|
20
38
|
|
21
39
|
#
|
22
40
|
# HTTP status code error
|
23
41
|
#
|
24
|
-
class StatusCodeError <
|
42
|
+
class StatusCodeError < ::HTTP::Error
|
25
43
|
# @return [Integer]
|
26
44
|
attr_reader :status_code
|
27
45
|
|
@@ -39,6 +57,10 @@ module Mihari
|
|
39
57
|
@status_code = status_code
|
40
58
|
@body = body
|
41
59
|
end
|
60
|
+
|
61
|
+
def detail
|
62
|
+
{ status_code: status_code, body: body }
|
63
|
+
end
|
42
64
|
end
|
43
65
|
|
44
66
|
#
|
@@ -56,5 +78,9 @@ module Mihari
|
|
56
78
|
|
57
79
|
@errors = errors
|
58
80
|
end
|
81
|
+
|
82
|
+
def detail
|
83
|
+
errors.to_h
|
84
|
+
end
|
59
85
|
end
|
60
86
|
end
|
data/lib/mihari/http.rb
CHANGED
@@ -18,11 +18,6 @@ module Mihari
|
|
18
18
|
)
|
19
19
|
end
|
20
20
|
|
21
|
-
def on_error(_request, error)
|
22
|
-
raise TimeoutError, error if error.is_a?(::HTTP::TimeoutError)
|
23
|
-
raise NetworkError, error if error.is_a?(::HTTP::Error)
|
24
|
-
end
|
25
|
-
|
26
21
|
::HTTP::Options.register_feature(:better_error, self)
|
27
22
|
end
|
28
23
|
|
data/lib/mihari/rule.rb
CHANGED
@@ -83,6 +83,20 @@ module Mihari
|
|
83
83
|
data[:data_types]
|
84
84
|
end
|
85
85
|
|
86
|
+
#
|
87
|
+
# @return [Date, nil]
|
88
|
+
#
|
89
|
+
def created_on
|
90
|
+
data[:created_on]
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# @return [Date, nil]
|
95
|
+
#
|
96
|
+
def updated_on
|
97
|
+
data[:updated_on]
|
98
|
+
end
|
99
|
+
|
86
100
|
#
|
87
101
|
# @return [Array<Mihari::Models::Tag>]
|
88
102
|
#
|
@@ -231,7 +245,7 @@ module Mihari
|
|
231
245
|
#
|
232
246
|
def diff?
|
233
247
|
model = Mihari::Models::Rule.find(id)
|
234
|
-
model.data !=
|
248
|
+
model.data != diff_comparable_data
|
235
249
|
rescue ActiveRecord::RecordNotFound
|
236
250
|
false
|
237
251
|
end
|
@@ -272,6 +286,18 @@ module Mihari
|
|
272
286
|
|
273
287
|
private
|
274
288
|
|
289
|
+
#
|
290
|
+
# @return [Hash]
|
291
|
+
#
|
292
|
+
def diff_comparable_data
|
293
|
+
# data is serialized as JSON so dates (created_on & updated_on) are stringified in there
|
294
|
+
# thus dates & (hash) keys have to be stringified when comparing
|
295
|
+
data.deep_dup.tap do |data|
|
296
|
+
data[:created_on] = created_on.to_s unless created_on.nil?
|
297
|
+
data[:updated_on] = updated_on.to_s unless updated_on.nil?
|
298
|
+
end.deep_stringify_keys
|
299
|
+
end
|
300
|
+
|
275
301
|
#
|
276
302
|
# Check whether a value is a falsepositive value or not
|
277
303
|
#
|
data/lib/mihari/structs/fofa.rb
CHANGED
@@ -8,6 +8,10 @@ module Mihari
|
|
8
8
|
# @return [Boolean]
|
9
9
|
attribute :error, Types::Bool
|
10
10
|
|
11
|
+
# @!attribute [r] errmsg
|
12
|
+
# @return [String, nil]
|
13
|
+
attribute? :errmsg, Types::String.optional
|
14
|
+
|
11
15
|
# @!attribute [r] size
|
12
16
|
# @return [Integer, nil]
|
13
17
|
attribute? :size, Types::Int.optional
|
@@ -35,6 +39,7 @@ module Mihari
|
|
35
39
|
def from_dynamic!(d)
|
36
40
|
new(
|
37
41
|
error: d.fetch("error"),
|
42
|
+
errmsg: d["errmsg"],
|
38
43
|
size: d["size"],
|
39
44
|
page: d["page"],
|
40
45
|
mode: d["mode"],
|
data/lib/mihari/version.rb
CHANGED
@@ -8,13 +8,16 @@ module Mihari
|
|
8
8
|
#
|
9
9
|
class IPAddresses < Grape::API
|
10
10
|
namespace :ip_addresses do
|
11
|
-
desc "Get
|
11
|
+
desc "Get IP address data", {
|
12
12
|
success: Entities::IPAddress,
|
13
|
-
failure: [
|
14
|
-
|
13
|
+
failure: [
|
14
|
+
{ code: 404, model: Entities::ErrorMessage },
|
15
|
+
{ code: 422, model: Entities::ErrorMessage }
|
16
|
+
],
|
17
|
+
summary: "Get IP address data"
|
15
18
|
}
|
16
19
|
params do
|
17
|
-
requires :ip, type: String
|
20
|
+
requires :ip, type: String
|
18
21
|
end
|
19
22
|
get "/:ip", requirements: { ip: %r{[^/]+} } do
|
20
23
|
ip = params[:ip].to_s
|
@@ -34,7 +37,8 @@ module Mihari
|
|
34
37
|
failure = result.failure
|
35
38
|
case failure
|
36
39
|
when Mihari::StatusCodeError
|
37
|
-
error!({ message: "
|
40
|
+
error!({ message: "IP:#{ip} not found" }, failure.status_code) if failure.status_code == 404
|
41
|
+
error!({ message: "IP format invalid" }, failure.status_code) if failure.status_code == 422
|
38
42
|
end
|
39
43
|
raise failure
|
40
44
|
end
|
@@ -17,7 +17,10 @@ module Mihari
|
|
17
17
|
def call(yaml, overwrite: true)
|
18
18
|
rule = Rule.from_yaml(yaml)
|
19
19
|
|
20
|
-
|
20
|
+
# To invoke ActiveRecord::RecordNotFound
|
21
|
+
Models::Rule.find(rule.id) if overwrite
|
22
|
+
|
23
|
+
raise IntegrityError, "ID:#{rule.id} already registered" if rule.exists? && !overwrite
|
21
24
|
|
22
25
|
rule.update_or_create
|
23
26
|
rule
|
@@ -141,13 +144,11 @@ module Mihari
|
|
141
144
|
summary: "Update a rule"
|
142
145
|
}
|
143
146
|
params do
|
144
|
-
requires :id, type: String, documentation: { param_type: "body" }
|
145
147
|
requires :yaml, type: String, documentation: { param_type: "body" }
|
146
148
|
end
|
147
149
|
put "/" do
|
148
150
|
status 201
|
149
151
|
|
150
|
-
id = params[:id].to_s
|
151
152
|
yaml = params[:yaml].to_s
|
152
153
|
|
153
154
|
result = RuleCreateUpdater.result(yaml, overwrite: true)
|
@@ -156,11 +157,11 @@ module Mihari
|
|
156
157
|
failure = result.failure
|
157
158
|
case failure
|
158
159
|
when ActiveRecord::RecordNotFound
|
159
|
-
error!({ message: "
|
160
|
+
error!({ message: "Rule not found" }, 404)
|
160
161
|
when Psych::SyntaxError
|
161
162
|
error!({ message: failure.message }, 422)
|
162
163
|
when ValidationError
|
163
|
-
error!({ message: "Rule format
|
164
|
+
error!({ message: "Rule format invalid", detail: failure.errors.to_h }, 422)
|
164
165
|
end
|
165
166
|
raise failure
|
166
167
|
end
|