mihari 7.2.0 → 7.3.0
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/Dockerfile +1 -1
- data/lib/mihari/actor.rb +7 -0
- data/lib/mihari/analyzers/base.rb +0 -7
- data/lib/mihari/enrichers/base.rb +54 -12
- data/lib/mihari/enrichers/google_public_dns.rb +28 -7
- data/lib/mihari/enrichers/mmdb.rb +25 -7
- data/lib/mihari/enrichers/shodan.rb +35 -4
- data/lib/mihari/enrichers/whois.rb +32 -24
- data/lib/mihari/models/alert.rb +12 -0
- data/lib/mihari/models/artifact.rb +105 -181
- data/lib/mihari/models/rule.rb +21 -0
- data/lib/mihari/rule.rb +27 -6
- data/lib/mihari/schemas/alert.rb +3 -3
- data/lib/mihari/schemas/analyzer.rb +27 -27
- data/lib/mihari/schemas/emitter.rb +9 -9
- data/lib/mihari/schemas/macros.rb +2 -2
- data/lib/mihari/schemas/options.rb +2 -5
- data/lib/mihari/schemas/rule.rb +12 -12
- data/lib/mihari/services/builders.rb +0 -153
- data/lib/mihari/services/enrichers.rb +1 -1
- data/lib/mihari/services/getters.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/{index-GWurHG1o.js → index-JHS0L8KZ.js} +29 -29
- data/lib/mihari/web/public/index.html +1 -1
- data/mihari.gemspec +2 -2
- data/requirements.txt +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 328a34edf637c36456cc7de39fccabdaaf75937b449b5e8a8e0434e71b9328c2
|
4
|
+
data.tar.gz: 8483c669cfb3e715c86b4a3878961885e4cd4f93611e487c4360cb3030267c18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9858608c1ceb30f846a27b487e4bcbbad463aba56a401991fdade512bf6a7a7f028e5facecd764b200d94795a1da1a2b631b85540dfe1bbf002c756100b6627f
|
7
|
+
data.tar.gz: c050fdafab0e7855eb610ad3d50762583ca931d31afeeb57fc654a2dc65ff381f20cb31eb28d0a61b5888c0a19df02320b09a529830fe332389b59f739721aba
|
data/Dockerfile
CHANGED
data/lib/mihari/actor.rb
CHANGED
@@ -40,13 +40,6 @@ module Mihari
|
|
40
40
|
options[:ignore_error] || Mihari.config.ignore_error
|
41
41
|
end
|
42
42
|
|
43
|
-
#
|
44
|
-
# @return [Boolean]
|
45
|
-
#
|
46
|
-
def parallel?
|
47
|
-
options[:parallel] || Mihari.config.parallel
|
48
|
-
end
|
49
|
-
|
50
43
|
# @return [Array<String>, Array<Mihari::Models::Artifact>]
|
51
44
|
def artifacts
|
52
45
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
@@ -6,46 +6,88 @@ module Mihari
|
|
6
6
|
# Base class for enrichers
|
7
7
|
#
|
8
8
|
class Base < Actor
|
9
|
-
|
10
|
-
|
9
|
+
#
|
10
|
+
# @param [Hash, nil] options
|
11
|
+
#
|
11
12
|
def initialize(options: nil)
|
12
13
|
super(options: options)
|
13
14
|
end
|
14
15
|
|
15
16
|
#
|
16
|
-
#
|
17
|
+
# Enrich an artifact
|
18
|
+
#
|
19
|
+
# @param [Mihari::Models::Artifact] artifact
|
17
20
|
#
|
18
|
-
|
21
|
+
# @return [Mihari::Models::Artifact]
|
22
|
+
#
|
23
|
+
def call(artifact)
|
19
24
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
20
25
|
end
|
21
26
|
|
22
27
|
#
|
23
|
-
# @param [Mihari::Models::Artifact]
|
28
|
+
# @param [Mihari::Models::Artifact] artifact
|
24
29
|
#
|
25
30
|
# @return [Dry::Monads::Result::Success<Object>, Dry::Monads::Result::Failure]
|
26
31
|
#
|
27
|
-
def result(
|
32
|
+
def result(artifact)
|
33
|
+
return unless callable?(artifact)
|
34
|
+
|
28
35
|
result = Try[StandardError] do
|
29
|
-
retry_on_error(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
) { call value }
|
36
|
+
retry_on_error(times: retry_times, interval: retry_interval,
|
37
|
+
exponential_backoff: retry_exponential_backoff) do
|
38
|
+
call artifact
|
39
|
+
end
|
34
40
|
end.to_result
|
35
41
|
|
36
42
|
if result.failure?
|
37
|
-
Mihari.logger.warn("Enricher:#{self.class.key} for #{
|
43
|
+
Mihari.logger.warn("Enricher:#{self.class.key} for #{artifact.data.truncate(32)} failed: #{result.failure}")
|
38
44
|
end
|
39
45
|
|
40
46
|
result
|
41
47
|
end
|
42
48
|
|
49
|
+
#
|
50
|
+
# @param [Mihari::Models::Artifact] artifact
|
51
|
+
#
|
52
|
+
# @return [Boolean]
|
53
|
+
#
|
54
|
+
def callable?(artifact)
|
55
|
+
callable_data_type?(artifact) && callable_relationships?(artifact)
|
56
|
+
end
|
57
|
+
|
43
58
|
class << self
|
44
59
|
def inherited(child)
|
45
60
|
super
|
46
61
|
Mihari.enrichers << child
|
47
62
|
end
|
48
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
#
|
68
|
+
# @param [Mihari::Models::Artifact] artifact
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
#
|
72
|
+
def callable_data_type?(artifact)
|
73
|
+
supported_data_types.include? artifact.data_type
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# @param [Mihari::Models::Artifact] artifact
|
78
|
+
#
|
79
|
+
# @return [Boolean]
|
80
|
+
#
|
81
|
+
def callable_relationships?(artifact)
|
82
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# @return [Array<String>]
|
87
|
+
#
|
88
|
+
def supported_data_types
|
89
|
+
[]
|
90
|
+
end
|
49
91
|
end
|
50
92
|
end
|
51
93
|
end
|
@@ -7,14 +7,22 @@ module Mihari
|
|
7
7
|
#
|
8
8
|
class GooglePublicDNS < Base
|
9
9
|
#
|
10
|
-
#
|
10
|
+
# @param [Mihari::Models::Artifact] artifact
|
11
11
|
#
|
12
|
-
# @
|
12
|
+
# @return [Mihari::Models::Artifact]
|
13
13
|
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
client.query_all
|
14
|
+
def call(artifact)
|
15
|
+
return if artifact.domain.nil?
|
16
|
+
|
17
|
+
res = client.query_all(artifact.domain)
|
18
|
+
|
19
|
+
artifact.tap do |tapped|
|
20
|
+
if tapped.dns_records.empty?
|
21
|
+
tapped.dns_records = res.answers.map do |answer|
|
22
|
+
Models::DnsRecord.new(resource: answer.resource_type, value: answer.data)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
18
26
|
end
|
19
27
|
|
20
28
|
class << self
|
@@ -28,8 +36,21 @@ module Mihari
|
|
28
36
|
|
29
37
|
private
|
30
38
|
|
39
|
+
#
|
40
|
+
# @param [Mihari::Models::Artifact] artifact
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
def callable_relationships?(artifact)
|
45
|
+
artifact.dns_records.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
def supported_data_types
|
49
|
+
%w[url domain]
|
50
|
+
end
|
51
|
+
|
31
52
|
def client
|
32
|
-
Clients::GooglePublicDNS.new(timeout: timeout)
|
53
|
+
@client ||= Clients::GooglePublicDNS.new(timeout: timeout)
|
33
54
|
end
|
34
55
|
end
|
35
56
|
end
|
@@ -7,18 +7,36 @@ module Mihari
|
|
7
7
|
#
|
8
8
|
class MMDB < Base
|
9
9
|
#
|
10
|
-
#
|
10
|
+
# @param [Mihari::Models::Artifact] artifact
|
11
11
|
#
|
12
|
-
|
12
|
+
def call(artifact)
|
13
|
+
res = client.query(artifact.data)
|
14
|
+
|
15
|
+
artifact.tap do |tapped|
|
16
|
+
tapped.autonomous_system ||= Models::AutonomousSystem.new(number: res.asn) if res.asn
|
17
|
+
if res.country_code
|
18
|
+
tapped.geolocation ||= Models::Geolocation.new(
|
19
|
+
country: NormalizeCountry(res.country_code, to: :short),
|
20
|
+
country_code: res.country_code
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
#
|
29
|
+
# @param [Mihari::Models::Artifact] artifact
|
13
30
|
#
|
14
|
-
# @return [
|
31
|
+
# @return [Boolean]
|
15
32
|
#
|
16
|
-
def
|
17
|
-
|
33
|
+
def callable_relationships?(artifact)
|
34
|
+
artifact.geolocation.nil? || artifact.autonomous_system.nil?
|
18
35
|
end
|
19
|
-
memo_wise :call
|
20
36
|
|
21
|
-
|
37
|
+
def supported_data_types
|
38
|
+
%w[ip]
|
39
|
+
end
|
22
40
|
|
23
41
|
def client
|
24
42
|
@client ||= Clients::MMDB.new(timeout: timeout)
|
@@ -9,17 +9,48 @@ module Mihari
|
|
9
9
|
#
|
10
10
|
# Query Shodan Internet DB
|
11
11
|
#
|
12
|
-
# @param [
|
12
|
+
# @param [Mihari::Models::Artifact] artifact
|
13
13
|
#
|
14
14
|
# @return [Mihari::Structs::Shodan::InternetDBResponse, nil]
|
15
15
|
#
|
16
|
-
def call(
|
17
|
-
client.query
|
16
|
+
def call(artifact)
|
17
|
+
res = client.query(artifact.data)
|
18
|
+
|
19
|
+
artifact.tap do |tapped|
|
20
|
+
tapped.cpes = (res&.cpes || []).map { |cpe| Models::CPE.new(name: cpe) } if tapped.cpes.empty?
|
21
|
+
tapped.ports = (res&.ports || []).map { |port| Models::Port.new(number: port) } if tapped.ports.empty?
|
22
|
+
if tapped.reverse_dns_names.empty?
|
23
|
+
tapped.reverse_dns_names = (res&.hostnames || []).map do |name|
|
24
|
+
Models::ReverseDnsName.new(name: name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# @param [Mihari::Models::Artifact] artifact
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
#
|
35
|
+
def callable?(artifact)
|
36
|
+
false unless supported_data_types.include?(artifact.data_type)
|
18
37
|
end
|
19
|
-
memo_wise :call
|
20
38
|
|
21
39
|
private
|
22
40
|
|
41
|
+
#
|
42
|
+
# @param [Mihari::Models::Artifact] artifact
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
#
|
46
|
+
def callable_relationships?(artifact)
|
47
|
+
artifact.cpes.empty? || artifact.ports.empty? || artifact.reverse_dns_names.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
def supported_data_types
|
51
|
+
%w[ip]
|
52
|
+
end
|
53
|
+
|
23
54
|
def client
|
24
55
|
@client ||= Clients::ShodanInternetDB.new(timeout: timeout)
|
25
56
|
end
|
@@ -8,46 +8,54 @@ module Mihari
|
|
8
8
|
# Whois enricher
|
9
9
|
#
|
10
10
|
class Whois < Base
|
11
|
+
prepend MemoWise
|
12
|
+
|
13
|
+
#
|
14
|
+
# Query IAIA Whois API
|
11
15
|
#
|
12
|
-
# @param [
|
16
|
+
# @param [Mihari::Models::Artifact] artifact
|
13
17
|
#
|
14
|
-
def
|
15
|
-
|
18
|
+
def call(artifact)
|
19
|
+
return if artifact.domain.nil?
|
20
|
+
|
21
|
+
domain = PublicSuffix.domain(artifact.domain)
|
22
|
+
record = memoized_lookup(domain)
|
23
|
+
return if record.parser.available?
|
24
|
+
|
25
|
+
artifact.whois_record ||= Models::WhoisRecord.new(
|
26
|
+
domain: domain,
|
27
|
+
created_on: get_created_on(record.parser),
|
28
|
+
updated_on: get_updated_on(record.parser),
|
29
|
+
expires_on: get_expires_on(record.parser),
|
30
|
+
registrar: get_registrar(record.parser),
|
31
|
+
contacts: get_contacts(record.parser)
|
32
|
+
)
|
16
33
|
end
|
17
34
|
|
35
|
+
private
|
36
|
+
|
18
37
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# @param [String] domain
|
38
|
+
# @param [Mihari::Models::Artifact] artifact
|
22
39
|
#
|
23
|
-
# @return [
|
40
|
+
# @return [Boolean]
|
24
41
|
#
|
25
|
-
def
|
26
|
-
|
42
|
+
def callable_relationships?(artifact)
|
43
|
+
artifact.whois_record.nil?
|
27
44
|
end
|
28
45
|
|
29
|
-
|
46
|
+
def supported_data_types
|
47
|
+
%w[url domain]
|
48
|
+
end
|
30
49
|
|
31
50
|
#
|
32
51
|
# @param [String] domain
|
33
52
|
#
|
34
53
|
# @return [Mihari::Models::WhoisRecord, nil]
|
35
54
|
#
|
36
|
-
def
|
37
|
-
|
38
|
-
parser = record.parser
|
39
|
-
return nil if parser.available?
|
40
|
-
|
41
|
-
Models::WhoisRecord.new(
|
42
|
-
domain: domain,
|
43
|
-
created_on: get_created_on(parser),
|
44
|
-
updated_on: get_updated_on(parser),
|
45
|
-
expires_on: get_expires_on(parser),
|
46
|
-
registrar: get_registrar(parser),
|
47
|
-
contacts: get_contacts(parser)
|
48
|
-
)
|
55
|
+
def memoized_lookup(domain)
|
56
|
+
whois.lookup domain
|
49
57
|
end
|
50
|
-
memo_wise :
|
58
|
+
memo_wise :memoized_lookup
|
51
59
|
|
52
60
|
#
|
53
61
|
# @return [::Whois::Client]
|
data/lib/mihari/models/alert.rb
CHANGED
@@ -6,6 +6,18 @@ module Mihari
|
|
6
6
|
# Alert model
|
7
7
|
#
|
8
8
|
class Alert < ActiveRecord::Base
|
9
|
+
# @!attribute [r] id
|
10
|
+
# @return [Integer, nil]
|
11
|
+
|
12
|
+
# @!attribute [rw] created_at
|
13
|
+
# @return [DateTime]
|
14
|
+
|
15
|
+
# @!attribute [r] rule
|
16
|
+
# @return [Mihari::Models::Rule]
|
17
|
+
|
18
|
+
# @!attribute [r] artifacts
|
19
|
+
# @return [Array<Mihari::Models::Artifact>]
|
20
|
+
|
9
21
|
belongs_to :rule
|
10
22
|
|
11
23
|
has_many :artifacts, dependent: :destroy
|