mihari 7.6.1 → 7.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +25 -5
- data/README.md +1 -0
- data/Rakefile +3 -1
- data/lefthook.yml +4 -4
- data/lib/mihari/clients/crtsh.rb +1 -1
- data/lib/mihari/clients/dnstwister.rb +1 -1
- data/lib/mihari/clients/google_public_dns.rb +1 -1
- data/lib/mihari/clients/mmdb.rb +1 -1
- data/lib/mihari/clients/shodan_internet_db.rb +1 -1
- data/lib/mihari/commands/rule.rb +27 -4
- data/lib/mihari/database.rb +1 -1
- data/lib/mihari/enrichers/base.rb +1 -1
- data/lib/mihari/enrichers/whois.rb +3 -1
- data/lib/mihari/models/artifact.rb +42 -6
- data/lib/mihari/rule.rb +24 -9
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/index-BgJUBUyh.css +1 -0
- data/lib/mihari/web/public/assets/index-FQP-p7mX.js +1563 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +389 -389
- data/mihari.gemspec +44 -42
- data/mkdocs.yml +1 -1
- data/requirements.txt +2 -2
- metadata +126 -99
- data/build_frontend.sh +0 -11
- data/lib/mihari/web/public/assets/index-CNoViC5p.css +0 -1
- data/lib/mihari/web/public/assets/index-ruBsf_QV.js +0 -1783
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d9b88f5339ebe2064567127db7fac4a4e0c9ec56648c575de2c96fcb16a3af8
|
4
|
+
data.tar.gz: 15f840a496f423dff92dcc34e2f7c4ad39ed7b1a370c053a5f5cdf29c39f84a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f07c6c65db1dd47634e0f5919d1b6faa8250cc02d255226085c1fd88f6a32e78338a6ddeaa7b0f221e4593af81735635c4652cdfd46b415bfa0e5ecaf08cb0f1
|
7
|
+
data.tar.gz: 5d8411ddbee4e1e466e3521ec611ffba67e52ea3852e3cfce8f095978b60584b15cc541118064d2fd8bfa9e40c14720899efafa006f96543e7947fecbd5b76da
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.2
|
3
|
+
NewCops: enable
|
3
4
|
Metrics/BlockLength:
|
4
5
|
Max: 150
|
5
6
|
Exclude:
|
@@ -12,13 +13,32 @@ Metrics/MethodLength:
|
|
12
13
|
Metrics/AbcSize:
|
13
14
|
Max: 50
|
14
15
|
RSpec/MultipleMemoizedHelpers:
|
15
|
-
Max:
|
16
|
+
Max: 15
|
16
17
|
RSpec/ExampleLength:
|
17
18
|
Max: 20
|
18
|
-
RSpec/
|
19
|
-
|
19
|
+
RSpec/NestedGroups:
|
20
|
+
Max: 5
|
21
|
+
RSpec/RepeatedExampleGroupDescription:
|
22
|
+
Enabled: false
|
23
|
+
RSpec/ReceiveMessages:
|
24
|
+
Enabled: false
|
25
|
+
RSpec/MultipleExpectations:
|
26
|
+
Enabled: false
|
27
|
+
RSpec/SpecFilePathFormat:
|
28
|
+
Enabled: false
|
29
|
+
FactoryBot/SyntaxMethods:
|
30
|
+
Enabled: false
|
20
31
|
require:
|
32
|
+
- rubocop-capybara
|
21
33
|
- rubocop-factory_bot
|
34
|
+
- rubocop-performance
|
22
35
|
- rubocop-rake
|
23
36
|
- rubocop-rspec
|
24
37
|
- rubocop-yard
|
38
|
+
- standard
|
39
|
+
- standard-custom
|
40
|
+
- standard-performance
|
41
|
+
inherit_gem:
|
42
|
+
standard: config/base.yml
|
43
|
+
standard-custom: config/base.yml
|
44
|
+
standard-performance: config/base.yml
|
data/README.md
CHANGED
@@ -27,6 +27,7 @@ Mihari supports the following services by default.
|
|
27
27
|
- [SecurityTrails](https://securitytrails.com/)
|
28
28
|
- [Shodan](https://shodan.io)
|
29
29
|
- [urlscan.io](https://urlscan.io)
|
30
|
+
- [Validin](https://validin.com)
|
30
31
|
- [VirusTotal](http://virustotal.com) & [VirusTotal Intelligence](https://www.virustotal.com/gui/intelligence-overview)
|
31
32
|
- [ZoomEye](https://zoomeye.org)
|
32
33
|
|
data/Rakefile
CHANGED
@@ -3,8 +3,9 @@
|
|
3
3
|
require "time"
|
4
4
|
|
5
5
|
require "rspec/core/rake_task"
|
6
|
-
require "
|
6
|
+
require "rubocop/rake_task"
|
7
7
|
|
8
|
+
RuboCop::RakeTask.new
|
8
9
|
RSpec::Core::RakeTask.new(:spec)
|
9
10
|
|
10
11
|
task default: :spec
|
@@ -67,6 +68,7 @@ namespace :build do
|
|
67
68
|
end
|
68
69
|
end
|
69
70
|
|
71
|
+
desc "Build including Swagger doc and frontend assets"
|
70
72
|
task :build do
|
71
73
|
Rake::Task["build:swagger"].invoke
|
72
74
|
Rake::Task["build:frontend"].invoke
|
data/lefthook.yml
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
pre-commit:
|
2
2
|
commands:
|
3
|
-
|
3
|
+
rubocop:
|
4
4
|
glob: "*.rb"
|
5
|
-
run: bundle exec
|
5
|
+
run: bundle exec rubocop --fix {staged_files}
|
6
6
|
stage_fixed: true
|
7
7
|
eslint:
|
8
8
|
root: "frontend/"
|
@@ -19,5 +19,5 @@ pre-commit:
|
|
19
19
|
glob: "*.{js,ts,vue}"
|
20
20
|
run: npm run type-check
|
21
21
|
actionlint:
|
22
|
-
glob: ".github/workflows/*.yaml"
|
23
|
-
run: actionlint
|
22
|
+
glob: ".github/workflows/*.{yaml,yml}"
|
23
|
+
run: actionlint {staged_files}
|
data/lib/mihari/clients/crtsh.rb
CHANGED
data/lib/mihari/clients/mmdb.rb
CHANGED
data/lib/mihari/commands/rule.rb
CHANGED
@@ -26,14 +26,37 @@ module Mihari
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
desc "validate PATH", "Validate
|
29
|
+
desc "validate PATH", "Validate rule(s)"
|
30
30
|
#
|
31
|
-
# Validate
|
31
|
+
# Validate rule(s)
|
32
|
+
#
|
33
|
+
# @param [Array<String>] paths
|
34
|
+
#
|
35
|
+
def validate(*paths)
|
36
|
+
# @type [Array<Mihari::ValidationError>]
|
37
|
+
errors = paths.flat_map { |path| Dir.glob(path) }.map do |path|
|
38
|
+
Dry::Monads::Try[ValidationError] { Mihari::Rule.from_file(path) }
|
39
|
+
end.filter_map do |result|
|
40
|
+
result.exception if result.error?
|
41
|
+
end
|
42
|
+
return if errors.empty?
|
43
|
+
|
44
|
+
errors.each do |error|
|
45
|
+
data = Entities::ErrorMessage.represent(message: error.message, detail: error.detail)
|
46
|
+
warn JSON.pretty_generate(data.as_json)
|
47
|
+
end
|
48
|
+
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "format PATH", "format a rule"
|
53
|
+
#
|
54
|
+
# Format a rule file
|
32
55
|
#
|
33
56
|
# @param [String] path
|
34
57
|
#
|
35
|
-
def
|
36
|
-
rule = Dry::Monads::Try[ValidationError] { Mihari::Rule.
|
58
|
+
def format(path)
|
59
|
+
rule = Dry::Monads::Try[ValidationError] { Mihari::Rule.from_file(path) }.value!
|
37
60
|
puts rule.data.to_yaml
|
38
61
|
end
|
39
62
|
|
data/lib/mihari/database.rb
CHANGED
@@ -6,7 +6,7 @@ ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym "CPE" }
|
|
6
6
|
#
|
7
7
|
# Mihari v7 DB schema
|
8
8
|
#
|
9
|
-
class V7Schema < ActiveRecord::Migration[7.
|
9
|
+
class V7Schema < ActiveRecord::Migration[7.2]
|
10
10
|
def change
|
11
11
|
create_table :rules, id: :string, if_not_exists: true do |t|
|
12
12
|
t.string :title, null: false
|
@@ -16,7 +16,9 @@ module Mihari
|
|
16
16
|
def call(artifact)
|
17
17
|
return if artifact.domain.nil?
|
18
18
|
|
19
|
-
artifact.
|
19
|
+
artifact.tap do |tapped|
|
20
|
+
tapped.whois_record ||= memoized_lookup(PublicSuffix.domain(artifact.domain))
|
21
|
+
end
|
20
22
|
end
|
21
23
|
|
22
24
|
private
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "ostruct"
|
4
|
+
|
3
5
|
module Mihari
|
4
6
|
module Models
|
5
7
|
#
|
@@ -158,10 +160,7 @@ module Mihari
|
|
158
160
|
# @return [Boolean] true if it is unique. Otherwise false.
|
159
161
|
#
|
160
162
|
def unique?(base_time: nil, artifact_ttl: nil)
|
161
|
-
artifact = self.class.joins(:alert).where(
|
162
|
-
data:,
|
163
|
-
alert: {rule_id:}
|
164
|
-
).order(created_at: :desc).first
|
163
|
+
artifact = self.class.joins(:alert).where(data:, alert: {rule_id:}).order(created_at: :desc).first
|
165
164
|
return true if artifact.nil?
|
166
165
|
|
167
166
|
# check whether the artifact is decayed or not
|
@@ -179,7 +178,32 @@ module Mihari
|
|
179
178
|
end
|
180
179
|
|
181
180
|
def enrich
|
182
|
-
callable_enrichers
|
181
|
+
enrich_by_enrichers callable_enrichers
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# @param [Array<Mihari::Enrichers::Base>] enrichers
|
186
|
+
# @param [Boolean] parallel
|
187
|
+
#
|
188
|
+
# @return [Mihari::Models::Artifact]
|
189
|
+
#
|
190
|
+
def enrich_by_enrichers(enrichers)
|
191
|
+
# NOTE: doing parallel with ActiveRecord objects is troublesome (e.g. connection issue, etc.)
|
192
|
+
# so converting the object to an OpenStruct object
|
193
|
+
s = struct
|
194
|
+
results = Parallel.map(enrichers) { |enricher| enricher.result s }
|
195
|
+
enriched = results.compact.map { |result| result.value_or(nil) }.compact
|
196
|
+
|
197
|
+
self.dns_records = enriched.map(&:dns_records).flatten.compact
|
198
|
+
self.cpes = enriched.map(&:cpes).flatten.compact
|
199
|
+
self.ports = enriched.map(&:ports).flatten.compact
|
200
|
+
self.vulnerabilities = enriched.map(&:vulnerabilities).flatten.compact
|
201
|
+
|
202
|
+
self.autonomous_system = enriched.map(&:autonomous_system).compact.first
|
203
|
+
self.geolocation = enriched.map(&:geolocation).compact.first
|
204
|
+
self.whois_record = enriched.map(&:whois_record).compact.first
|
205
|
+
|
206
|
+
self
|
183
207
|
end
|
184
208
|
|
185
209
|
#
|
@@ -195,6 +219,18 @@ module Mihari
|
|
195
219
|
end
|
196
220
|
end
|
197
221
|
|
222
|
+
def struct
|
223
|
+
OpenStruct.new(attributes).tap do |s|
|
224
|
+
s.domain = domain
|
225
|
+
s.cpes ||= []
|
226
|
+
s.dns_records ||= []
|
227
|
+
s.ports ||= []
|
228
|
+
s.reverse_dns_names ||= []
|
229
|
+
s.vulnerabilities ||= []
|
230
|
+
s.tags ||= []
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
198
234
|
class << self
|
199
235
|
# @!method search_by_filter(filter)
|
200
236
|
# @param [Mihari::Structs::Filters::Search] filter
|
@@ -212,7 +248,7 @@ module Mihari
|
|
212
248
|
#
|
213
249
|
def callable_enrichers
|
214
250
|
@callable_enrichers ||= Mihari.enrichers.map(&:new).select do |enricher|
|
215
|
-
enricher.callable?
|
251
|
+
enricher.callable? self
|
216
252
|
end
|
217
253
|
end
|
218
254
|
|
data/lib/mihari/rule.rb
CHANGED
@@ -5,6 +5,9 @@ module Mihari
|
|
5
5
|
include Concerns::FalsePositiveNormalizable
|
6
6
|
include Concerns::FalsePositiveValidatable
|
7
7
|
|
8
|
+
# @return [String, nil]
|
9
|
+
attr_reader :path_or_id
|
10
|
+
|
8
11
|
# @return [Hash]
|
9
12
|
attr_reader :data
|
10
13
|
|
@@ -19,9 +22,11 @@ module Mihari
|
|
19
22
|
#
|
20
23
|
# @param [Hash] data
|
21
24
|
#
|
22
|
-
|
25
|
+
# @param [Object] path_or_id
|
26
|
+
def initialize(path_or_id: nil, **data)
|
23
27
|
super()
|
24
28
|
|
29
|
+
@path_or_id = path_or_id
|
25
30
|
@data = data.deep_symbolize_keys
|
26
31
|
@errors = nil
|
27
32
|
@base_time = Time.now.utc
|
@@ -173,10 +178,7 @@ module Mihari
|
|
173
178
|
#
|
174
179
|
def enriched_artifacts
|
175
180
|
@enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
|
176
|
-
artifact.
|
177
|
-
# NOTE: To apply changes correctly, enrichers should be applied to an artifact serially
|
178
|
-
enrichers.each { |enricher| enricher.result(tapped) }
|
179
|
-
end
|
181
|
+
artifact.enrich_by_enrichers enrichers
|
180
182
|
end
|
181
183
|
end
|
182
184
|
|
@@ -251,16 +253,29 @@ module Mihari
|
|
251
253
|
end
|
252
254
|
|
253
255
|
class << self
|
256
|
+
#
|
257
|
+
# Load rule from YAML file
|
258
|
+
#
|
259
|
+
# @param [String] path
|
260
|
+
#
|
261
|
+
# @return [Mihari::Rule]
|
262
|
+
#
|
263
|
+
def from_file(path)
|
264
|
+
yaml = File.read(path)
|
265
|
+
from_yaml(yaml, path: path)
|
266
|
+
end
|
267
|
+
|
254
268
|
#
|
255
269
|
# Load rule from YAML string
|
256
270
|
#
|
257
271
|
# @param [String] yaml
|
272
|
+
# @param [String, nil] path
|
258
273
|
#
|
259
274
|
# @return [Mihari::Rule]
|
260
275
|
#
|
261
|
-
def from_yaml(yaml)
|
276
|
+
def from_yaml(yaml, path: nil)
|
262
277
|
data = YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol])
|
263
|
-
new(**data)
|
278
|
+
new(path_or_id: path, **data)
|
264
279
|
end
|
265
280
|
|
266
281
|
#
|
@@ -269,7 +284,7 @@ module Mihari
|
|
269
284
|
# @return [Mihari::Rule]
|
270
285
|
#
|
271
286
|
def from_model(model)
|
272
|
-
new(**model.data)
|
287
|
+
new(path_or_id: model.id, **model.data)
|
273
288
|
end
|
274
289
|
end
|
275
290
|
|
@@ -409,7 +424,7 @@ module Mihari
|
|
409
424
|
@data = result.to_h
|
410
425
|
@errors = result.errors
|
411
426
|
|
412
|
-
raise ValidationError.new("
|
427
|
+
raise ValidationError.new("#{path_or_id}: validation failed", errors) if errors?
|
413
428
|
end
|
414
429
|
end
|
415
430
|
end
|
data/lib/mihari/version.rb
CHANGED