mihari 7.1.0 → 7.1.2
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/.rubocop.yml +2 -0
- data/README.md +2 -1
- data/Rakefile +39 -1
- data/lib/mihari/actor.rb +5 -5
- data/lib/mihari/analyzers/base.rb +18 -11
- data/lib/mihari/analyzers/passivetotal.rb +1 -1
- data/lib/mihari/analyzers/securitytrails.rb +1 -1
- data/lib/mihari/analyzers/virustotal.rb +1 -1
- data/lib/mihari/analyzers/virustotal_intelligence.rb +2 -2
- data/lib/mihari/cli/application.rb +16 -4
- data/lib/mihari/commands/alert.rb +5 -5
- data/lib/mihari/commands/artifact.rb +5 -5
- data/lib/mihari/commands/rule.rb +6 -6
- data/lib/mihari/commands/search.rb +1 -1
- data/lib/mihari/commands/tag.rb +3 -3
- data/lib/mihari/concerns/retriable.rb +1 -1
- data/lib/mihari/constants.rb +1 -1
- data/lib/mihari/database.rb +1 -1
- data/lib/mihari/emitters/base.rb +15 -1
- data/lib/mihari/emitters/database.rb +4 -0
- data/lib/mihari/emitters/misp.rb +7 -0
- data/lib/mihari/emitters/slack.rb +7 -0
- data/lib/mihari/emitters/the_hive.rb +7 -0
- data/lib/mihari/emitters/webhook.rb +7 -0
- data/lib/mihari/enrichers/base.rb +9 -1
- data/lib/mihari/enrichers/google_public_dns.rb +1 -1
- data/lib/mihari/rule.rb +2 -14
- data/lib/mihari/schemas/analyzer.rb +19 -19
- data/lib/mihari/schemas/emitter.rb +5 -5
- data/lib/mihari/schemas/enricher.rb +4 -4
- data/lib/mihari/structs/config.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/{index-U5u7qHZZ.js → index-Guw2aMpk.js} +53 -53
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +28 -28
- data/lib/mihari.rb +14 -3
- data/mihari.gemspec +4 -4
- data/mkdocs.yml +1 -1
- metadata +57 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52d1c8320fdb5233c9738f3b4599868260fef892599cdfb42da6c3af17583b75
|
4
|
+
data.tar.gz: 625cd92558eff5a4d5613e588cc5ee85b9b714a9af211788da1a294d8d54ac45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96811d3ebfffcb27a7577b814be280916dd46bd3477233374ffafd2bf784d9e5133f89b1d7650c722c25e2f678f82380f5ced2eff8f9b7e1f3c96583f54a1114
|
7
|
+
data.tar.gz: 5c13581e670c7aff158e93335fe9c626294cad622f153e61a338f31bb5a1e459302f34a3a9ddf18e0bb322e1359c43a6b782387cdc800f9b047c04e5521f4c70
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# mihari
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/mihari)
|
4
|
-
[](https://github.com/ninoseki/mihari/actions/workflows/ruby.yml)
|
5
|
+
[](https://github.com/ninoseki/mihari/actions/workflows/node.yml)
|
5
6
|
[](https://coveralls.io/github/ninoseki/mihari?branch=master)
|
6
7
|
[](https://www.codefactor.io/repository/github/ninoseki/mihari)
|
7
8
|
|
data/Rakefile
CHANGED
@@ -7,11 +7,49 @@ RSpec::Core::RakeTask.new(:spec)
|
|
7
7
|
|
8
8
|
task default: :spec
|
9
9
|
|
10
|
-
desc "
|
10
|
+
desc "Run rackup (with rerun)"
|
11
11
|
task :rackup do
|
12
12
|
sh "rerun --pattern '{Gemfile.lock,lib/**/*.rb,lib/*.rb}' -- rackup config.ru"
|
13
13
|
end
|
14
14
|
|
15
|
+
def recursive_delete(hash, to_remove)
|
16
|
+
hash.delete(to_remove)
|
17
|
+
hash.each_value do |value|
|
18
|
+
recursive_delete(value, to_remove) if value.is_a? Hash
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_swagger_doc(path)
|
23
|
+
require_relative "lib/mihari"
|
24
|
+
require_relative "lib/mihari/web/application"
|
25
|
+
|
26
|
+
require "rack/test"
|
27
|
+
|
28
|
+
app = Mihari::Web::App.new
|
29
|
+
session = Rack::Test::Session.new(app)
|
30
|
+
|
31
|
+
res = session.request("/api/swagger_doc")
|
32
|
+
|
33
|
+
json = JSON.parse(res.body.to_s)
|
34
|
+
# remove host because it can be varied
|
35
|
+
keys_to_remove = %w[host]
|
36
|
+
keys_to_remove.each do |key|
|
37
|
+
recursive_delete json, key
|
38
|
+
end
|
39
|
+
|
40
|
+
f = File.open(path, "w")
|
41
|
+
f.write json.to_yaml
|
42
|
+
f.close
|
43
|
+
end
|
44
|
+
|
45
|
+
namespace :build do
|
46
|
+
desc "Build Swagger doc"
|
47
|
+
task :swagger, [:path] do |_t, args|
|
48
|
+
args.with_defaults(path: "./frontend/swagger.yaml")
|
49
|
+
build_swagger_doc args.path
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
15
53
|
def ci?
|
16
54
|
ENV.fetch("CI", false)
|
17
55
|
end
|
data/lib/mihari/actor.rb
CHANGED
@@ -55,7 +55,7 @@ module Mihari
|
|
55
55
|
|
56
56
|
joined = self.class.configuration_keys.join(", ")
|
57
57
|
be = (self.class.configuration_keys.length > 1) ? "are" : "is"
|
58
|
-
message = "#{self.class.
|
58
|
+
message = "#{self.class.key} is not configured correctly. #{joined} #{be} missing."
|
59
59
|
raise ConfigurationError, message
|
60
60
|
end
|
61
61
|
|
@@ -75,22 +75,22 @@ module Mihari
|
|
75
75
|
#
|
76
76
|
# @return [String]
|
77
77
|
#
|
78
|
-
def
|
78
|
+
def key
|
79
79
|
to_s.split("::").last.downcase
|
80
80
|
end
|
81
81
|
|
82
82
|
#
|
83
83
|
# @return [Array<String>, nil]
|
84
84
|
#
|
85
|
-
def
|
85
|
+
def key_aliases
|
86
86
|
nil
|
87
87
|
end
|
88
88
|
|
89
89
|
#
|
90
90
|
# @return [Array<String>]
|
91
91
|
#
|
92
|
-
def
|
93
|
-
([
|
92
|
+
def keys
|
93
|
+
([key] + [key_aliases]).flatten.compact.map(&:downcase)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -65,7 +65,7 @@ module Mihari
|
|
65
65
|
# It is set automatically in #initialize
|
66
66
|
artifact = artifact.is_a?(Models::Artifact) ? artifact : Models::Artifact.new(data: artifact)
|
67
67
|
|
68
|
-
artifact.source = self.class.
|
68
|
+
artifact.source = self.class.key
|
69
69
|
artifact.query = query
|
70
70
|
|
71
71
|
artifact
|
@@ -80,7 +80,7 @@ module Mihari
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def result(...)
|
83
|
-
|
83
|
+
result = Try[StandardError] do
|
84
84
|
retry_on_error(
|
85
85
|
times: retry_times,
|
86
86
|
interval: retry_interval,
|
@@ -88,19 +88,26 @@ module Mihari
|
|
88
88
|
) do
|
89
89
|
call(...)
|
90
90
|
end
|
91
|
-
end
|
92
|
-
|
93
|
-
return res.recover { [] } if ignore_error?
|
91
|
+
end.to_result
|
94
92
|
|
95
|
-
result = res.to_result
|
96
93
|
return result if result.success?
|
97
94
|
|
98
95
|
# Wrap failure with AnalyzerError to explicitly name a failed analyzer
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
)
|
96
|
+
error = AnalyzerError.new(result.failure.message, self.class.key, cause: result.failure)
|
97
|
+
return Failure(error) unless ignore_error?
|
98
|
+
|
99
|
+
# Return Success if ignore_error? is true with logging
|
100
|
+
Mihari.logger.warn("Analyzer:#{self.class.key} with #{truncated_query} failed - #{result.failure}")
|
101
|
+
Success([])
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Truncate query for logging
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
def truncated_query
|
110
|
+
query.truncate(32)
|
104
111
|
end
|
105
112
|
|
106
113
|
class << self
|
@@ -38,6 +38,21 @@ module Mihari
|
|
38
38
|
include Concerns::ErrorUnwrappable
|
39
39
|
|
40
40
|
no_commands do
|
41
|
+
#
|
42
|
+
# @param [StandardError] error
|
43
|
+
#
|
44
|
+
# @return [String, Hash, nil]
|
45
|
+
#
|
46
|
+
def error_to_detail(error)
|
47
|
+
# Dirty hack to show the DB error message
|
48
|
+
# (NOTE: #safe_execute block suppress #with_db_connection's error message)
|
49
|
+
if error.is_a?(ActiveRecord::StatementInvalid)
|
50
|
+
return "DB migration is not yet complete. Please run 'mihari db migrate'."
|
51
|
+
end
|
52
|
+
|
53
|
+
error.respond_to?(:detail) ? error.detail : nil
|
54
|
+
end
|
55
|
+
|
41
56
|
def safe_execute
|
42
57
|
yield
|
43
58
|
rescue StandardError => e
|
@@ -48,10 +63,7 @@ module Mihari
|
|
48
63
|
# Raise error if debug is set as true
|
49
64
|
raise error if options["debug"]
|
50
65
|
|
51
|
-
data = Entities::ErrorMessage.represent(
|
52
|
-
message: error.message,
|
53
|
-
detail: error.respond_to?(:detail) ? error.detail : nil
|
54
|
-
)
|
66
|
+
data = Entities::ErrorMessage.represent(message: error.message, detail: error_to_detail(error))
|
55
67
|
warn JSON.pretty_generate(data.as_json)
|
56
68
|
|
57
69
|
Sentry.capture_exception(error) if Sentry.initialized? && !error.is_a?(ValidationError)
|
@@ -26,7 +26,7 @@ module Mihari
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
desc "create
|
29
|
+
desc "create PATH", "Create an alert"
|
30
30
|
around :with_db_connection
|
31
31
|
#
|
32
32
|
# @param [String] path
|
@@ -46,7 +46,7 @@ module Mihari
|
|
46
46
|
puts JSON.pretty_generate(data.as_json)
|
47
47
|
end
|
48
48
|
|
49
|
-
desc "list
|
49
|
+
desc "list QUERY", "List/search alerts"
|
50
50
|
around :with_db_connection
|
51
51
|
method_option :page, type: :numeric, default: 1
|
52
52
|
method_option :limit, type: :numeric, default: 10
|
@@ -67,7 +67,7 @@ module Mihari
|
|
67
67
|
desc "list-transform QUERY", "List/search alerts with transformation"
|
68
68
|
around :with_db_connection
|
69
69
|
method_option :template, type: :string, required: true, aliases: "-t",
|
70
|
-
|
70
|
+
desc: "Jbuilder template stringor a path to a template"
|
71
71
|
method_option :page, type: :numeric, default: 1
|
72
72
|
method_option :limit, type: :numeric, default: 10
|
73
73
|
#
|
@@ -86,7 +86,7 @@ module Mihari
|
|
86
86
|
)
|
87
87
|
end
|
88
88
|
|
89
|
-
desc "get
|
89
|
+
desc "get ID", "Get an alert"
|
90
90
|
around :with_db_connection
|
91
91
|
#
|
92
92
|
# @param [Integer] id
|
@@ -97,7 +97,7 @@ module Mihari
|
|
97
97
|
puts JSON.pretty_generate(data.as_json)
|
98
98
|
end
|
99
99
|
|
100
|
-
desc "delete
|
100
|
+
desc "delete ID", "Delete an alert"
|
101
101
|
around :with_db_connection
|
102
102
|
#
|
103
103
|
# @param [Integer] id
|
@@ -25,7 +25,7 @@ module Mihari
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
desc "list
|
28
|
+
desc "list QUERY", "List/search artifacts"
|
29
29
|
around :with_db_connection
|
30
30
|
method_option :page, type: :numeric, default: 1
|
31
31
|
method_option :limit, type: :numeric, default: 10
|
@@ -46,7 +46,7 @@ module Mihari
|
|
46
46
|
desc "list-transform QUERY", "List/search artifacts with transformation"
|
47
47
|
around :with_db_connection
|
48
48
|
method_option :template, type: :string, required: true, aliases: "-t",
|
49
|
-
|
49
|
+
desc: "Jbuilder template stringor a path to a template"
|
50
50
|
method_option :page, type: :numeric, default: 1
|
51
51
|
method_option :limit, type: :numeric, default: 10
|
52
52
|
#
|
@@ -65,7 +65,7 @@ module Mihari
|
|
65
65
|
)
|
66
66
|
end
|
67
67
|
|
68
|
-
desc "get
|
68
|
+
desc "get ID", "Get an artifact"
|
69
69
|
around :with_db_connection
|
70
70
|
#
|
71
71
|
# @param [Integer] id
|
@@ -76,7 +76,7 @@ module Mihari
|
|
76
76
|
puts JSON.pretty_generate(data.as_json)
|
77
77
|
end
|
78
78
|
|
79
|
-
desc "enrich
|
79
|
+
desc "enrich ID", "Enrich an artifact"
|
80
80
|
around :with_db_connection
|
81
81
|
#
|
82
82
|
# @param [Integer] id
|
@@ -85,7 +85,7 @@ module Mihari
|
|
85
85
|
Services::ArtifactEnricher.result(id).value!
|
86
86
|
end
|
87
87
|
|
88
|
-
desc "delete
|
88
|
+
desc "delete ID", "Delete an artifact"
|
89
89
|
around :with_db_connection
|
90
90
|
#
|
91
91
|
# @param [Integer] id
|
data/lib/mihari/commands/rule.rb
CHANGED
@@ -26,7 +26,7 @@ module Mihari
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
desc "validate
|
29
|
+
desc "validate PATH", "Validate a rule"
|
30
30
|
#
|
31
31
|
# Validate format of a rule
|
32
32
|
#
|
@@ -37,7 +37,7 @@ module Mihari
|
|
37
37
|
puts rule.data.to_yaml
|
38
38
|
end
|
39
39
|
|
40
|
-
desc "init
|
40
|
+
desc "init PATH", "Initialize a new rule"
|
41
41
|
#
|
42
42
|
# Initialize a new rule file
|
43
43
|
#
|
@@ -50,7 +50,7 @@ module Mihari
|
|
50
50
|
Services::RuleInitializer.call(path)
|
51
51
|
end
|
52
52
|
|
53
|
-
desc "list
|
53
|
+
desc "list QUERY", "List/search rules"
|
54
54
|
around :with_db_connection
|
55
55
|
method_option :page, type: :numeric, default: 1
|
56
56
|
method_option :limit, type: :numeric, default: 10
|
@@ -71,7 +71,7 @@ module Mihari
|
|
71
71
|
desc "list-transform QUERY", "List/search rules with transformation"
|
72
72
|
around :with_db_connection
|
73
73
|
method_option :template, type: :string, required: true, aliases: "-t",
|
74
|
-
|
74
|
+
desc: "Jbuilder template stringor a path to a template"
|
75
75
|
method_option :page, type: :numeric, default: 1
|
76
76
|
method_option :limit, type: :numeric, default: 10
|
77
77
|
#
|
@@ -90,7 +90,7 @@ module Mihari
|
|
90
90
|
)
|
91
91
|
end
|
92
92
|
|
93
|
-
desc "get
|
93
|
+
desc "get ID", "Get a rule"
|
94
94
|
around :with_db_connection
|
95
95
|
def get(id)
|
96
96
|
value = Services::RuleGetter.result(id).value!
|
@@ -98,7 +98,7 @@ module Mihari
|
|
98
98
|
puts JSON.pretty_generate(data.as_json)
|
99
99
|
end
|
100
100
|
|
101
|
-
desc "delete
|
101
|
+
desc "delete ID", "Delete a rule"
|
102
102
|
around :with_db_connection
|
103
103
|
#
|
104
104
|
# @param [String] id
|
@@ -11,7 +11,7 @@ module Mihari
|
|
11
11
|
thor.class_eval do
|
12
12
|
include Concerns::DatabaseConnectable
|
13
13
|
|
14
|
-
desc "search
|
14
|
+
desc "search PATH_OR_ID", "Search by a rule"
|
15
15
|
around :with_db_connection
|
16
16
|
method_option :force_overwrite, type: :boolean, default: false, aliases: "-f",
|
17
17
|
desc: "Force overwriting a rule"
|
data/lib/mihari/commands/tag.rb
CHANGED
@@ -25,7 +25,7 @@ module Mihari
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
desc "list", "List/search tags"
|
28
|
+
desc "list QUERY", "List/search tags"
|
29
29
|
around :with_db_connection
|
30
30
|
method_option :page, type: :numeric, default: 1
|
31
31
|
method_option :limit, type: :numeric, default: 10
|
@@ -46,7 +46,7 @@ module Mihari
|
|
46
46
|
desc "list-transform QUERY", "List/search tags with transformation"
|
47
47
|
around :with_db_connection
|
48
48
|
method_option :template, type: :string, required: true, aliases: "-t",
|
49
|
-
|
49
|
+
desc: "Jbuilder template stringor a path to a template"
|
50
50
|
method_option :page, type: :numeric, default: 1
|
51
51
|
method_option :limit, type: :numeric, default: 10
|
52
52
|
#
|
@@ -65,7 +65,7 @@ module Mihari
|
|
65
65
|
)
|
66
66
|
end
|
67
67
|
|
68
|
-
desc "delete
|
68
|
+
desc "delete ID", "Delete a tag"
|
69
69
|
around :with_db_connection
|
70
70
|
#
|
71
71
|
# @param [Integer] id
|
data/lib/mihari/constants.rb
CHANGED
@@ -5,7 +5,7 @@ module Mihari
|
|
5
5
|
DEFAULT_DATA_TYPES = Types::DataTypes.values.freeze
|
6
6
|
|
7
7
|
# @return [Array<Hash>]
|
8
|
-
DEFAULT_EMITTERS = Emitters::Database.
|
8
|
+
DEFAULT_EMITTERS = Emitters::Database.keys.map { |name| { emitter: name.downcase } }.freeze
|
9
9
|
|
10
10
|
# @return [Array<Hash>]
|
11
11
|
DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| { enricher: name.downcase } }.freeze
|
data/lib/mihari/database.rb
CHANGED
@@ -156,7 +156,7 @@ module Mihari
|
|
156
156
|
Mihari::Database.connect unless connected?
|
157
157
|
yield
|
158
158
|
rescue ActiveRecord::StatementInvalid
|
159
|
-
Mihari.logger.error("
|
159
|
+
Mihari.logger.error("DB migration is not yet complete. Please run 'mihari db migrate'.")
|
160
160
|
ensure
|
161
161
|
Mihari::Database.close
|
162
162
|
end
|
data/lib/mihari/emitters/base.rb
CHANGED
@@ -19,6 +19,14 @@ module Mihari
|
|
19
19
|
@rule = rule
|
20
20
|
end
|
21
21
|
|
22
|
+
# A target to emit the data
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
#
|
26
|
+
def target
|
27
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
28
|
+
end
|
29
|
+
|
22
30
|
#
|
23
31
|
# @param [Array<Mihari::Models::Artifact>] artifacts
|
24
32
|
#
|
@@ -30,13 +38,19 @@ module Mihari
|
|
30
38
|
# @return [Dry::Monads::Result::Success<Object>, Dry::Monads::Result::Failure]
|
31
39
|
#
|
32
40
|
def result(artifacts)
|
33
|
-
Try[StandardError] do
|
41
|
+
result = Try[StandardError] do
|
34
42
|
retry_on_error(
|
35
43
|
times: retry_times,
|
36
44
|
interval: retry_interval,
|
37
45
|
exponential_backoff: retry_exponential_backoff
|
38
46
|
) { call(artifacts) }
|
39
47
|
end.to_result
|
48
|
+
|
49
|
+
if result.failure?
|
50
|
+
Mihari.logger.warn("Emitter:#{self.class.key} for #{target.truncate(32)} failed - #{result.failure}")
|
51
|
+
end
|
52
|
+
|
53
|
+
result
|
40
54
|
end
|
41
55
|
|
42
56
|
class << self
|
data/lib/mihari/emitters/misp.rb
CHANGED
@@ -19,17 +19,25 @@ module Mihari
|
|
19
19
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
20
20
|
end
|
21
21
|
|
22
|
+
#
|
23
|
+
# @param [Mihari::Models::Artifact] value
|
22
24
|
#
|
23
25
|
# @return [Dry::Monads::Result::Success<Object>, Dry::Monads::Result::Failure]
|
24
26
|
#
|
25
27
|
def result(value)
|
26
|
-
Try[StandardError] do
|
28
|
+
result = Try[StandardError] do
|
27
29
|
retry_on_error(
|
28
30
|
times: retry_times,
|
29
31
|
interval: retry_interval,
|
30
32
|
exponential_backoff: retry_exponential_backoff
|
31
33
|
) { call value }
|
32
34
|
end.to_result
|
35
|
+
|
36
|
+
if result.failure?
|
37
|
+
Mihari.logger.warn("Enricher:#{self.class.key} for #{value.truncate(32)} failed: #{result.failure}")
|
38
|
+
end
|
39
|
+
|
40
|
+
result
|
33
41
|
end
|
34
42
|
|
35
43
|
class << self
|
data/lib/mihari/rule.rb
CHANGED
@@ -188,20 +188,8 @@ module Mihari
|
|
188
188
|
def bulk_emit
|
189
189
|
return [] if enriched_artifacts.empty?
|
190
190
|
|
191
|
-
|
192
|
-
|
193
|
-
results = Parallel.map(emitters) { |emitter| emitter.result enriched_artifacts }
|
194
|
-
results.zip(emitters).map do |result_and_emitter|
|
195
|
-
result, emitter = result_and_emitter
|
196
|
-
|
197
|
-
case result
|
198
|
-
when Success
|
199
|
-
Mihari.logger.info "Emission by #{emitter.class} succeed"
|
200
|
-
else
|
201
|
-
Mihari.logger.info "Emission by #{emitter.class} failed: #{result.failure}"
|
202
|
-
end
|
203
|
-
|
204
|
-
result.value_or nil
|
191
|
+
Parallel.map(emitters) do |emitter|
|
192
|
+
emitter.result(enriched_artifacts).value_or nil
|
205
193
|
end.compact
|
206
194
|
end
|
207
195
|
|