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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b91d99562526b653b793f71e8ef5575f113d26859ca86b91f1980d1c44e898c
4
- data.tar.gz: 07c302ce446b0f0986bfc82dd575ebde203f4b25b73a1aee72a2ce37a08b9b87
3
+ metadata.gz: 328a34edf637c36456cc7de39fccabdaaf75937b449b5e8a8e0434e71b9328c2
4
+ data.tar.gz: 8483c669cfb3e715c86b4a3878961885e4cd4f93611e487c4360cb3030267c18
5
5
  SHA512:
6
- metadata.gz: 67d607a09ab2992b6721358b9e96e0c049a355221b2e97358440d70f96ef4933ac86b337bcc12bdc4b2aafccf4e5d6cf8cd68f4b2bbe567523bfba22b7fbd88c
7
- data.tar.gz: 6f73de19824d31e21bae4ee7ce456c1b15fa0a875d1f07025c33ac0356ef257ac2eb819ffd8b685bf8914a472da04f691e5ae02a361397da9a83253d5345b108
6
+ metadata.gz: 9858608c1ceb30f846a27b487e4bcbbad463aba56a401991fdade512bf6a7a7f028e5facecd764b200d94795a1da1a2b631b85540dfe1bbf002c756100b6627f
7
+ data.tar.gz: c050fdafab0e7855eb610ad3d50762583ca931d31afeeb57fc654a2dc65ff381f20cb31eb28d0a61b5888c0a19df02320b09a529830fe332389b59f739721aba
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:3.2.2-alpine3.19
1
+ FROM ruby:3.3.0-alpine3.19
2
2
 
3
3
  ARG MIHARI_VERSION=0.0.0
4
4
 
data/lib/mihari/actor.rb CHANGED
@@ -50,6 +50,13 @@ module Mihari
50
50
  options[:timeout]
51
51
  end
52
52
 
53
+ #
54
+ # @return [Boolean]
55
+ #
56
+ def parallel?
57
+ options[:parallel] || Mihari.config.parallel
58
+ end
59
+
53
60
  def validate_configuration!
54
61
  return if configured?
55
62
 
@@ -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
- prepend MemoWise
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
- # @param [String] value
17
+ # Enrich an artifact
18
+ #
19
+ # @param [Mihari::Models::Artifact] artifact
17
20
  #
18
- def call(value)
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] value
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(value)
32
+ def result(artifact)
33
+ return unless callable?(artifact)
34
+
28
35
  result = Try[StandardError] do
29
- retry_on_error(
30
- times: retry_times,
31
- interval: retry_interval,
32
- exponential_backoff: retry_exponential_backoff
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 #{value.truncate(32)} failed: #{result.failure}")
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
- # Query Google Public DNS
10
+ # @param [Mihari::Models::Artifact] artifact
11
11
  #
12
- # @param [String] name
12
+ # @return [Mihari::Models::Artifact]
13
13
  #
14
- # @return [Mihari::Structs::GooglePublicDNS::Response]
15
- #
16
- def call(name)
17
- client.query_all name
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
- # Query MMDB
10
+ # @param [Mihari::Models::Artifact] artifact
11
11
  #
12
- # @param [String] ip
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 [Mihari::Structs::MMDB::Response]
31
+ # @return [Boolean]
15
32
  #
16
- def call(ip)
17
- client.query ip
33
+ def callable_relationships?(artifact)
34
+ artifact.geolocation.nil? || artifact.autonomous_system.nil?
18
35
  end
19
- memo_wise :call
20
36
 
21
- private
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 [String] ip
12
+ # @param [Mihari::Models::Artifact] artifact
13
13
  #
14
14
  # @return [Mihari::Structs::Shodan::InternetDBResponse, nil]
15
15
  #
16
- def call(ip)
17
- client.query ip
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 [Hash, nil] options
16
+ # @param [Mihari::Models::Artifact] artifact
13
17
  #
14
- def initialize(options: nil)
15
- super(options: options)
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
- # Query IAIA Whois API
20
- #
21
- # @param [String] domain
38
+ # @param [Mihari::Models::Artifact] artifact
22
39
  #
23
- # @return [Mihari::Models::WhoisRecord, nil]
40
+ # @return [Boolean]
24
41
  #
25
- def call(domain)
26
- memoized_call PublicSuffix.domain(domain)
42
+ def callable_relationships?(artifact)
43
+ artifact.whois_record.nil?
27
44
  end
28
45
 
29
- private
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 memoized_call(domain)
37
- record = whois.lookup(domain)
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 :memoized_call
58
+ memo_wise :memoized_lookup
51
59
 
52
60
  #
53
61
  # @return [::Whois::Client]
@@ -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