mihari 4.12.0 → 5.0.1
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/Steepfile +0 -1
- data/lib/mihari/analyzers/base.rb +18 -37
- data/lib/mihari/analyzers/rule.rb +23 -36
- data/lib/mihari/cli/main.rb +6 -11
- data/lib/mihari/commands/initializer.rb +47 -0
- data/lib/mihari/commands/searcher.rb +57 -0
- data/lib/mihari/commands/validator.rb +2 -2
- data/lib/mihari/constants.rb +3 -3
- data/lib/mihari/database.rb +52 -87
- data/lib/mihari/emitters/database.rb +16 -7
- data/lib/mihari/emitters/misp.rb +13 -5
- data/lib/mihari/emitters/slack.rb +15 -8
- data/lib/mihari/emitters/the_hive.rb +42 -21
- data/lib/mihari/emitters/webhook.rb +99 -31
- data/lib/mihari/entities/alert.rb +7 -5
- data/lib/mihari/entities/artifact.rb +20 -8
- data/lib/mihari/entities/config.rb +2 -6
- data/lib/mihari/entities/rule.rb +8 -0
- data/lib/mihari/http.rb +13 -13
- data/lib/mihari/mixins/{disallowed_data_value.rb → falsepositive.rb} +8 -8
- data/lib/mihari/models/alert.rb +2 -15
- data/lib/mihari/models/artifact.rb +28 -17
- data/lib/mihari/models/rule.rb +7 -13
- data/lib/mihari/schemas/emitter.rb +6 -8
- data/lib/mihari/schemas/rule.rb +11 -13
- data/lib/mihari/structs/config.rb +41 -0
- data/lib/mihari/structs/filters.rb +2 -2
- data/lib/mihari/structs/rule.rb +94 -85
- data/lib/mihari/templates/rule.yml.erb +5 -23
- data/lib/mihari/types.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/api.rb +0 -2
- data/lib/mihari/web/endpoints/alerts.rb +11 -3
- data/lib/mihari/web/endpoints/configs.rb +1 -6
- data/lib/mihari/web/endpoints/rules.rb +27 -15
- data/lib/mihari/web/public/assets/{fa-brands-400-b1d1c1b0.ttf → fa-brands-400-2ef6fdde.ttf} +0 -0
- data/lib/mihari/web/public/assets/fa-brands-400-f4617423.woff2 +0 -0
- data/lib/mihari/web/public/assets/fa-regular-400-12dea17b.ttf +0 -0
- data/lib/mihari/web/public/assets/fa-regular-400-7ba24c41.woff2 +0 -0
- data/lib/mihari/web/public/assets/fa-solid-900-67a880b4.ttf +0 -0
- data/lib/mihari/web/public/assets/fa-solid-900-e2c5cf54.woff2 +0 -0
- data/lib/mihari/web/public/assets/fa-v4compatibility-7c377405.woff2 +0 -0
- data/lib/mihari/web/public/assets/fa-v4compatibility-8d9500e8.ttf +0 -0
- data/lib/mihari/web/public/assets/{index-07aa1ba2.css → index-625e95fe.css} +3 -3
- data/lib/mihari/web/public/assets/index-63900d73.js +50 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +26 -27
- data/lib/mihari.rb +11 -21
- data/mihari.gemspec +4 -4
- metadata +25 -111
- data/lib/mihari/cli/init.rb +0 -11
- data/lib/mihari/cli/validator.rb +0 -11
- data/lib/mihari/commands/init.rb +0 -51
- data/lib/mihari/commands/search.rb +0 -63
- data/lib/mihari/emitters/http.rb +0 -127
- data/lib/mihari/entities/source.rb +0 -9
- data/lib/mihari/status.rb +0 -55
- data/lib/mihari/web/endpoints/sources.rb +0 -19
- data/lib/mihari/web/public/assets/fa-brands-400-c61287c2.woff2 +0 -0
- data/lib/mihari/web/public/assets/fa-regular-400-5da313b0.woff2 +0 -0
- data/lib/mihari/web/public/assets/fa-regular-400-d7b19fe2.ttf +0 -0
- data/lib/mihari/web/public/assets/fa-solid-900-8f06540f.woff2 +0 -0
- data/lib/mihari/web/public/assets/fa-solid-900-e4f6a7e9.ttf +0 -0
- data/lib/mihari/web/public/assets/fa-v4compatibility-2ddb3b41.ttf +0 -0
- data/lib/mihari/web/public/assets/fa-v4compatibility-f46715c9.woff2 +0 -0
- data/lib/mihari/web/public/assets/index-a7fe697b.js +0 -63
- data/sig/lib/mihari/analyzers/base.rbs +0 -90
- data/sig/lib/mihari/analyzers/binaryedge.rbs +0 -26
- data/sig/lib/mihari/analyzers/censys.rbs +0 -41
- data/sig/lib/mihari/analyzers/circl.rbs +0 -31
- data/sig/lib/mihari/analyzers/crtsh.rbs +0 -17
- data/sig/lib/mihari/analyzers/dnpedia.rbs +0 -15
- data/sig/lib/mihari/analyzers/dnstwister.rbs +0 -25
- data/sig/lib/mihari/analyzers/feed.rbs +0 -20
- data/sig/lib/mihari/analyzers/onyphe.rbs +0 -34
- data/sig/lib/mihari/analyzers/otx.rbs +0 -33
- data/sig/lib/mihari/analyzers/passivetotal.rbs +0 -35
- data/sig/lib/mihari/analyzers/pulsedive.rbs +0 -27
- data/sig/lib/mihari/analyzers/rule.rbs +0 -68
- data/sig/lib/mihari/analyzers/securitytrails.rbs +0 -33
- data/sig/lib/mihari/analyzers/shodan.rbs +0 -36
- data/sig/lib/mihari/analyzers/urlscan.rbs +0 -31
- data/sig/lib/mihari/analyzers/virustotal.rbs +0 -31
- data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +0 -33
- data/sig/lib/mihari/analyzers/zoomeye.rbs +0 -35
- data/sig/lib/mihari/cli/base.rbs +0 -9
- data/sig/lib/mihari/cli/init.rbs +0 -7
- data/sig/lib/mihari/cli/main.rbs +0 -9
- data/sig/lib/mihari/cli/validator.rbs +0 -7
- data/sig/lib/mihari/commands/init.rbs +0 -9
- data/sig/lib/mihari/commands/json.rbs +0 -7
- data/sig/lib/mihari/commands/search.rbs +0 -35
- data/sig/lib/mihari/commands/validator.rbs +0 -9
- data/sig/lib/mihari/commands/web.rbs +0 -7
- data/sig/lib/mihari/constants.rbs +0 -5
- data/sig/lib/mihari/database.rbs +0 -25
- data/sig/lib/mihari/emitters/base.rbs +0 -18
- data/sig/lib/mihari/emitters/database.rbs +0 -9
- data/sig/lib/mihari/emitters/http.rbs +0 -35
- data/sig/lib/mihari/emitters/misp.rbs +0 -34
- data/sig/lib/mihari/emitters/slack.rbs +0 -73
- data/sig/lib/mihari/emitters/stdout.rbs +0 -9
- data/sig/lib/mihari/emitters/the_hive.rbs +0 -32
- data/sig/lib/mihari/emitters/webhook.rbs +0 -20
- data/sig/lib/mihari/enrichers/base.rbs +0 -12
- data/sig/lib/mihari/enrichers/google_public_dns.rbs +0 -18
- data/sig/lib/mihari/enrichers/ipinfo.rbs +0 -16
- data/sig/lib/mihari/errors.rbs +0 -10
- data/sig/lib/mihari/feed/parser.rbs +0 -11
- data/sig/lib/mihari/feed/reader.rbs +0 -56
- data/sig/lib/mihari/http.rbs +0 -64
- data/sig/lib/mihari/mixins/autonomous_system.rbs +0 -14
- data/sig/lib/mihari/mixins/configurable.rbs +0 -30
- data/sig/lib/mihari/mixins/configuration.rbs +0 -45
- data/sig/lib/mihari/mixins/disallowed_data_value.rbs +0 -23
- data/sig/lib/mihari/mixins/error_notification.rbs +0 -12
- data/sig/lib/mihari/mixins/hash.rbs +0 -14
- data/sig/lib/mihari/mixins/refang.rbs +0 -14
- data/sig/lib/mihari/mixins/retriable.rbs +0 -15
- data/sig/lib/mihari/models/alert.rbs +0 -18
- data/sig/lib/mihari/models/artifact.rbs +0 -69
- data/sig/lib/mihari/models/autonomous_system.rbs +0 -14
- data/sig/lib/mihari/models/cpe.rbs +0 -7
- data/sig/lib/mihari/models/dns.rbs +0 -19
- data/sig/lib/mihari/models/geolocation.rbs +0 -15
- data/sig/lib/mihari/models/port.rbs +0 -7
- data/sig/lib/mihari/models/reverse_dns.rbs +0 -14
- data/sig/lib/mihari/models/rule.rbs +0 -17
- data/sig/lib/mihari/models/tag.rbs +0 -5
- data/sig/lib/mihari/models/tagging.rbs +0 -4
- data/sig/lib/mihari/models/whois.rbs +0 -66
- data/sig/lib/mihari/status.rbs +0 -25
- data/sig/lib/mihari/structs/censys.rbs +0 -58
- data/sig/lib/mihari/structs/filters.rbs +0 -40
- data/sig/lib/mihari/structs/google_public_dns.rbs +0 -21
- data/sig/lib/mihari/structs/greynoise.rbs +0 -30
- data/sig/lib/mihari/structs/ipinfo.rbs +0 -17
- data/sig/lib/mihari/structs/onyphe.rbs +0 -25
- data/sig/lib/mihari/structs/rule.rbs +0 -57
- data/sig/lib/mihari/structs/shodan.rbs +0 -30
- data/sig/lib/mihari/structs/urlscan.rbs +0 -28
- data/sig/lib/mihari/structs/virustotal_intelligence.rbs +0 -33
- data/sig/lib/mihari/type_checker.rbs +0 -48
- data/sig/lib/mihari/types.rbs +0 -23
- data/sig/lib/mihari/version.rbs +0 -3
- data/sig/lib/mihari/web/app.rbs +0 -5
- data/sig/lib/mihari.rbs +0 -54
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7944fcbb2ef6b1ff7fccbe5c8158bd21a186b05e8fae70a6700256dce10adbb
|
|
4
|
+
data.tar.gz: 36605153506952b323be6e3a7646fd4446f280943fc6c587d7885e8eec413c33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba53c1fb987ffd933017ccc64a3a72adc032de81a377e34684c4927304d295d85531ad11e796abd3c17489411fcf15eedd494d7ff7a8b7d0c53fdeb511eb5a8d
|
|
7
|
+
data.tar.gz: 6dff687f32dfef7f0cc19b76cba77a6233ebbefa9b90f522f37092468370eb5abb0e7f185d86f74077944cb0c76609b01390e7878ca494a82419ac44e59d93e5
|
data/Steepfile
CHANGED
|
@@ -5,18 +5,19 @@ module Mihari
|
|
|
5
5
|
class Base
|
|
6
6
|
extend Dry::Initializer
|
|
7
7
|
|
|
8
|
+
option :rule, default: proc {}
|
|
9
|
+
|
|
8
10
|
include Mixins::AutonomousSystem
|
|
9
11
|
include Mixins::Configurable
|
|
10
|
-
include Mixins::Database
|
|
11
12
|
include Mixins::Retriable
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
# @return [Mihari::Structs::Rule, nil]
|
|
15
|
+
attr_reader :rule
|
|
14
16
|
|
|
15
17
|
def initialize(*args, **kwargs)
|
|
16
|
-
super
|
|
18
|
+
super(*args, **kwargs)
|
|
17
19
|
|
|
18
|
-
@
|
|
19
|
-
@ignore_threshold = 0
|
|
20
|
+
@base_time = Time.now.utc
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
# @return [Array<String>, Array<Mihari::Artifact>]
|
|
@@ -24,26 +25,11 @@ module Mihari
|
|
|
24
25
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
|
25
26
|
end
|
|
26
27
|
|
|
27
|
-
# @return [String]
|
|
28
|
-
def title
|
|
29
|
-
self.class.to_s.split("::").last.to_s
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# @return [String]
|
|
33
|
-
def description
|
|
34
|
-
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
28
|
# @return [String]
|
|
38
29
|
def source
|
|
39
30
|
self.class.to_s.split("::").last.to_s
|
|
40
31
|
end
|
|
41
32
|
|
|
42
|
-
# @return [Array<String>]
|
|
43
|
-
def tags
|
|
44
|
-
[]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
33
|
#
|
|
48
34
|
# Set artifacts & run emitters in parallel
|
|
49
35
|
#
|
|
@@ -55,16 +41,14 @@ module Mihari
|
|
|
55
41
|
raise ConfigurationError, "#{class_name} is not configured correctly"
|
|
56
42
|
end
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
set_enriched_artifacts
|
|
60
|
-
|
|
61
|
-
responses = Parallel.map(valid_emitters) do |emitter|
|
|
62
|
-
run_emitter emitter
|
|
63
|
-
end
|
|
44
|
+
set_enriched_artifacts
|
|
64
45
|
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
responses = Parallel.map(valid_emitters) do |emitter|
|
|
47
|
+
run_emitter emitter
|
|
67
48
|
end
|
|
49
|
+
|
|
50
|
+
# returns Mihari::Alert created by the database emitter
|
|
51
|
+
responses.find { |res| res.is_a?(Mihari::Alert) }
|
|
68
52
|
end
|
|
69
53
|
|
|
70
54
|
#
|
|
@@ -77,13 +61,7 @@ module Mihari
|
|
|
77
61
|
def run_emitter(emitter)
|
|
78
62
|
return if enriched_artifacts.empty?
|
|
79
63
|
|
|
80
|
-
alert_or_something = emitter.run(
|
|
81
|
-
title: title,
|
|
82
|
-
description: description,
|
|
83
|
-
artifacts: enriched_artifacts,
|
|
84
|
-
source: source,
|
|
85
|
-
tags: tags
|
|
86
|
-
)
|
|
64
|
+
alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
|
|
87
65
|
|
|
88
66
|
Mihari.logger.info "Emission by #{emitter.class} is succedded"
|
|
89
67
|
|
|
@@ -112,7 +90,10 @@ module Mihari
|
|
|
112
90
|
# No need to set data_type manually
|
|
113
91
|
# It is set automatically in #initialize
|
|
114
92
|
artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
|
|
115
|
-
end.select(&:valid?).uniq(&:data)
|
|
93
|
+
end.select(&:valid?).uniq(&:data).map do |artifact|
|
|
94
|
+
artifact.rule_id = rule&.id
|
|
95
|
+
artifact
|
|
96
|
+
end
|
|
116
97
|
end
|
|
117
98
|
|
|
118
99
|
private
|
|
@@ -124,7 +105,7 @@ module Mihari
|
|
|
124
105
|
#
|
|
125
106
|
def unique_artifacts
|
|
126
107
|
@unique_artifacts ||= normalized_artifacts.select do |artifact|
|
|
127
|
-
artifact.unique?(
|
|
108
|
+
artifact.unique?(base_time: @base_time, artifact_lifetime: rule&.artifact_lifetime)
|
|
128
109
|
end
|
|
129
110
|
end
|
|
130
111
|
|
|
@@ -29,38 +29,21 @@ module Mihari
|
|
|
29
29
|
|
|
30
30
|
EMITTER_TO_CLASS = {
|
|
31
31
|
"database" => Emitters::Database,
|
|
32
|
-
"http" => Emitters::HTTP,
|
|
33
32
|
"misp" => Emitters::MISP,
|
|
34
33
|
"slack" => Emitters::Slack,
|
|
35
34
|
"the_hive" => Emitters::TheHive,
|
|
36
35
|
"webhook" => Emitters::Webhook
|
|
37
36
|
}.freeze
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
option :title
|
|
43
|
-
option :description
|
|
44
|
-
option :queries
|
|
45
|
-
|
|
46
|
-
option :id, default: proc { "" }
|
|
47
|
-
option :tags, default: proc { [] }
|
|
48
|
-
option :allowed_data_types, default: proc { ALLOWED_DATA_TYPES }
|
|
49
|
-
option :disallowed_data_values, default: proc { [] }
|
|
38
|
+
# @return [Mihari::Structs::Rule]
|
|
39
|
+
attr_reader :rule
|
|
50
40
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
attr_reader :source
|
|
41
|
+
class Rule < Base
|
|
42
|
+
include Mixins::FalsePositive
|
|
55
43
|
|
|
56
44
|
def initialize(**kwargs)
|
|
57
45
|
super(**kwargs)
|
|
58
46
|
|
|
59
|
-
@source = id
|
|
60
|
-
|
|
61
|
-
@emitters = emitters || DEFAULT_EMITTERS
|
|
62
|
-
@enrichers = enrichers || DEFAULT_ENRICHERS
|
|
63
|
-
|
|
64
47
|
validate_analyzer_configurations
|
|
65
48
|
end
|
|
66
49
|
|
|
@@ -72,7 +55,7 @@ module Mihari
|
|
|
72
55
|
def artifacts
|
|
73
56
|
artifacts = []
|
|
74
57
|
|
|
75
|
-
queries.each do |original_params|
|
|
58
|
+
rule.queries.each do |original_params|
|
|
76
59
|
parmas = original_params.deep_dup
|
|
77
60
|
|
|
78
61
|
analyzer_name = parmas[:analyzer]
|
|
@@ -83,8 +66,12 @@ module Mihari
|
|
|
83
66
|
# set interval in the top level
|
|
84
67
|
options = parmas[:options] || {}
|
|
85
68
|
interval = options[:interval]
|
|
69
|
+
|
|
86
70
|
parmas[:interval] = interval if interval
|
|
87
71
|
|
|
72
|
+
# set rule
|
|
73
|
+
parmas[:rule] = rule
|
|
74
|
+
|
|
88
75
|
analyzer = klass.new(query, **parmas)
|
|
89
76
|
|
|
90
77
|
# Use #normalized_artifacts method to get atrifacts as Array<Mihari::Artifact>
|
|
@@ -106,9 +93,9 @@ module Mihari
|
|
|
106
93
|
#
|
|
107
94
|
def normalized_artifacts
|
|
108
95
|
@normalized_artifacts ||= artifacts.uniq(&:data).select(&:valid?).select do |artifact|
|
|
109
|
-
|
|
96
|
+
rule.data_types.include? artifact.data_type
|
|
110
97
|
end.reject do |artifact|
|
|
111
|
-
|
|
98
|
+
falsepositive? artifact.data
|
|
112
99
|
end
|
|
113
100
|
end
|
|
114
101
|
|
|
@@ -119,7 +106,7 @@ module Mihari
|
|
|
119
106
|
#
|
|
120
107
|
def enriched_artifacts
|
|
121
108
|
@enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
|
|
122
|
-
enrichers.each do |enricher|
|
|
109
|
+
rule.enrichers.each do |enricher|
|
|
123
110
|
artifact.enrich_by_enricher(enricher[:enricher])
|
|
124
111
|
end
|
|
125
112
|
|
|
@@ -132,22 +119,22 @@ module Mihari
|
|
|
132
119
|
#
|
|
133
120
|
# @return [Array<Regexp, String>]
|
|
134
121
|
#
|
|
135
|
-
def
|
|
136
|
-
@
|
|
122
|
+
def normalized_falsepositives
|
|
123
|
+
@normalized_falsepositives ||= rule.falsepositives.map { |v| normalize_falsepositive v }
|
|
137
124
|
end
|
|
138
125
|
|
|
139
126
|
#
|
|
140
|
-
# Check whether a value is a
|
|
127
|
+
# Check whether a value is a falsepositive value or not
|
|
141
128
|
#
|
|
142
129
|
# @return [Boolean]
|
|
143
130
|
#
|
|
144
|
-
def
|
|
145
|
-
return true if
|
|
131
|
+
def falsepositive?(value)
|
|
132
|
+
return true if normalized_falsepositives.include?(value)
|
|
146
133
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
end.any? do |
|
|
150
|
-
|
|
134
|
+
normalized_falsepositives.select do |falsepositive|
|
|
135
|
+
falsepositive.is_a?(Regexp)
|
|
136
|
+
end.any? do |falseposistive|
|
|
137
|
+
falseposistive.match?(value)
|
|
151
138
|
end
|
|
152
139
|
end
|
|
153
140
|
|
|
@@ -168,7 +155,7 @@ module Mihari
|
|
|
168
155
|
end
|
|
169
156
|
|
|
170
157
|
def valid_emitters
|
|
171
|
-
@valid_emitters ||= emitters.filter_map do |original_params|
|
|
158
|
+
@valid_emitters ||= rule.emitters.filter_map do |original_params|
|
|
172
159
|
params = original_params.deep_dup
|
|
173
160
|
|
|
174
161
|
name = params[:emitter]
|
|
@@ -199,7 +186,7 @@ module Mihari
|
|
|
199
186
|
# Validate configuration of analyzers
|
|
200
187
|
#
|
|
201
188
|
def validate_analyzer_configurations
|
|
202
|
-
queries.each do |params|
|
|
189
|
+
rule.queries.each do |params|
|
|
203
190
|
analyzer_name = params[:analyzer]
|
|
204
191
|
klass = get_analyzer_class(analyzer_name)
|
|
205
192
|
|
data/lib/mihari/cli/main.rb
CHANGED
|
@@ -3,28 +3,23 @@
|
|
|
3
3
|
require "thor"
|
|
4
4
|
|
|
5
5
|
# Commands
|
|
6
|
-
require "mihari/commands/
|
|
6
|
+
require "mihari/commands/initializer"
|
|
7
|
+
require "mihari/commands/searcher"
|
|
8
|
+
require "mihari/commands/validator"
|
|
7
9
|
require "mihari/commands/version"
|
|
8
10
|
require "mihari/commands/web"
|
|
9
11
|
|
|
10
12
|
# CLIs
|
|
11
13
|
require "mihari/cli/base"
|
|
12
14
|
|
|
13
|
-
require "mihari/cli/init"
|
|
14
|
-
require "mihari/cli/validator"
|
|
15
|
-
|
|
16
15
|
module Mihari
|
|
17
16
|
module CLI
|
|
18
17
|
class Main < Base
|
|
19
|
-
include Mihari::Commands::
|
|
18
|
+
include Mihari::Commands::Searcher
|
|
20
19
|
include Mihari::Commands::Version
|
|
21
20
|
include Mihari::Commands::Web
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
subcommand "init", Initialization
|
|
25
|
-
|
|
26
|
-
desc "validate", "Sub commands to validate format of a rule"
|
|
27
|
-
subcommand "validate", Validator
|
|
21
|
+
include Mihari::Commands::Validator
|
|
22
|
+
include Mihari::Commands::Initializer
|
|
28
23
|
end
|
|
29
24
|
end
|
|
30
25
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Mihari
|
|
6
|
+
module Commands
|
|
7
|
+
module Initializer
|
|
8
|
+
def self.included(thor)
|
|
9
|
+
thor.class_eval do
|
|
10
|
+
desc "init", "Initialize a new rule"
|
|
11
|
+
method_option :path, type: :string, default: "./rule.yml"
|
|
12
|
+
def init
|
|
13
|
+
path = options["path"]
|
|
14
|
+
|
|
15
|
+
warning = "#{path} exists. Do you want to overwrite it? (y/n)"
|
|
16
|
+
return if Pathname(path).exist? && !(yes? warning)
|
|
17
|
+
|
|
18
|
+
initialize_rule path
|
|
19
|
+
|
|
20
|
+
Mihari.logger.info "A new rule is initialized as #{path}."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
no_commands do
|
|
24
|
+
#
|
|
25
|
+
# @return [Mihari::Structs::Rule]
|
|
26
|
+
#
|
|
27
|
+
def rule_template
|
|
28
|
+
Structs::Rule.from_path File.expand_path("../templates/rule.yml.erb", __dir__)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Create a new rule
|
|
33
|
+
#
|
|
34
|
+
# @param [String] path
|
|
35
|
+
# @param [Dry::Files] files
|
|
36
|
+
#
|
|
37
|
+
# @return [nil]
|
|
38
|
+
#
|
|
39
|
+
def initialize_rule(path, files = Dry::Files.new)
|
|
40
|
+
files.write(path, rule_template.yaml)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Commands
|
|
5
|
+
module Searcher
|
|
6
|
+
include Mixins::Database
|
|
7
|
+
include Mixins::ErrorNotification
|
|
8
|
+
|
|
9
|
+
def self.included(thor)
|
|
10
|
+
thor.class_eval do
|
|
11
|
+
desc "search [PATH]", "Search by a rule"
|
|
12
|
+
method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
|
|
13
|
+
def search(path_or_id)
|
|
14
|
+
with_db_connection do
|
|
15
|
+
rule = Structs::Rule.from_path_or_id path_or_id
|
|
16
|
+
|
|
17
|
+
# validate
|
|
18
|
+
begin
|
|
19
|
+
rule.validate!
|
|
20
|
+
rescue RuleValidationError
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
force_overwrite = options["force_overwrite"] || false
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
rule_model = Mihari::Rule.find(rule.id)
|
|
28
|
+
has_change = rule_model.data != rule.data.deep_stringify_keys
|
|
29
|
+
has_change_and_not_force_overwrite = has_change & !force_overwrite
|
|
30
|
+
|
|
31
|
+
if has_change_and_not_force_overwrite && !yes?("This operation will overwrite the rule in the database (Rule ID: #{rule.id}). Are you sure you want to update the rule? (y/n)")
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# update the rule
|
|
36
|
+
rule.model.save
|
|
37
|
+
rescue ActiveRecord::RecordNotFound
|
|
38
|
+
# create a new rule
|
|
39
|
+
rule.model.save
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
with_error_notification do
|
|
43
|
+
alert = rule.analyzer.run
|
|
44
|
+
if alert
|
|
45
|
+
data = Mihari::Entities::Alert.represent(alert)
|
|
46
|
+
puts JSON.pretty_generate(data.as_json)
|
|
47
|
+
else
|
|
48
|
+
Mihari.logger.info "There is no new alert created in the database"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -5,7 +5,7 @@ module Mihari
|
|
|
5
5
|
module Validator
|
|
6
6
|
def self.included(thor)
|
|
7
7
|
thor.class_eval do
|
|
8
|
-
desc "
|
|
8
|
+
desc "validate [PATH]", "Validate a rule file"
|
|
9
9
|
#
|
|
10
10
|
# Validate format of a rule
|
|
11
11
|
#
|
|
@@ -13,7 +13,7 @@ module Mihari
|
|
|
13
13
|
#
|
|
14
14
|
# @return [nil]
|
|
15
15
|
#
|
|
16
|
-
def
|
|
16
|
+
def validate(path)
|
|
17
17
|
rule = Structs::Rule.from_path_or_id(path)
|
|
18
18
|
|
|
19
19
|
begin
|
data/lib/mihari/constants.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Mihari
|
|
4
|
-
|
|
4
|
+
DEFAULT_DATA_TYPES = %w[hash ip domain url mail].freeze
|
|
5
5
|
|
|
6
|
-
DEFAULT_EMITTERS = [
|
|
6
|
+
DEFAULT_EMITTERS = %w[database misp slack the_hive].map { |name| { emitter: name } }.freeze
|
|
7
7
|
|
|
8
|
-
DEFAULT_ENRICHERS = [
|
|
8
|
+
DEFAULT_ENRICHERS = %w[whois ipinfo shodan google_public_dns].map { |name| { enricher: name } }.freeze
|
|
9
9
|
end
|
data/lib/mihari/database.rb
CHANGED
|
@@ -17,53 +17,44 @@ def development_env?
|
|
|
17
17
|
env == "development"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
class
|
|
20
|
+
class V5Schema < ActiveRecord::Migration[7.0]
|
|
21
21
|
def change
|
|
22
|
-
create_table :
|
|
23
|
-
t.string :
|
|
22
|
+
create_table :rules, id: :string, if_not_exists: true do |t|
|
|
23
|
+
t.string :title, null: false
|
|
24
|
+
t.string :description, null: false
|
|
25
|
+
t.json :data, null: false
|
|
26
|
+
t.timestamps
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
create_table :alerts, if_not_exists: true do |t|
|
|
27
|
-
t.string :title, null: false
|
|
28
|
-
t.string :description, null: true
|
|
29
|
-
t.string :source, null: false
|
|
30
30
|
t.timestamps
|
|
31
|
+
|
|
32
|
+
t.belongs_to :rule, foreign_key: true, type: :string, null: false
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
create_table :artifacts, if_not_exists: true do |t|
|
|
34
36
|
t.string :data, null: false
|
|
35
37
|
t.string :data_type, null: false
|
|
36
|
-
t.
|
|
38
|
+
t.string :source
|
|
39
|
+
t.json :metadata
|
|
37
40
|
t.timestamps
|
|
38
|
-
end
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
t.integer :tag_id
|
|
42
|
-
t.integer :alert_id
|
|
42
|
+
t.belongs_to :alert, foreign_key: true, null: false
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
add_index :taggings, :tag_id, if_not_exists: true
|
|
46
|
-
add_index :taggings, [:tag_id, :alert_id], unique: true, if_not_exists: true
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
class AddeSourceToArtifactSchema < ActiveRecord::Migration[7.0]
|
|
51
|
-
def change
|
|
52
|
-
add_column :artifacts, :source, :string, if_not_exists: true
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
class EnrichmentsSchema < ActiveRecord::Migration[7.0]
|
|
57
|
-
def change
|
|
58
45
|
create_table :autonomous_systems, if_not_exists: true do |t|
|
|
59
46
|
t.integer :asn, null: false
|
|
60
|
-
t.
|
|
47
|
+
t.datetime :created_at
|
|
48
|
+
|
|
49
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
|
61
50
|
end
|
|
62
51
|
|
|
63
52
|
create_table :geolocations, if_not_exists: true do |t|
|
|
64
53
|
t.string :country, null: false
|
|
65
54
|
t.string :country_code, null: false
|
|
66
|
-
t.
|
|
55
|
+
t.datetime :created_at
|
|
56
|
+
|
|
57
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
|
67
58
|
end
|
|
68
59
|
|
|
69
60
|
create_table :whois_records, if_not_exists: true do |t|
|
|
@@ -73,73 +64,59 @@ class EnrichmentsSchema < ActiveRecord::Migration[7.0]
|
|
|
73
64
|
t.date :expires_on
|
|
74
65
|
t.json :registrar
|
|
75
66
|
t.json :contacts
|
|
76
|
-
t.
|
|
67
|
+
t.datetime :created_at
|
|
68
|
+
|
|
69
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
|
77
70
|
end
|
|
78
71
|
|
|
79
72
|
create_table :dns_records, if_not_exists: true do |t|
|
|
80
73
|
t.string :resource, null: false
|
|
81
74
|
t.string :value, null: false
|
|
82
|
-
t.
|
|
75
|
+
t.datetime :created_at
|
|
76
|
+
|
|
77
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
|
83
78
|
end
|
|
84
79
|
|
|
85
80
|
create_table :reverse_dns_names, if_not_exists: true do |t|
|
|
86
81
|
t.string :name, null: false
|
|
87
|
-
t.
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
82
|
+
t.datetime :created_at
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
def change
|
|
94
|
-
# Add created_at column because now it is able to enrich an atrifact after the creation
|
|
95
|
-
add_column :autonomous_systems, :created_at, :datetime, if_not_exists: true
|
|
96
|
-
add_column :geolocations, :created_at, :datetime, if_not_exists: true
|
|
97
|
-
add_column :whois_records, :created_at, :datetime, if_not_exists: true
|
|
98
|
-
add_column :dns_records, :created_at, :datetime, if_not_exists: true
|
|
99
|
-
add_column :reverse_dns_names, :created_at, :datetime, if_not_exists: true
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
class RuleSchema < ActiveRecord::Migration[7.0]
|
|
104
|
-
def change
|
|
105
|
-
create_table :rules, id: :string, if_not_exists: true do |t|
|
|
106
|
-
t.string :title, null: false
|
|
107
|
-
t.string :description, null: false
|
|
108
|
-
t.json :data, null: false
|
|
109
|
-
t.timestamps
|
|
84
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
|
110
85
|
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
class AddeMetadataToArtifactSchema < ActiveRecord::Migration[7.0]
|
|
115
|
-
def change
|
|
116
|
-
add_column :artifacts, :metadata, :json, if_not_exists: true
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
86
|
|
|
120
|
-
class AddYAMLToRulesSchema < ActiveRecord::Migration[7.0]
|
|
121
|
-
def change
|
|
122
|
-
add_column :rules, :yaml, :text, if_not_exists: true
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
class EnrichmentsV45Schema < ActiveRecord::Migration[7.0]
|
|
127
|
-
def change
|
|
128
87
|
create_table :cpes, if_not_exists: true do |t|
|
|
129
88
|
t.string :cpe, null: false
|
|
130
|
-
t.
|
|
89
|
+
t.datetime :created_at
|
|
90
|
+
|
|
91
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
|
131
92
|
end
|
|
132
93
|
|
|
133
94
|
create_table :ports, if_not_exists: true do |t|
|
|
134
95
|
t.integer :port, null: false
|
|
135
|
-
t.
|
|
96
|
+
t.datetime :created_at
|
|
97
|
+
|
|
98
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
create_table :tags, if_not_exists: true do |t|
|
|
102
|
+
t.string :name, null: false
|
|
103
|
+
t.datetime :created_at
|
|
136
104
|
end
|
|
105
|
+
|
|
106
|
+
create_table :taggings, if_not_exists: true do |t|
|
|
107
|
+
t.integer :tag_id
|
|
108
|
+
t.integer :alert_id
|
|
109
|
+
t.datetime :created_at
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
add_index :taggings, :tag_id, if_not_exists: true
|
|
113
|
+
add_index :taggings, %i[tag_id alert_id], unique: true, if_not_exists: true
|
|
137
114
|
end
|
|
138
115
|
end
|
|
139
116
|
|
|
140
117
|
def adapter
|
|
141
|
-
return "postgresql" if Mihari.config.
|
|
142
|
-
return "mysql2" if Mihari.config.
|
|
118
|
+
return "postgresql" if %w[postgresql postgres].include?(Mihari.config.database_url.scheme)
|
|
119
|
+
return "mysql2" if Mihari.config.database_url.scheme == "mysql2"
|
|
143
120
|
|
|
144
121
|
"sqlite3"
|
|
145
122
|
end
|
|
@@ -157,19 +134,7 @@ module Mihari
|
|
|
157
134
|
def migrate(direction)
|
|
158
135
|
ActiveRecord::Migration.verbose = false
|
|
159
136
|
|
|
160
|
-
[
|
|
161
|
-
InitialSchema,
|
|
162
|
-
AddeSourceToArtifactSchema,
|
|
163
|
-
EnrichmentsSchema,
|
|
164
|
-
EnrichmentCreatedAtSchema,
|
|
165
|
-
# v4.0
|
|
166
|
-
RuleSchema,
|
|
167
|
-
AddeMetadataToArtifactSchema,
|
|
168
|
-
# v4.4
|
|
169
|
-
AddYAMLToRulesSchema,
|
|
170
|
-
# v4.5
|
|
171
|
-
EnrichmentsV45Schema
|
|
172
|
-
].each { |schema| schema.migrate direction }
|
|
137
|
+
[V5Schema].each { |schema| schema.migrate direction }
|
|
173
138
|
end
|
|
174
139
|
memoize :migrate unless test_env?
|
|
175
140
|
|
|
@@ -181,19 +146,19 @@ module Mihari
|
|
|
181
146
|
|
|
182
147
|
case adapter
|
|
183
148
|
when "postgresql", "mysql2"
|
|
184
|
-
ActiveRecord::Base.establish_connection(Mihari.config.
|
|
149
|
+
ActiveRecord::Base.establish_connection(Mihari.config.database_url.to_s)
|
|
185
150
|
else
|
|
186
151
|
ActiveRecord::Base.establish_connection(
|
|
187
152
|
adapter: adapter,
|
|
188
|
-
database: Mihari.config.
|
|
153
|
+
database: Mihari.config.database_url.path[1..]
|
|
189
154
|
)
|
|
190
155
|
end
|
|
191
156
|
|
|
192
157
|
ActiveRecord::Base.logger = Logger.new($stdout) if development_env?
|
|
193
158
|
|
|
194
159
|
migrate :up
|
|
195
|
-
rescue StandardError
|
|
196
|
-
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
Mihari.logger.error e
|
|
197
162
|
end
|
|
198
163
|
|
|
199
164
|
#
|