mihari 5.2.2 → 5.2.4
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/build_frontend.sh +1 -9
- data/frontend/.browserslistrc +3 -0
- data/frontend/.eslintrc.js +33 -0
- data/frontend/.gitignore +25 -0
- data/frontend/README.md +3 -0
- data/frontend/babel.config.js +3 -0
- data/frontend/index.html +21 -0
- data/frontend/jest.config.js +9 -0
- data/frontend/package-lock.json +13216 -0
- data/frontend/package.json +54 -0
- data/frontend/public/favicon.ico +0 -0
- data/frontend/scripts/swagger_doc_to_yaml.rb +23 -0
- data/frontend/src/App.vue +27 -0
- data/frontend/src/api-helper.ts +113 -0
- data/frontend/src/api.ts +105 -0
- data/frontend/src/components/ErrorMessage.vue +32 -0
- data/frontend/src/components/Loading.vue +15 -0
- data/frontend/src/components/Navbar.vue +59 -0
- data/frontend/src/components/Pagination.vue +126 -0
- data/frontend/src/components/alert/Alert.vue +92 -0
- data/frontend/src/components/alert/Alerts.vue +66 -0
- data/frontend/src/components/alert/AlertsWithPagination.vue +91 -0
- data/frontend/src/components/alert/AlertsWrapper.vue +141 -0
- data/frontend/src/components/alert/Form.vue +185 -0
- data/frontend/src/components/artifact/AS.vue +29 -0
- data/frontend/src/components/artifact/Artifact.vue +321 -0
- data/frontend/src/components/artifact/ArtifactTag.vue +70 -0
- data/frontend/src/components/artifact/ArtifactTags.vue +29 -0
- data/frontend/src/components/artifact/ArtifactWrapper.vue +62 -0
- data/frontend/src/components/artifact/CPEs.vue +23 -0
- data/frontend/src/components/artifact/DnsRecords.vue +38 -0
- data/frontend/src/components/artifact/Ports.vue +23 -0
- data/frontend/src/components/artifact/ReverseDnsNames.vue +31 -0
- data/frontend/src/components/artifact/Tags.vue +29 -0
- data/frontend/src/components/artifact/WhoisRecord.vue +49 -0
- data/frontend/src/components/config/Configs.vue +68 -0
- data/frontend/src/components/config/ConfigsWrapper.vue +40 -0
- data/frontend/src/components/link/Link.vue +32 -0
- data/frontend/src/components/link/Links.vue +47 -0
- data/frontend/src/components/rule/EditRule.vue +74 -0
- data/frontend/src/components/rule/EditRuleWrapper.vue +56 -0
- data/frontend/src/components/rule/Form.vue +160 -0
- data/frontend/src/components/rule/InputForm.vue +80 -0
- data/frontend/src/components/rule/NewRule.vue +60 -0
- data/frontend/src/components/rule/Rule.vue +108 -0
- data/frontend/src/components/rule/RuleWrapper.vue +62 -0
- data/frontend/src/components/rule/Rules.vue +88 -0
- data/frontend/src/components/rule/RulesWrapper.vue +130 -0
- data/frontend/src/components/rule/YAML.vue +47 -0
- data/frontend/src/components/tag/Tag.vue +73 -0
- data/frontend/src/components/tag/Tags.vue +37 -0
- data/frontend/src/countries.ts +350 -0
- data/frontend/src/index.ts +23 -0
- data/frontend/src/links/anyrun.ts +19 -0
- data/frontend/src/links/base.ts +14 -0
- data/frontend/src/links/censys.ts +20 -0
- data/frontend/src/links/crtsh.ts +20 -0
- data/frontend/src/links/dnslytics.ts +38 -0
- data/frontend/src/links/greynoise.ts +20 -0
- data/frontend/src/links/index.ts +40 -0
- data/frontend/src/links/intezer.ts +20 -0
- data/frontend/src/links/otx.ts +33 -0
- data/frontend/src/links/securitytrails.ts +38 -0
- data/frontend/src/links/shodan.ts +20 -0
- data/frontend/src/links/urlscan.ts +50 -0
- data/frontend/src/links/virustotal.ts +72 -0
- data/frontend/src/main.ts +11 -0
- data/frontend/src/router/index.ts +57 -0
- data/frontend/src/rule.ts +14 -0
- data/frontend/src/shims-vue.d.ts +6 -0
- data/frontend/src/swagger.yaml +737 -0
- data/frontend/src/types.ts +188 -0
- data/frontend/src/utils.ts +60 -0
- data/frontend/src/views/Alerts.vue +20 -0
- data/frontend/src/views/Artifact.vue +44 -0
- data/frontend/src/views/Configs.vue +20 -0
- data/frontend/src/views/EditRule.vue +44 -0
- data/frontend/src/views/NewRule.vue +26 -0
- data/frontend/src/views/Rule.vue +44 -0
- data/frontend/src/views/Rules.vue +20 -0
- data/frontend/tests/unit/utils.spec.ts +7 -0
- data/frontend/tsconfig.json +40 -0
- data/frontend/vite.config.js +24 -0
- data/lefthook.yml +10 -0
- data/lib/mihari/analyzers/base.rb +22 -5
- data/lib/mihari/analyzers/binaryedge.rb +0 -1
- data/lib/mihari/analyzers/censys.rb +7 -2
- data/lib/mihari/analyzers/circl.rb +1 -1
- data/lib/mihari/analyzers/passivetotal.rb +1 -1
- data/lib/mihari/analyzers/rule.rb +43 -73
- data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -2
- data/lib/mihari/clients/base.rb +1 -1
- data/lib/mihari/commands/database.rb +12 -11
- data/lib/mihari/commands/rule.rb +47 -45
- data/lib/mihari/commands/search.rb +73 -45
- data/lib/mihari/commands/version.rb +8 -6
- data/lib/mihari/commands/web.rb +26 -23
- data/lib/mihari/emitters/base.rb +14 -1
- data/lib/mihari/emitters/database.rb +3 -10
- data/lib/mihari/emitters/misp.rb +16 -5
- data/lib/mihari/emitters/slack.rb +13 -15
- data/lib/mihari/emitters/the_hive.rb +17 -19
- data/lib/mihari/emitters/webhook.rb +23 -23
- data/lib/mihari/enrichers/whois.rb +1 -0
- data/lib/mihari/entities/rule.rb +1 -1
- data/lib/mihari/entities/tag.rb +1 -1
- data/lib/mihari/feed/parser.rb +1 -0
- data/lib/mihari/feed/reader.rb +29 -14
- data/lib/mihari/mixins/configurable.rb +13 -4
- data/lib/mihari/schemas/analyzer.rb +2 -7
- data/lib/mihari/schemas/rule.rb +1 -1
- data/lib/mihari/structs/censys.rb +96 -82
- data/lib/mihari/structs/config.rb +46 -21
- data/lib/mihari/structs/google_public_dns.rb +27 -23
- data/lib/mihari/structs/greynoise.rb +44 -38
- data/lib/mihari/structs/onyphe.rb +34 -30
- data/lib/mihari/structs/rule.rb +1 -1
- data/lib/mihari/structs/shodan.rb +77 -69
- data/lib/mihari/structs/urlscan.rb +42 -36
- data/lib/mihari/structs/virustotal_intelligence.rb +57 -49
- data/lib/mihari/type_checker.rb +10 -8
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/index-ac4e5ffa.js +50 -0
- data/lib/mihari/web/public/index.html +1 -1
- data/mihari.gemspec +8 -8
- metadata +103 -22
- data/.gitmodules +0 -0
- data/.overcommit.yml +0 -12
- data/lib/mihari/web/public/assets/index-cbe1734c.js +0 -50
|
@@ -46,7 +46,7 @@ module Mihari
|
|
|
46
46
|
#
|
|
47
47
|
# @param [Mihari::Structs::Rule] rule
|
|
48
48
|
#
|
|
49
|
-
def initialize(rule
|
|
49
|
+
def initialize(rule)
|
|
50
50
|
@rule = rule
|
|
51
51
|
@base_time = Time.now.utc
|
|
52
52
|
|
|
@@ -54,12 +54,12 @@ module Mihari
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
#
|
|
57
|
-
# Returns a list of artifacts matched with queries
|
|
57
|
+
# Returns a list of artifacts matched with queries/analyzers
|
|
58
58
|
#
|
|
59
59
|
# @return [Array<Mihari::Artifact>]
|
|
60
60
|
#
|
|
61
61
|
def artifacts
|
|
62
|
-
|
|
62
|
+
analyzers.flat_map(&:normalized_artifacts)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
#
|
|
@@ -111,26 +111,15 @@ module Mihari
|
|
|
111
111
|
# @return [Array<Mihari::Alert>]
|
|
112
112
|
#
|
|
113
113
|
def bulk_emit
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
#
|
|
124
|
-
def emit(emitter)
|
|
125
|
-
return if enriched_artifacts.empty?
|
|
126
|
-
|
|
127
|
-
alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
|
|
128
|
-
|
|
129
|
-
Mihari.logger.info "Emission by #{emitter.class} is succeeded"
|
|
130
|
-
|
|
131
|
-
alert_or_something
|
|
132
|
-
rescue StandardError => e
|
|
133
|
-
Mihari.logger.info "Emission by #{emitter.class} is failed: #{e}"
|
|
114
|
+
return [] if enriched_artifacts.empty?
|
|
115
|
+
|
|
116
|
+
Parallel.map(valid_emitters) do |emitter|
|
|
117
|
+
emission = emitter.emit
|
|
118
|
+
Mihari.logger.info "Emission by #{emitter.class} is succeeded"
|
|
119
|
+
emission
|
|
120
|
+
rescue StandardError => e
|
|
121
|
+
Mihari.logger.info "Emission by #{emitter.class} is failed: #{e}"
|
|
122
|
+
end.compact
|
|
134
123
|
end
|
|
135
124
|
|
|
136
125
|
#
|
|
@@ -165,27 +154,28 @@ module Mihari
|
|
|
165
154
|
end
|
|
166
155
|
|
|
167
156
|
#
|
|
168
|
-
#
|
|
157
|
+
# Get analyzer class
|
|
158
|
+
#
|
|
159
|
+
# @param [String] analyzer_name
|
|
169
160
|
#
|
|
170
|
-
# @return [
|
|
161
|
+
# @return [Class<Mihari::Analyzers::Base>] analyzer class
|
|
171
162
|
#
|
|
172
|
-
def
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# set interval in the top level
|
|
177
|
-
options = params[:options] || {}
|
|
178
|
-
interval = options[:interval]
|
|
179
|
-
params[:interval] = interval if interval
|
|
163
|
+
def get_analyzer_class(analyzer_name)
|
|
164
|
+
analyzer = ANALYZER_TO_CLASS[analyzer_name]
|
|
165
|
+
return analyzer if analyzer
|
|
180
166
|
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
query = params[:query]
|
|
184
|
-
analyzer = klass.new(query, **params)
|
|
167
|
+
raise ArgumentError, "#{analyzer_name} is not supported"
|
|
168
|
+
end
|
|
185
169
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
170
|
+
#
|
|
171
|
+
# @return [Array<Mihari::Analyzers::Base>]
|
|
172
|
+
#
|
|
173
|
+
def analyzers
|
|
174
|
+
@analyzers ||= rule.queries.map do |query_params|
|
|
175
|
+
analyzer_name = query_params[:analyzer]
|
|
176
|
+
klass = get_analyzer_class(analyzer_name)
|
|
177
|
+
klass.from_query(query_params)
|
|
178
|
+
end
|
|
189
179
|
end
|
|
190
180
|
|
|
191
181
|
#
|
|
@@ -203,53 +193,33 @@ module Mihari
|
|
|
203
193
|
end
|
|
204
194
|
|
|
205
195
|
#
|
|
206
|
-
#
|
|
196
|
+
# Deep copied emitters
|
|
207
197
|
#
|
|
208
|
-
# @return [Mihari::
|
|
198
|
+
# @return [Array<Mihari::Emitters::Base>]
|
|
209
199
|
#
|
|
210
|
-
def
|
|
211
|
-
|
|
212
|
-
|
|
200
|
+
def emitters
|
|
201
|
+
rule.emitters.map(&:deep_dup).map do |params|
|
|
202
|
+
name = params[:emitter]
|
|
203
|
+
params.delete(:emitter)
|
|
213
204
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
emitter.valid? ? emitter : nil
|
|
205
|
+
klass = get_emitter_class(name)
|
|
206
|
+
klass.new(artifacts: enriched_artifacts, rule: rule, **params)
|
|
207
|
+
end
|
|
218
208
|
end
|
|
219
209
|
|
|
220
210
|
#
|
|
221
|
-
# @return [Array<Mihari::
|
|
211
|
+
# @return [Array<Mihari::Emitters::Base>]
|
|
222
212
|
#
|
|
223
213
|
def valid_emitters
|
|
224
|
-
@valid_emitters ||=
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
#
|
|
228
|
-
# Get analyzer class
|
|
229
|
-
#
|
|
230
|
-
# @param [String] analyzer_name
|
|
231
|
-
#
|
|
232
|
-
# @return [Class<Mihari::Analyzers::Base>] analyzer class
|
|
233
|
-
#
|
|
234
|
-
def get_analyzer_class(analyzer_name)
|
|
235
|
-
analyzer = ANALYZER_TO_CLASS[analyzer_name]
|
|
236
|
-
return analyzer if analyzer
|
|
237
|
-
|
|
238
|
-
raise ArgumentError, "#{analyzer_name} is not supported"
|
|
214
|
+
@valid_emitters ||= emitters.select(&:valid?)
|
|
239
215
|
end
|
|
240
216
|
|
|
241
217
|
#
|
|
242
218
|
# Validate configuration of analyzers
|
|
243
219
|
#
|
|
244
220
|
def validate_analyzer_configurations
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
klass = get_analyzer_class(analyzer_name)
|
|
249
|
-
klass_name = klass.to_s.split("::").last
|
|
250
|
-
|
|
251
|
-
instance = klass.new("dummy")
|
|
252
|
-
raise ConfigurationError, "#{klass_name} is not configured correctly" unless instance.configured?
|
|
221
|
+
analyzers.map do |analyzer|
|
|
222
|
+
raise ConfigurationError, "#{analyzer.source} is not configured correctly" unless analyzer.configured?
|
|
253
223
|
end
|
|
254
224
|
end
|
|
255
225
|
end
|
data/lib/mihari/clients/base.rb
CHANGED
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Commands
|
|
5
5
|
module Database
|
|
6
|
-
|
|
7
|
-
thor
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
class << self
|
|
7
|
+
def included(thor)
|
|
8
|
+
thor.class_eval do
|
|
9
|
+
desc "migrate", "Migrate DB schemas"
|
|
10
|
+
method_option :verbose, type: :boolean, default: true
|
|
11
|
+
#
|
|
12
|
+
# @param [String] direction
|
|
13
|
+
#
|
|
14
|
+
def migrate(direction = "up")
|
|
15
|
+
ActiveRecord::Migration.verbose = options["verbose"]
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Mihari::Database.with_db_connection { Mihari::Database.migrate direction.to_sym }
|
|
18
|
+
end
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
end
|
data/lib/mihari/commands/rule.rb
CHANGED
|
@@ -5,60 +5,62 @@ require "pathname"
|
|
|
5
5
|
module Mihari
|
|
6
6
|
module Commands
|
|
7
7
|
module Rule
|
|
8
|
-
|
|
9
|
-
thor
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# Validate format of a rule
|
|
13
|
-
#
|
|
14
|
-
# @param [String] path
|
|
15
|
-
#
|
|
16
|
-
def validate(path)
|
|
17
|
-
rule = Structs::Rule.from_path_or_id(path)
|
|
18
|
-
|
|
19
|
-
begin
|
|
20
|
-
rule.validate!
|
|
21
|
-
Mihari.logger.info "Valid format. The input is parsed as the following:"
|
|
22
|
-
Mihari.logger.info rule.data.to_yaml
|
|
23
|
-
rescue RuleValidationError
|
|
24
|
-
nil
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
desc "init [PATH]", "Initialize a new rule file"
|
|
29
|
-
#
|
|
30
|
-
# Initialize a new rule file
|
|
31
|
-
#
|
|
32
|
-
# @param [String] path
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
def init(path = "./rule.yml")
|
|
36
|
-
warning = "#{path} exists. Do you want to overwrite it? (y/n)"
|
|
37
|
-
return if Pathname(path).exist? && !(yes? warning)
|
|
38
|
-
|
|
39
|
-
initialize_rule path
|
|
40
|
-
|
|
41
|
-
Mihari.logger.info "A new rule is initialized: #{path}."
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
no_commands do
|
|
8
|
+
class << self
|
|
9
|
+
def included(thor)
|
|
10
|
+
thor.class_eval do
|
|
11
|
+
desc "validate [PATH]", "Validate a rule file"
|
|
45
12
|
#
|
|
46
|
-
#
|
|
13
|
+
# Validate format of a rule
|
|
47
14
|
#
|
|
48
|
-
|
|
49
|
-
|
|
15
|
+
# @param [String] path
|
|
16
|
+
#
|
|
17
|
+
def validate(path)
|
|
18
|
+
rule = Structs::Rule.from_path_or_id(path)
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
rule.validate!
|
|
22
|
+
Mihari.logger.info "Valid format. The input is parsed as the following:"
|
|
23
|
+
Mihari.logger.info rule.data.to_yaml
|
|
24
|
+
rescue RuleValidationError
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
50
27
|
end
|
|
51
28
|
|
|
29
|
+
desc "init [PATH]", "Initialize a new rule file"
|
|
52
30
|
#
|
|
53
|
-
#
|
|
31
|
+
# Initialize a new rule file
|
|
54
32
|
#
|
|
55
33
|
# @param [String] path
|
|
56
|
-
# @param [Dry::Files] files
|
|
57
34
|
#
|
|
58
|
-
# @return [nil]
|
|
59
35
|
#
|
|
60
|
-
def
|
|
61
|
-
|
|
36
|
+
def init(path = "./rule.yml")
|
|
37
|
+
warning = "#{path} exists. Do you want to overwrite it? (y/n)"
|
|
38
|
+
return if Pathname(path).exist? && !(yes? warning)
|
|
39
|
+
|
|
40
|
+
initialize_rule path
|
|
41
|
+
|
|
42
|
+
Mihari.logger.info "A new rule file has been initialized: #{path}."
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
no_commands do
|
|
46
|
+
#
|
|
47
|
+
# @return [Mihari::Structs::Rule]
|
|
48
|
+
#
|
|
49
|
+
def rule_template
|
|
50
|
+
Structs::Rule.from_path File.expand_path("../templates/rule.yml.erb", __dir__)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# Create a new rule
|
|
55
|
+
#
|
|
56
|
+
# @param [String] path
|
|
57
|
+
# @param [Dry::Files] files
|
|
58
|
+
#
|
|
59
|
+
# @return [nil]
|
|
60
|
+
#
|
|
61
|
+
def initialize_rule(path, files = Dry::Files.new)
|
|
62
|
+
files.write(path, rule_template.yaml)
|
|
63
|
+
end
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
end
|
|
@@ -3,64 +3,92 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Commands
|
|
5
5
|
module Search
|
|
6
|
-
|
|
6
|
+
class << self
|
|
7
|
+
class RuleWrapper
|
|
8
|
+
include Mixins::ErrorNotification
|
|
9
|
+
|
|
10
|
+
# @return [Nihari::Structs::Rule]
|
|
11
|
+
attr_reader :rule
|
|
12
|
+
|
|
13
|
+
# @return [Boolean]
|
|
14
|
+
attr_reader :force_overwrite
|
|
15
|
+
|
|
16
|
+
def initialize(rule, force_overwrite:)
|
|
17
|
+
@rule = rule
|
|
18
|
+
@force_overwrite = force_overwrite
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def force_overwrite?
|
|
22
|
+
force_overwrite
|
|
23
|
+
end
|
|
7
24
|
|
|
8
|
-
def self.included(thor)
|
|
9
|
-
thor.class_eval do
|
|
10
|
-
desc "search [PATH]", "Search by a rule"
|
|
11
|
-
method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
|
|
12
|
-
#
|
|
13
|
-
# Search by a rule
|
|
14
25
|
#
|
|
15
|
-
# @
|
|
26
|
+
# @return [Boolean]
|
|
16
27
|
#
|
|
17
|
-
def
|
|
18
|
-
Mihari::
|
|
19
|
-
|
|
28
|
+
def diff?
|
|
29
|
+
model = Mihari::Rule.find(rule.id)
|
|
30
|
+
model.data != rule.data.deep_stringify_keys
|
|
31
|
+
rescue ActiveRecord::RecordNotFound
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def update_or_create
|
|
36
|
+
rule.model.save
|
|
37
|
+
end
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
def run
|
|
40
|
+
begin
|
|
41
|
+
analyzer = rule.analyzer
|
|
42
|
+
rescue ConfigurationError => e
|
|
43
|
+
# if there is a configuration error, output that error without the stack trace
|
|
44
|
+
Mihari.logger.error e.to_s
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
with_error_notification do
|
|
49
|
+
alert = analyzer.run
|
|
50
|
+
if alert.nil?
|
|
51
|
+
Mihari.logger.info "There is no new artifact found"
|
|
24
52
|
return
|
|
25
53
|
end
|
|
26
54
|
|
|
27
|
-
|
|
28
|
-
|
|
55
|
+
data = Mihari::Entities::Alert.represent(alert)
|
|
56
|
+
puts JSON.pretty_generate(data.as_json)
|
|
29
57
|
end
|
|
30
58
|
end
|
|
31
59
|
end
|
|
32
|
-
end
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
def included(thor)
|
|
62
|
+
thor.class_eval do
|
|
63
|
+
desc "search [PATH]", "Search by a rule"
|
|
64
|
+
method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
|
|
65
|
+
#
|
|
66
|
+
# Search by a rule
|
|
67
|
+
#
|
|
68
|
+
# @param [String] path_or_id
|
|
69
|
+
#
|
|
70
|
+
def search(path_or_id)
|
|
71
|
+
Mihari::Database.with_db_connection do
|
|
72
|
+
rule = Structs::Rule.from_path_or_id path_or_id
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# create a new rule
|
|
49
|
-
rule.model.save
|
|
50
|
-
end
|
|
51
|
-
end
|
|
74
|
+
begin
|
|
75
|
+
rule.validate!
|
|
76
|
+
rescue RuleValidationError
|
|
77
|
+
return
|
|
78
|
+
end
|
|
52
79
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
force_overwrite = options["force_overwrite"] || false
|
|
81
|
+
wrapper = RuleWrapper.new(rule, force_overwrite: force_overwrite)
|
|
82
|
+
|
|
83
|
+
if wrapper.diff? && !force_overwrite
|
|
84
|
+
message = "There is diff in the rule (#{rule.id}). Are you sure you want to overwrite the rule? (y/n)"
|
|
85
|
+
return unless yes?(message)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
wrapper.update_or_create
|
|
89
|
+
wrapper.run
|
|
90
|
+
end
|
|
91
|
+
end
|
|
64
92
|
end
|
|
65
93
|
end
|
|
66
94
|
end
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Commands
|
|
5
5
|
module Version
|
|
6
|
-
|
|
7
|
-
thor
|
|
8
|
-
|
|
6
|
+
class << self
|
|
7
|
+
def included(thor)
|
|
8
|
+
thor.class_eval do
|
|
9
|
+
map %w[--version -v] => :__print_version
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
desc "--version, -v", "Print the version"
|
|
12
|
+
def __print_version
|
|
13
|
+
puts Mihari::VERSION
|
|
14
|
+
end
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
end
|
data/lib/mihari/commands/web.rb
CHANGED
|
@@ -3,29 +3,32 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Commands
|
|
5
5
|
module Web
|
|
6
|
-
|
|
7
|
-
thor
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
6
|
+
class << self
|
|
7
|
+
def included(thor)
|
|
8
|
+
thor.class_eval do
|
|
9
|
+
desc "web", "Launch the web app"
|
|
10
|
+
method_option :port, type: :numeric, default: 9292, desc: "Hostname to listen on"
|
|
11
|
+
method_option :host, type: :string, default: "localhost", desc: "Port to listen on"
|
|
12
|
+
method_option :threads, type: :string, default: "0:5", desc: "min:max threads to use"
|
|
13
|
+
method_option :verbose, type: :boolean, default: true, desc: "Report each request"
|
|
14
|
+
method_option :worker_timeout, type: :numeric, default: 60, desc: "Worker timeout value (in seconds)"
|
|
15
|
+
method_option :hide_config_values, type: :boolean, default: false,
|
|
16
|
+
desc: "Whether to hide config values or not"
|
|
17
|
+
method_option :open, type: :boolean, default: true, desc: "Whether to open the app in browser or not"
|
|
18
|
+
method_option :rack_env, type: :string, default: "production", desc: "Rack environment"
|
|
19
|
+
def web
|
|
20
|
+
Mihari.config.hide_config_values = options["hide_config_values"]
|
|
21
|
+
# set rack env as production
|
|
22
|
+
ENV["RACK_ENV"] ||= options["rack_env"]
|
|
23
|
+
Mihari::App.run!(
|
|
24
|
+
port: options["port"],
|
|
25
|
+
host: options["host"],
|
|
26
|
+
threads: options["threads"],
|
|
27
|
+
verbose: options["verbose"],
|
|
28
|
+
worker_timeout: options["worker_timeout"],
|
|
29
|
+
open: options["open"]
|
|
30
|
+
)
|
|
31
|
+
end
|
|
29
32
|
end
|
|
30
33
|
end
|
|
31
34
|
end
|
data/lib/mihari/emitters/base.rb
CHANGED
|
@@ -6,7 +6,20 @@ module Mihari
|
|
|
6
6
|
include Mixins::Configurable
|
|
7
7
|
include Mixins::Retriable
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# @return [Array<Mihari::Artifact>]
|
|
10
|
+
attr_reader :artifacts
|
|
11
|
+
|
|
12
|
+
# @return [Mihari::Structs::Rule]
|
|
13
|
+
attr_reader :rule
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# @param [Array<Mihari::Artifact>] artifacts
|
|
17
|
+
# @param [Mihari::Structs::Rule] rule
|
|
18
|
+
# @param [Hash] **_options
|
|
19
|
+
#
|
|
20
|
+
def initialize(artifacts:, rule:, **_options)
|
|
21
|
+
@artifacts = artifacts
|
|
22
|
+
@rule = rule
|
|
10
23
|
end
|
|
11
24
|
|
|
12
25
|
class << self
|
|
@@ -4,28 +4,21 @@ module Mihari
|
|
|
4
4
|
module Emitters
|
|
5
5
|
class Database < Base
|
|
6
6
|
def valid?
|
|
7
|
-
|
|
7
|
+
configured?
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
#
|
|
11
11
|
# Create an alert
|
|
12
12
|
#
|
|
13
|
-
# @param [Arra<Mihari::Artifact>] artifacts
|
|
14
|
-
# @param [Mihari::Structs::Rule] rule
|
|
15
|
-
#
|
|
16
13
|
# @return [Mihari::Alert]
|
|
17
14
|
#
|
|
18
|
-
def emit
|
|
15
|
+
def emit
|
|
19
16
|
return if artifacts.empty?
|
|
20
17
|
|
|
21
18
|
tags = rule.tags.filter_map { |name| Tag.find_or_create_by(name: name) }.uniq
|
|
22
19
|
taggings = tags.map { |tag| Tagging.new(tag_id: tag.id) }
|
|
23
20
|
|
|
24
|
-
alert = Alert.new(
|
|
25
|
-
artifacts: artifacts,
|
|
26
|
-
taggings: taggings,
|
|
27
|
-
rule_id: rule.id
|
|
28
|
-
)
|
|
21
|
+
alert = Alert.new(artifacts: artifacts, taggings: taggings, rule_id: rule.id)
|
|
29
22
|
alert.save
|
|
30
23
|
alert
|
|
31
24
|
end
|
data/lib/mihari/emitters/misp.rb
CHANGED
|
@@ -9,11 +9,22 @@ module Mihari
|
|
|
9
9
|
# @return [String, nil]
|
|
10
10
|
attr_reader :api_key
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# @return [Array<Mihari::Artifact>]
|
|
13
|
+
attr_reader :artifacts
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
# @return [Mihari::Structs::Rule]
|
|
16
|
+
attr_reader :rule
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# @param [Array<Mihari::Artifact>] artifacts
|
|
20
|
+
# @param [Mihari::Structs::Rule] rule
|
|
21
|
+
# @param [Hash] **options
|
|
22
|
+
#
|
|
23
|
+
def initialize(artifacts:, rule:, **options)
|
|
24
|
+
super(artifacts: artifacts, rule: rule, **options)
|
|
25
|
+
|
|
26
|
+
@url = options[:url] || Mihari.config.misp_url
|
|
27
|
+
@api_key = options[:api_key] || Mihari.config.misp_api_key
|
|
17
28
|
end
|
|
18
29
|
|
|
19
30
|
# @return [Boolean]
|
|
@@ -40,7 +51,7 @@ module Mihari
|
|
|
40
51
|
#
|
|
41
52
|
# @return [::MISP::Event]
|
|
42
53
|
#
|
|
43
|
-
def emit
|
|
54
|
+
def emit
|
|
44
55
|
return if artifacts.empty?
|
|
45
56
|
|
|
46
57
|
client.create_event({
|