mihari 6.1.0 → 6.2.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/lib/mihari/actor.rb +3 -5
- data/lib/mihari/analyzers/base.rb +7 -3
- data/lib/mihari/analyzers/circl.rb +1 -1
- data/lib/mihari/analyzers/dnstwister.rb +1 -1
- data/lib/mihari/analyzers/otx.rb +1 -1
- data/lib/mihari/analyzers/passivetotal.rb +1 -1
- data/lib/mihari/analyzers/pulsedive.rb +1 -1
- data/lib/mihari/analyzers/securitytrails.rb +1 -1
- data/lib/mihari/analyzers/virustotal.rb +1 -1
- data/lib/mihari/clients/google_public_dns.rb +31 -0
- data/lib/mihari/config.rb +5 -1
- data/lib/mihari/{type_checker.rb → data_type.rb} +32 -37
- data/lib/mihari/database.rb +1 -3
- data/lib/mihari/enrichers/google_public_dns.rb +4 -21
- data/lib/mihari/models/artifact.rb +1 -1
- data/lib/mihari/models/dns.rb +3 -8
- data/lib/mihari/rule.rb +20 -7
- data/lib/mihari/schemas/options.rb +5 -1
- data/lib/mihari/structs/google_public_dns.rb +4 -8
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/{index-216d49d1.js → index-1d77cd61.js} +56 -50
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +1 -1
- data/lib/mihari.rb +2 -1
- data/mihari.gemspec +4 -3
- data/requirements.txt +1 -1
- metadata +19 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d49d8a079c765cb4b7ad7a08825a9d0bd4e9e21fbaf2c0f185c4f27e8f2f2ca6
|
4
|
+
data.tar.gz: aa693115eb1dacc09d13ca0942d859e24566c09a4cd6d702840ec9df6c3cc87a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e50a254b0c5565c3691cc34df0413258a1903f8864f0ced141fab63cb673f02998451723036917f2adf21f759ec3f0086b772e98a02ee0436869dd5846bf3427
|
7
|
+
data.tar.gz: 63738367f8edd23331518eb839c188dde8fb67257b83b5cd6a11e79dc725aa89b151753e7429515ff1b2f7c689da77adf4eac998241b7bfd04f88302aa52c923
|
data/lib/mihari/actor.rb
CHANGED
@@ -65,11 +65,9 @@ module Mihari
|
|
65
65
|
|
66
66
|
def result(...)
|
67
67
|
Try[StandardError] do
|
68
|
-
retry_on_error(
|
69
|
-
|
70
|
-
|
71
|
-
exponential_backoff: retry_exponential_backoff
|
72
|
-
) { call(...) }
|
68
|
+
retry_on_error(times: retry_times, interval: retry_interval, exponential_backoff: retry_exponential_backoff) do
|
69
|
+
call(...)
|
70
|
+
end
|
73
71
|
end.to_result
|
74
72
|
end
|
75
73
|
|
@@ -37,10 +37,14 @@ module Mihari
|
|
37
37
|
# @return [Boolean]
|
38
38
|
#
|
39
39
|
def ignore_error?
|
40
|
-
|
41
|
-
|
40
|
+
options[:ignore_error] || Mihari.config.ignore_error
|
41
|
+
end
|
42
42
|
|
43
|
-
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
#
|
46
|
+
def parallel?
|
47
|
+
options[:parallel] || Mihari.config.parallel
|
44
48
|
end
|
45
49
|
|
46
50
|
# @return [Array<String>, Array<Mihari::Models::Artifact>]
|
@@ -26,7 +26,7 @@ module Mihari
|
|
26
26
|
def initialize(query, options: nil, username: nil, password: nil)
|
27
27
|
super(refang(query), options: options)
|
28
28
|
|
29
|
-
@type =
|
29
|
+
@type = DataType.type(query)
|
30
30
|
|
31
31
|
@username = username || Mihari.config.circl_passive_username
|
32
32
|
@password = password || Mihari.config.circl_passive_password
|
data/lib/mihari/analyzers/otx.rb
CHANGED
@@ -26,7 +26,7 @@ module Mihari
|
|
26
26
|
def initialize(query, options: nil, api_key: nil, username: nil)
|
27
27
|
super(refang(query), options: options)
|
28
28
|
|
29
|
-
@type =
|
29
|
+
@type = DataType.type(query)
|
30
30
|
|
31
31
|
@username = username || Mihari.config.passivetotal_username
|
32
32
|
@api_key = api_key || Mihari.config.passivetotal_api_key
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
#
|
6
|
+
# Google Public DNS enricher
|
7
|
+
#
|
8
|
+
class GooglePublicDNS < Base
|
9
|
+
#
|
10
|
+
# @param [String] base_url
|
11
|
+
# @param [Hash] headers
|
12
|
+
# @param [Integer, nil] timeout
|
13
|
+
#
|
14
|
+
def initialize(base_url = "https://dns.google", headers: {}, timeout: nil)
|
15
|
+
super(base_url, headers: headers, timeout: timeout)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Query Google Public DNS by resource type
|
20
|
+
#
|
21
|
+
# @param [String] name
|
22
|
+
#
|
23
|
+
# @return [Mihari::Structs::GooglePublicDNS::Response, nil]
|
24
|
+
#
|
25
|
+
def query_all(name)
|
26
|
+
Structs::GooglePublicDNS::Response.from_dynamic! get_json("/resolve",
|
27
|
+
params: { name: name, type: "ALL" })
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/mihari/config.rb
CHANGED
@@ -42,6 +42,7 @@ module Mihari
|
|
42
42
|
ignore_error: false,
|
43
43
|
pagination_interval: 0,
|
44
44
|
pagination_limit: 100,
|
45
|
+
parallel: false,
|
45
46
|
retry_exponential_backoff: true,
|
46
47
|
retry_interval: 5,
|
47
48
|
retry_times: 3,
|
@@ -62,7 +63,7 @@ module Mihari
|
|
62
63
|
# @return [String, nil]
|
63
64
|
|
64
65
|
# @!attribute [r] database_url
|
65
|
-
# @return [
|
66
|
+
# @return [URI, nil]
|
66
67
|
|
67
68
|
# @!attribute [r] fofa_api_key
|
68
69
|
# @return [String, nil]
|
@@ -151,6 +152,9 @@ module Mihari
|
|
151
152
|
# @!attribute [r] pagination_limit
|
152
153
|
# @return [Integer]
|
153
154
|
|
155
|
+
# @!attribute [r] parallel
|
156
|
+
# @return [Boolean]
|
157
|
+
|
154
158
|
# @!attribute [r] ignore_error
|
155
159
|
# @return [Boolean]
|
156
160
|
|
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
#
|
5
|
-
# Artifact
|
5
|
+
# (Artifact) Data Type
|
6
6
|
#
|
7
|
-
class
|
7
|
+
class DataType
|
8
|
+
include Dry::Monads[:try]
|
9
|
+
|
8
10
|
# @return [String]
|
9
11
|
attr_reader :data
|
10
12
|
|
@@ -24,26 +26,25 @@ module Mihari
|
|
24
26
|
|
25
27
|
# @return [Boolean]
|
26
28
|
def ip?
|
27
|
-
IPAddr
|
28
|
-
|
29
|
-
|
30
|
-
false
|
29
|
+
Try[IPAddr::InvalidAddressError] do
|
30
|
+
IPAddr.new(data).to_s == data
|
31
|
+
end.to_result.value_or(false)
|
31
32
|
end
|
32
33
|
|
33
34
|
# @return [Boolean]
|
34
35
|
def domain?
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
false
|
36
|
+
Try[Addressable::URI::InvalidURIError] do
|
37
|
+
uri = Addressable::URI.parse("http://#{data}")
|
38
|
+
uri.host == data && PublicSuffix.valid?(uri.host)
|
39
|
+
end.to_result.value_or(false)
|
39
40
|
end
|
40
41
|
|
41
42
|
# @return [Boolean]
|
42
43
|
def url?
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
false
|
44
|
+
Try[Addressable::URI::InvalidURIError] do
|
45
|
+
uri = Addressable::URI.parse(data)
|
46
|
+
uri.scheme && uri.host && uri.path && PublicSuffix.valid?(uri.host)
|
47
|
+
end.to_result.value_or(false)
|
47
48
|
end
|
48
49
|
|
49
50
|
# @return [Boolean]
|
@@ -53,38 +54,20 @@ module Mihari
|
|
53
54
|
|
54
55
|
# @return [String, nil]
|
55
56
|
def type
|
56
|
-
|
57
|
-
return
|
58
|
-
return "domain" if domain?
|
59
|
-
return "url" if url?
|
57
|
+
found = %i[hash? ip? domain? url? mail?].find { |method| send(method) if respond_to?(method) }
|
58
|
+
return nil if found.nil?
|
60
59
|
|
61
|
-
|
60
|
+
found[...-1].to_s
|
62
61
|
end
|
63
62
|
|
64
63
|
# @return [String, nil]
|
65
64
|
def detailed_type
|
66
|
-
|
67
|
-
return
|
68
|
-
return "sha256" if sha256?
|
69
|
-
return "sha512" if sha512?
|
65
|
+
found = %i[md5? sha1? sha256? sha512?].find { |method| send(method) if respond_to?(method) }
|
66
|
+
return found[...-1].to_s unless found.nil?
|
70
67
|
|
71
68
|
type
|
72
69
|
end
|
73
70
|
|
74
|
-
class << self
|
75
|
-
# @return [String, nil]
|
76
|
-
def type(data)
|
77
|
-
new(data).type
|
78
|
-
end
|
79
|
-
|
80
|
-
# @return [String, nil]
|
81
|
-
def detailed_type(data)
|
82
|
-
new(data).detailed_type
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
71
|
# @return [Boolean]
|
89
72
|
def md5?
|
90
73
|
data.match?(/^[A-Fa-f0-9]{32}$/)
|
@@ -104,5 +87,17 @@ module Mihari
|
|
104
87
|
def sha512?
|
105
88
|
data.match?(/^[A-Fa-f0-9]{128}$/)
|
106
89
|
end
|
90
|
+
|
91
|
+
class << self
|
92
|
+
# @return [String, nil]
|
93
|
+
def type(data)
|
94
|
+
new(data).type
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [String, nil]
|
98
|
+
def detailed_type(data)
|
99
|
+
new(data).detailed_type
|
100
|
+
end
|
101
|
+
end
|
107
102
|
end
|
108
103
|
end
|
data/lib/mihari/database.rb
CHANGED
@@ -154,7 +154,7 @@ module Mihari
|
|
154
154
|
|
155
155
|
case adapter
|
156
156
|
when "postgresql", "mysql2"
|
157
|
-
ActiveRecord::Base.establish_connection
|
157
|
+
ActiveRecord::Base.establish_connection Mihari.config.database_url.to_s
|
158
158
|
else
|
159
159
|
ActiveRecord::Base.establish_connection(
|
160
160
|
adapter: adapter,
|
@@ -162,8 +162,6 @@ module Mihari
|
|
162
162
|
)
|
163
163
|
end
|
164
164
|
ActiveRecord::Base.logger = Logger.new($stdout) if development_env?
|
165
|
-
rescue StandardError => e
|
166
|
-
Mihari.logger.error e
|
167
165
|
end
|
168
166
|
|
169
167
|
#
|
@@ -11,27 +11,10 @@ module Mihari
|
|
11
11
|
#
|
12
12
|
# @param [String] name
|
13
13
|
#
|
14
|
-
# @return [
|
14
|
+
# @return [Mihari::Structs::GooglePublicDNS::Response]
|
15
15
|
#
|
16
16
|
def call(name)
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
#
|
21
|
-
# Query Google Public DNS by resource type
|
22
|
-
#
|
23
|
-
# @param [String] name
|
24
|
-
# @param [String] resource_type
|
25
|
-
#
|
26
|
-
# @return [Mihari::Structs::GooglePublicDNS::Response, nil]
|
27
|
-
#
|
28
|
-
def query_by_type(name, resource_type)
|
29
|
-
url = "https://dns.google/resolve"
|
30
|
-
params = { name: name, type: resource_type }
|
31
|
-
res = http.get(url, params: params)
|
32
|
-
Structs::GooglePublicDNS::Response.from_dynamic! JSON.parse(res.body.to_s)
|
33
|
-
rescue HTTPError
|
34
|
-
nil
|
17
|
+
client.query_all name
|
35
18
|
end
|
36
19
|
|
37
20
|
class << self
|
@@ -45,8 +28,8 @@ module Mihari
|
|
45
28
|
|
46
29
|
private
|
47
30
|
|
48
|
-
def
|
49
|
-
|
31
|
+
def client
|
32
|
+
Clients::GooglePublicDNS.new
|
50
33
|
end
|
51
34
|
end
|
52
35
|
end
|
data/lib/mihari/models/dns.rb
CHANGED
@@ -20,16 +20,11 @@ module Mihari
|
|
20
20
|
# @return [Array<Mihari::Models::DnsRecord>]
|
21
21
|
#
|
22
22
|
def build_by_domain(domain, enricher: Enrichers::GooglePublicDNS.new)
|
23
|
-
|
23
|
+
enricher.result(domain).bind do |res|
|
24
24
|
Success(
|
25
|
-
|
26
|
-
res.answers.map do |answer|
|
27
|
-
new(resource: answer.resource_type, value: answer.data)
|
28
|
-
end
|
29
|
-
end.flatten
|
25
|
+
res.answers.map { |answer| new(resource: answer.resource_type, value: answer.data) }
|
30
26
|
)
|
31
|
-
end
|
32
|
-
result.value_or []
|
27
|
+
end.value_or([])
|
33
28
|
end
|
34
29
|
end
|
35
30
|
end
|
data/lib/mihari/rule.rb
CHANGED
@@ -110,9 +110,7 @@ module Mihari
|
|
110
110
|
# @return [Array<Mihari::Models::Artifact>]
|
111
111
|
#
|
112
112
|
def artifacts
|
113
|
-
|
114
|
-
# @type [Dry::Monads::Result::Success<Array<Mihari::Models::Artifact>>, Dry::Monads::Result::Failure]
|
115
|
-
result = analyzer.result
|
113
|
+
analyzer_results.flat_map do |result|
|
116
114
|
case result
|
117
115
|
when Success
|
118
116
|
artifacts = result.value!
|
@@ -123,7 +121,7 @@ module Mihari
|
|
123
121
|
else
|
124
122
|
raise result.failure unless analyzer.ignore_error?
|
125
123
|
end
|
126
|
-
end
|
124
|
+
end
|
127
125
|
end
|
128
126
|
|
129
127
|
#
|
@@ -292,15 +290,30 @@ module Mihari
|
|
292
290
|
# @return [Array<Mihari::Analyzers::Base>]
|
293
291
|
#
|
294
292
|
def analyzers
|
295
|
-
@analyzers ||= queries.map do |
|
296
|
-
analyzer_name =
|
293
|
+
@analyzers ||= queries.map do |params|
|
294
|
+
analyzer_name = params[:analyzer]
|
297
295
|
klass = get_analyzer_class(analyzer_name)
|
298
|
-
analyzer = klass.from_query(
|
296
|
+
analyzer = klass.from_query(params)
|
299
297
|
analyzer.validate_configuration!
|
300
298
|
analyzer
|
301
299
|
end
|
302
300
|
end
|
303
301
|
|
302
|
+
def parallel_analyzers
|
303
|
+
analyzers.select(&:parallel?)
|
304
|
+
end
|
305
|
+
|
306
|
+
def serial_analyzers
|
307
|
+
analyzers.reject(&:parallel?)
|
308
|
+
end
|
309
|
+
|
310
|
+
# @return [Array<Dry::Monads::Result::Success<Array<Mihari::Models::Artifact>>, Dry::Monads::Result::Failure>]
|
311
|
+
def analyzer_results
|
312
|
+
parallel_results = Parallel.map(parallel_analyzers) { |analyzer| analyzer.result }
|
313
|
+
serial_results = serial_analyzers.map(&:result)
|
314
|
+
parallel_results + serial_results
|
315
|
+
end
|
316
|
+
|
304
317
|
#
|
305
318
|
# Get emitter class
|
306
319
|
#
|
@@ -15,7 +15,11 @@ module Mihari
|
|
15
15
|
optional(:ignore_error).value(:bool).default(Mihari.config.ignore_error)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
ParallelOptions = Dry::Schema.Params do
|
19
|
+
optional(:parallel).value(:bool).default(Mihari.config.parallel)
|
20
|
+
end
|
21
|
+
|
22
|
+
AnalyzerOptions = Options | IgnoreErrorOptions | ParallelOptions
|
19
23
|
|
20
24
|
PaginationOptions = Dry::Schema.Params do
|
21
25
|
optional(:pagination_interval).value(:integer).default(Mihari.config.pagination_interval)
|
@@ -3,13 +3,9 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Structs
|
5
5
|
module GooglePublicDNS
|
6
|
-
INT_TYPE_TO_TYPE =
|
7
|
-
1 =>
|
8
|
-
|
9
|
-
5 => "CNAME",
|
10
|
-
16 => "TXT",
|
11
|
-
28 => "AAAA"
|
12
|
-
}.freeze
|
6
|
+
INT_TYPE_TO_TYPE =
|
7
|
+
{ 1 => :A, 38 => :A6, 28 => :AAAA, 18 => :AFSDB, 255 => :ANY, 42 => :APL, 34 => :ATMA, 252 => :AXFR, 37 => :CERT,
|
8
|
+
5 => :CNAME, 49 => :DHCID, 32_769 => :DLV, 39 => :DNAME, 48 => :DNSKEY, 43 => :DS, 31 => :EID, 102 => :GID, 27 => :GPOS, 13 => :HINFO, 45 => :IPSECKEY, 20 => :ISDN, 251 => :IXFR, 25 => :KEY, 36 => :KX, 29 => :LOC, 254 => :MAILA, 253 => :MAILB, 7 => :MB, 3 => :MD, 4 => :MF, 8 => :MG, 14 => :MINFO, 9 => :MR, 15 => :MX, 35 => :NAPTR, 32 => :NIMLOC, 2 => :NS, 22 => :NSAP, 23 => :NSAP_PTR, 47 => :NSEC, 50 => :NSEC3, 51 => :NSEC3PARAMS, 10 => :NULL, 30 => :NXT, 41 => :OPT, 12 => :PTR, 26 => :PX, 17 => :RP, 46 => :RRSIG, 21 => :RT, 24 => :SIG, 40 => :SINK, 6 => :SOA, 33 => :SRV, 44 => :SSHFP, 250 => :TSIG, 16 => :TXT, 101 => :UID, 100 => :UINFO, 103 => :UNSPEC, 11 => :WKS, 19 => :X25 }
|
13
9
|
|
14
10
|
class Answer < Dry::Struct
|
15
11
|
# @!attribute [r] name
|
@@ -30,7 +26,7 @@ module Mihari
|
|
30
26
|
#
|
31
27
|
def from_dynamic!(d)
|
32
28
|
d = Types::Hash[d]
|
33
|
-
resource_type = INT_TYPE_TO_TYPE[d.fetch("type")]
|
29
|
+
resource_type = INT_TYPE_TO_TYPE[d.fetch("type")].to_s
|
34
30
|
new(
|
35
31
|
name: d.fetch("name"),
|
36
32
|
data: d.fetch("data"),
|
data/lib/mihari/version.rb
CHANGED