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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc2d39ad14f53cf88ed2c68bb2e2668e9a43f36444099401ef9fbe1347948644
4
- data.tar.gz: 2d08fed6ec6f82da36f72a2fb05ae1ede82ba65d6bc9bcdb7c60b8f7e244c71a
3
+ metadata.gz: d49d8a079c765cb4b7ad7a08825a9d0bd4e9e21fbaf2c0f185c4f27e8f2f2ca6
4
+ data.tar.gz: aa693115eb1dacc09d13ca0942d859e24566c09a4cd6d702840ec9df6c3cc87a
5
5
  SHA512:
6
- metadata.gz: 684c4106554f9c11bc7ff7f8633b32c5fa24b4127001415e6d14bd4de73e51a04084eb802a29cac109d8b56c71bc7068c8657f4d6ec7cd53e0519ef353aa859f
7
- data.tar.gz: 4a4c3183ca85d47b54f7a98837059a9fae585c2d079fe005336520c62290bc317aa61880695f923a2ad0ad7ba5cf18e3e888990a9394d82b4ac41939f34b3403
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
- times: retry_times,
70
- interval: retry_interval,
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
- ignore_error = options[:ignore_error]
41
- return ignore_error unless ignore_error.nil?
40
+ options[:ignore_error] || Mihari.config.ignore_error
41
+ end
42
42
 
43
- Mihari.config.ignore_error
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 = TypeChecker.type(query)
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
@@ -18,7 +18,7 @@ module Mihari
18
18
  def initialize(query, options: nil)
19
19
  super(refang(query), options: options)
20
20
 
21
- @type = TypeChecker.type(query)
21
+ @type = DataType.type(query)
22
22
  end
23
23
 
24
24
  def artifacts
@@ -22,7 +22,7 @@ module Mihari
22
22
  def initialize(query, options: nil, api_key: nil)
23
23
  super(refang(query), options: options)
24
24
 
25
- @type = TypeChecker.type(query)
25
+ @type = DataType.type(query)
26
26
 
27
27
  @api_key = api_key || Mihari.config.otx_api_key
28
28
  end
@@ -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 = TypeChecker.type(query)
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
@@ -22,7 +22,7 @@ module Mihari
22
22
  def initialize(query, options: nil, api_key: nil)
23
23
  super(refang(query), options: options)
24
24
 
25
- @type = TypeChecker.type(query)
25
+ @type = DataType.type(query)
26
26
 
27
27
  @api_key = api_key || Mihari.config.pulsedive_api_key
28
28
  end
@@ -25,7 +25,7 @@ module Mihari
25
25
  def initialize(query, options: nil, api_key: nil)
26
26
  super(refang(query), options: options)
27
27
 
28
- @type = TypeChecker.type(query)
28
+ @type = DataType.type(query)
29
29
 
30
30
  @api_key = api_key || Mihari.config.securitytrails_api_key
31
31
  end
@@ -22,7 +22,7 @@ module Mihari
22
22
  def initialize(query, options: nil, api_key: nil)
23
23
  super(refang(query), options: options)
24
24
 
25
- @type = TypeChecker.type(query)
25
+ @type = DataType.type(query)
26
26
 
27
27
  @api_key = api_key || Mihari.config.virustotal_api_key
28
28
  end
@@ -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 [String, nil]
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 type checker
5
+ # (Artifact) Data Type
6
6
  #
7
- class TypeChecker
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.new data
28
- true
29
- rescue IPAddr::InvalidAddressError => _e
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
- uri = Addressable::URI.parse("http://#{data}")
36
- uri.host == data && PublicSuffix.valid?(uri.host)
37
- rescue Addressable::URI::InvalidURIError => _e
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
- uri = Addressable::URI.parse(data)
44
- uri.scheme && uri.host && uri.path && PublicSuffix.valid?(uri.host)
45
- rescue Addressable::URI::InvalidURIError => _e
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
- return "hash" if hash?
57
- return "ip" if ip?
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
- "mail" if mail?
60
+ found[...-1].to_s
62
61
  end
63
62
 
64
63
  # @return [String, nil]
65
64
  def detailed_type
66
- return "md5" if md5?
67
- return "sha1" if sha1?
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
@@ -154,7 +154,7 @@ module Mihari
154
154
 
155
155
  case adapter
156
156
  when "postgresql", "mysql2"
157
- ActiveRecord::Base.establish_connection(Mihari.config.database_url.to_s)
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 [Array<Mihari::Structs::GooglePublicDNS::Response>]
14
+ # @return [Mihari::Structs::GooglePublicDNS::Response]
15
15
  #
16
16
  def call(name)
17
- %w[A AAAA CNAME TXT NS].filter_map { |resource_type| query_by_type(name, resource_type) }
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 http
49
- HTTP::Factory.build timeout: timeout
31
+ def client
32
+ Clients::GooglePublicDNS.new
50
33
  end
51
34
  end
52
35
  end
@@ -46,7 +46,7 @@ module Mihari
46
46
 
47
47
  super(*args, **kwargs)
48
48
 
49
- self.data_type = TypeChecker.type(data)
49
+ self.data_type = DataType.type(data)
50
50
 
51
51
  @tags = []
52
52
  @rule_id = ""
@@ -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
- result = enricher.result(domain).bind do |responses|
23
+ enricher.result(domain).bind do |res|
24
24
  Success(
25
- responses.map do |res|
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
- analyzers.flat_map do |analyzer|
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.compact
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 |query_params|
296
- analyzer_name = query_params[:analyzer]
293
+ @analyzers ||= queries.map do |params|
294
+ analyzer_name = params[:analyzer]
297
295
  klass = get_analyzer_class(analyzer_name)
298
- analyzer = klass.from_query(query_params)
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
- AnalyzerOptions = Options | IgnoreErrorOptions
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 => "A",
8
- 2 => "NS",
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"),
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "6.1.0"
4
+ VERSION = "6.2.0"
5
5
  end