mihari 4.6.0 → 4.7.1

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: 1f340898bd140d76041cde8c5cb266bcb379f056bac511fcd9de9a4dd20fc299
4
- data.tar.gz: fb96310ecb6efb1dd5059feea692a8128bdff3610579473134653979c2f0512c
3
+ metadata.gz: 38f68ec091b2469095f321f104c1e238599c56afa7f55763920cfeef0df03ca0
4
+ data.tar.gz: 625bbd99f92e4af768b76db15ab72cd53dba6a04150acc3ddfe7feb7d1a82bf7
5
5
  SHA512:
6
- metadata.gz: 8d9758d027fc056f52be261fb2d7c6fa81dc402d4f47c01a8e49c93e1bc084a501af74c0e51c73ac2967c7795529130f1c34801661f1a9935075e197eaf446b0
7
- data.tar.gz: e48d29aa3756a260cc273f9b05e65aa28e6e9e67f377fcc9c4ff722931f7789f363478c9a33470aea41a1bd72a9c9e73c05b76583d52bdb80096c2fdaf627b54
6
+ metadata.gz: fe847e37658ede26d1910e8c73ee1726e2b32335571e03c97c891b2d799380397ed1d32de7e4ed31ae57c861b4a6aa74433454d4871e38e2137100ce342894a1
7
+ data.tar.gz: 688c3adcf6ebb754d02b0fecde033602b47fe23ae11fffaff329ce53b93c268ab274319fdb630c819163856ce9708f2658bfe6fe7790e54ab1488e8d2af90245
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Analyzers
5
+ module Clients
6
+ class OTX
7
+ attr_reader :api_key
8
+
9
+ def initialize(api_key)
10
+ @api_key = api_key
11
+ end
12
+
13
+ def query_by_ip(ip)
14
+ get "https://otx.alienvault.com/api/v1/indicators/IPv4/#{ip}/passive_dns"
15
+ end
16
+
17
+ def query_by_domain(domain)
18
+ get "https://otx.alienvault.com/api/v1/indicators/domain/#{domain}/passive_dns"
19
+ end
20
+
21
+ private
22
+
23
+ def headers
24
+ { "x-otx-api-key": api_key }
25
+ end
26
+
27
+ def get(url)
28
+ res = HTTP.get(url, headers: headers)
29
+ JSON.parse(res.body.to_s)
30
+ rescue HTTPError
31
+ nil
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "otx_ruby"
3
+ require "mihari/analyzers/clients/otx"
4
4
 
5
5
  module Mihari
6
6
  module Analyzers
@@ -34,12 +34,8 @@ module Mihari
34
34
  %w[otx_api_key]
35
35
  end
36
36
 
37
- def domain_client
38
- @domain_client ||= ::OTX::Domain.new(api_key)
39
- end
40
-
41
- def ip_client
42
- @ip_client ||= ::OTX::IP.new(api_key)
37
+ def client
38
+ @client ||= Mihari::Analyzers::Clients::OTX.new(api_key)
43
39
  end
44
40
 
45
41
  #
@@ -73,9 +69,15 @@ module Mihari
73
69
  # @return [Array<String>]
74
70
  #
75
71
  def domain_search
76
- records = domain_client.get_passive_dns(query)
72
+ res = client.query_by_domain(query)
73
+ return [] if res.nil?
74
+
75
+ records = res["passive_dns"] || []
77
76
  records.filter_map do |record|
78
- record.address if record.record_type == "A"
77
+ record_type = record["record_type"]
78
+ address = record["address"]
79
+
80
+ address if record_type == "A"
79
81
  end.uniq
80
82
  end
81
83
 
@@ -85,9 +87,15 @@ module Mihari
85
87
  # @return [Array<String>]
86
88
  #
87
89
  def ip_search
88
- records = ip_client.get_passive_dns(query)
90
+ res = client.query_by_ip(query)
91
+ return [] if res.nil?
92
+
93
+ records = res["passive_dns"] || []
89
94
  records.filter_map do |record|
90
- record.hostname if record.record_type == "A"
95
+ record_type = record["record_type"]
96
+ hostname = record["hostname"]
97
+
98
+ hostname if record_type == "A"
91
99
  end.uniq
92
100
  end
93
101
  end
@@ -51,6 +51,7 @@ module Mihari
51
51
  option :disallowed_data_values, default: proc { [] }
52
52
 
53
53
  option :emitters, optional: true
54
+ option :enrichers, optional: true
54
55
 
55
56
  attr_reader :source
56
57
 
@@ -60,6 +61,7 @@ module Mihari
60
61
  @source = id
61
62
 
62
63
  @emitters = emitters || DEFAULT_EMITTERS
64
+ @enrichers = enrichers || DEFAULT_ENRICHERS
63
65
 
64
66
  validate_analyzer_configurations
65
67
  end
@@ -112,6 +114,21 @@ module Mihari
112
114
  end
113
115
  end
114
116
 
117
+ #
118
+ # Enriched artifacts
119
+ #
120
+ # @return [Array<Mihari::Artifact>]
121
+ #
122
+ def enriched_artifacts
123
+ @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
124
+ enrichers.each do |enricher|
125
+ artifact.enrich_by_enricher(enricher[:enricher])
126
+ end
127
+
128
+ artifact
129
+ end
130
+ end
131
+
115
132
  #
116
133
  # Normalized disallowed data values
117
134
  #
@@ -4,4 +4,6 @@ module Mihari
4
4
  ALLOWED_DATA_TYPES = ["hash", "ip", "domain", "url", "mail"].freeze
5
5
 
6
6
  DEFAULT_EMITTERS = ["database", "misp", "slack", "the_hive", "webhook"].map { |name| { emitter: name } }.freeze
7
+
8
+ DEFAULT_ENRICHERS = ["whois", "ipinfo", "shodan", "google_public_dns"].map { |name| { enricher: name } }.freeze
7
9
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/https"
4
+
5
+ module Mihari
6
+ module Enrichers
7
+ class GooglePublicDNS < Base
8
+ # @return [Boolean]
9
+ def valid?
10
+ true
11
+ end
12
+
13
+ class << self
14
+ #
15
+ # Query Google Public DNS
16
+ #
17
+ # @param [String] name
18
+ # @param [String] resource_type
19
+ #
20
+ # @return [Mihari::Structs::Shodan::GooglePublicDNS::Response, nil]
21
+ #
22
+ def query(name, resource_type)
23
+ url = "https://dns.google/resolve"
24
+ params = { name: name, type: resource_type }
25
+ res = HTTP.get(url, params: params)
26
+
27
+ data = JSON.parse(res.body.to_s)
28
+
29
+ Structs::GooglePublicDNS::Response.from_dynamic! data
30
+ rescue HTTPError
31
+ nil
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "whois-parser"
4
+
5
+ module Mihari
6
+ module Enrichers
7
+ class Whois < Base
8
+ @memo = {}
9
+
10
+ # @return [Boolean]
11
+ def valid?
12
+ true
13
+ end
14
+
15
+ class << self
16
+ #
17
+ # Query IAIA Whois API
18
+ #
19
+ # @param [String] name
20
+ #
21
+ # @return [Mihari::WhoisRecord, nil]
22
+ #
23
+ def query(domain)
24
+ domain = PublicSuffix.domain(domain)
25
+
26
+ # check memo
27
+ if @memo.key?(domain)
28
+ whois_record = @memo[domain]
29
+ # return clone of the record
30
+ return whois_record.dup
31
+ end
32
+
33
+ record = ::Whois.whois(domain)
34
+ parser = record.parser
35
+
36
+ return nil if parser.available?
37
+
38
+ whois_record = WhoisRecord.new(
39
+ domain: domain,
40
+ created_on: get_created_on(parser),
41
+ updated_on: get_updated_on(parser),
42
+ expires_on: get_expires_on(parser),
43
+ registrar: get_registrar(parser),
44
+ contacts: get_contacts(parser)
45
+ )
46
+ # set memo
47
+ @memo[domain] = whois_record
48
+ whois_record
49
+ rescue ::Whois::Error, ::Whois::ParserError, Timeout::Error
50
+ nil
51
+ end
52
+
53
+ def reset_cache
54
+ @memo = {}
55
+ end
56
+
57
+ private
58
+
59
+ #
60
+ # Get created_on
61
+ #
62
+ # @param [::Whois::Parser:] parser
63
+ #
64
+ # @return [Date, nil]
65
+ #
66
+ def get_created_on(parser)
67
+ parser.created_on
68
+ rescue ::Whois::AttributeNotImplemented
69
+ nil
70
+ end
71
+
72
+ #
73
+ # Get updated_on
74
+ #
75
+ # @param [::Whois::Parser:] parser
76
+ #
77
+ # @return [Date, nil]
78
+ #
79
+ def get_updated_on(parser)
80
+ parser.updated_on
81
+ rescue ::Whois::AttributeNotImplemented
82
+ nil
83
+ end
84
+
85
+ #
86
+ # Get expires_on
87
+ #
88
+ # @param [::Whois::Parser:] parser
89
+ #
90
+ # @return [Date, nil]
91
+ #
92
+ def get_expires_on(parser)
93
+ parser.expires_on
94
+ rescue ::Whois::AttributeNotImplemented
95
+ nil
96
+ end
97
+
98
+ #
99
+ # Get registrar
100
+ #
101
+ # @param [::Whois::Parser:] parser
102
+ #
103
+ # @return [Hash, nil]
104
+ #
105
+ def get_registrar(parser)
106
+ parser.registrar&.to_h
107
+ rescue ::Whois::AttributeNotImplemented
108
+ nil
109
+ end
110
+
111
+ #
112
+ # Get contacts
113
+ #
114
+ # @param [::Whois::Parser:] parser
115
+ #
116
+ # @return [Array[Hash], nil]
117
+ #
118
+ def get_contacts(parser)
119
+ parser.contacts.map(&:to_h)
120
+ rescue ::Whois::AttributeNotImplemented
121
+ nil
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
data/lib/mihari/http.rb CHANGED
@@ -44,8 +44,8 @@ module Mihari
44
44
  end
45
45
 
46
46
  class << self
47
- def get(uri, headers: {}, payload: {})
48
- client = new(uri, headers: headers, payload: payload)
47
+ def get(uri, headers: {}, params: {})
48
+ client = new(uri, headers: headers, payload: params)
49
49
  client.get
50
50
  end
51
51
 
@@ -135,6 +135,36 @@ module Mihari
135
135
  enrich_cpes
136
136
  end
137
137
 
138
+ ENRICH_METHODS_BY_ENRICHER = {
139
+ whois: [
140
+ :enrich_whois
141
+ ],
142
+ ipinfo: [
143
+ :enrich_autonomous_system,
144
+ :enrich_geolocation
145
+ ],
146
+ shodan: [
147
+ :enrich_ports,
148
+ :enrich_cpes,
149
+ :enrich_reverse_dns
150
+ ],
151
+ google_public_dns: [
152
+ :enrich_dns
153
+ ]
154
+ }.freeze
155
+
156
+ #
157
+ # Enrich by name of enricher
158
+ #
159
+ # @param [String] enricher
160
+ #
161
+ def enrich_by_enricher(enricher)
162
+ methods = ENRICH_METHODS_BY_ENRICHER[enricher.downcase.to_sym] || []
163
+ methods.each do |method|
164
+ send(method) if respond_to?(method)
165
+ end
166
+ end
167
+
138
168
  private
139
169
 
140
170
  def normalize_as_domain(url_or_domain)
@@ -14,7 +14,7 @@ module Mihari
14
14
  #
15
15
  def build_by_ip(ip)
16
16
  res = Enrichers::Shodan.query(ip)
17
- return if res.nil?
17
+ return [] if res.nil?
18
18
 
19
19
  res.cpes.map { |cpe| new(cpe: cpe) }
20
20
  end
@@ -13,14 +13,7 @@ module Mihari
13
13
  # @return [Array<Mihari::DnsRecord>]
14
14
  #
15
15
  def build_by_domain(domain)
16
- resource_types = [
17
- Resolv::DNS::Resource::IN::A,
18
- Resolv::DNS::Resource::IN::AAAA,
19
- Resolv::DNS::Resource::IN::CNAME,
20
- Resolv::DNS::Resource::IN::TXT,
21
- Resolv::DNS::Resource::IN::NS
22
- ]
23
-
16
+ resource_types = %w[A AAAA CNAME TXT NS]
24
17
  resource_types.map do |resource_type|
25
18
  get_values domain, resource_type
26
19
  rescue Resolv::ResolvError
@@ -31,20 +24,11 @@ module Mihari
31
24
  private
32
25
 
33
26
  def get_values(domain, resource_type)
34
- resources = Resolv::DNS.new.getresources(domain, resource_type)
35
- resource_name = resource_type.to_s.split("::").last
27
+ response = Enrichers::GooglePublicDNS.query(domain, resource_type)
28
+ answers = response.answers || []
36
29
 
37
- resources.filter_map do |resource|
38
- # A, AAAA
39
- if resource.respond_to?(:address)
40
- new(resource: resource_name, value: resource.address.to_s)
41
- # CNAME, NS
42
- elsif resource.respond_to?(:name)
43
- new(resource: resource_name, value: resource.name.to_s)
44
- # TXT
45
- elsif resource.respond_to?(:data)
46
- new(resource: resource_name, value: resource.data.to_s)
47
- end
30
+ answers.filter_map do |answer|
31
+ new(resource: answer.resource_type, value: answer.data)
48
32
  end
49
33
  end
50
34
  end
@@ -14,7 +14,7 @@ module Mihari
14
14
  #
15
15
  def build_by_ip(ip)
16
16
  res = Enrichers::Shodan.query(ip)
17
- return if res.nil?
17
+ return [] if res.nil?
18
18
 
19
19
  res.hostnames.map { |name| new(name: name) }
20
20
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "whois-parser"
4
-
5
3
  module Mihari
6
4
  class WhoisRecord < ActiveRecord::Base
7
5
  belongs_to :artifact
@@ -17,100 +15,7 @@ module Mihari
17
15
  # @return [WhoisRecord, nil]
18
16
  #
19
17
  def build_by_domain(domain)
20
- domain = PublicSuffix.domain(domain)
21
-
22
- # check memo
23
- if @memo.key?(domain)
24
- whois_record = @memo[domain]
25
- # return clone of the record
26
- return whois_record.dup
27
- end
28
-
29
- record = Whois.whois(domain)
30
- parser = record.parser
31
-
32
- return nil if parser.available?
33
-
34
- whois_record = new(
35
- domain: domain,
36
- created_on: get_created_on(parser),
37
- updated_on: get_updated_on(parser),
38
- expires_on: get_expires_on(parser),
39
- registrar: get_registrar(parser),
40
- contacts: get_contacts(parser)
41
- )
42
- # set memo
43
- @memo[domain] = whois_record
44
- whois_record
45
- rescue Whois::Error, Whois::ParserError, Timeout::Error
46
- nil
47
- end
48
-
49
- private
50
-
51
- #
52
- # Get created_on
53
- #
54
- # @param [::Whois::Parser:] parser
55
- #
56
- # @return [Date, nil]
57
- #
58
- def get_created_on(parser)
59
- parser.created_on
60
- rescue ::Whois::AttributeNotImplemented
61
- nil
62
- end
63
-
64
- #
65
- # Get updated_on
66
- #
67
- # @param [::Whois::Parser:] parser
68
- #
69
- # @return [Date, nil]
70
- #
71
- def get_updated_on(parser)
72
- parser.updated_on
73
- rescue ::Whois::AttributeNotImplemented
74
- nil
75
- end
76
-
77
- #
78
- # Get expires_on
79
- #
80
- # @param [::Whois::Parser:] parser
81
- #
82
- # @return [Date, nil]
83
- #
84
- def get_expires_on(parser)
85
- parser.expires_on
86
- rescue ::Whois::AttributeNotImplemented
87
- nil
88
- end
89
-
90
- #
91
- # Get registrar
92
- #
93
- # @param [::Whois::Parser:] parser
94
- #
95
- # @return [Hash, nil]
96
- #
97
- def get_registrar(parser)
98
- parser.registrar&.to_h
99
- rescue ::Whois::AttributeNotImplemented
100
- nil
101
- end
102
-
103
- #
104
- # Get contacts
105
- #
106
- # @param [::Whois::Parser:] parser
107
- #
108
- # @return [Array[Hash], nil]
109
- #
110
- def get_contacts(parser)
111
- parser.contacts.map(&:to_h)
112
- rescue ::Whois::AttributeNotImplemented
113
- nil
18
+ Enrichers::Whois.query domain
114
19
  end
115
20
  end
116
21
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Schemas
5
+ Enricher = Dry::Schema.Params do
6
+ required(:enricher).value(Types::EnricherTypes)
7
+ end
8
+ end
9
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mihari/schemas/analyzer"
4
4
  require "mihari/schemas/emitter"
5
+ require "mihari/schemas/enricher"
5
6
 
6
7
  module Mihari
7
8
  module Schemas
@@ -20,6 +21,8 @@ module Mihari
20
21
 
21
22
  optional(:emitters).value(:array).each { Emitter | MISP | TheHive | Slack | HTTP }
22
23
 
24
+ optional(:enrichers).value(:array).each(Enricher)
25
+
23
26
  optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
24
27
  optional(:disallowed_data_values).value(array[:string]).default([])
25
28
 
@@ -35,6 +38,9 @@ module Mihari
35
38
  emitters = h[:emitters]
36
39
  h[:emitters] = emitters || DEFAULT_EMITTERS
37
40
 
41
+ enrichers = h[:enrichers]
42
+ h[:enrichers] = enrichers || DEFAULT_ENRICHERS
43
+
38
44
  h
39
45
  end
40
46
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Structs
5
+ module GooglePublicDNS
6
+ INT_TYPE_TO_TYPE = {
7
+ 1 => "A",
8
+ 2 => "NS",
9
+ 5 => "CNAME",
10
+ 16 => "TXT",
11
+ 28 => "AAAA"
12
+ }
13
+
14
+ class Answer < Dry::Struct
15
+ attribute :name, Types::String
16
+ attribute :data, Types::String
17
+ attribute :resource_type, Types::String
18
+
19
+ def self.from_dynamic!(d)
20
+ d = Types::Hash[d]
21
+ resource_type = INT_TYPE_TO_TYPE[d.fetch("type")]
22
+ new(
23
+ name: d.fetch("name"),
24
+ data: d.fetch("data"),
25
+ resource_type: resource_type
26
+ )
27
+ end
28
+ end
29
+
30
+ class Response < Dry::Struct
31
+ attribute :answers, Types.Array(Answer)
32
+
33
+ def self.from_dynamic!(d)
34
+ d = Types::Hash[d]
35
+ new(
36
+ answers: d.fetch("Answer", []).map { |x| Answer.from_dynamic!(x) }
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -88,7 +88,7 @@ module Mihari
88
88
 
89
89
  raise RuleValidationError, error_messages.join("\n") if errors?
90
90
 
91
- raise RuleValidationError, "Something wrong with queries or emitters." unless @no_method_error.nil?
91
+ raise RuleValidationError, "Something wrong with queries, emitters or enrichers." unless @no_method_error.nil?
92
92
  end
93
93
 
94
94
  def [](key)
@@ -150,6 +150,7 @@ module Mihari
150
150
  allowed_data_types: self[:allowed_data_types],
151
151
  disallowed_data_values: self[:disallowed_data_values],
152
152
  emitters: self[:emitters],
153
+ enrichers: self[:enrichers],
153
154
  id: id
154
155
  )
155
156
  analyzer.ignore_old_artifacts = self[:ignore_old_artifacts]
@@ -168,6 +169,7 @@ module Mihari
168
169
  #
169
170
  def from_model(model)
170
171
  data = model.data.deep_symbolize_keys
172
+ # set ID if YAML data do not have ID
171
173
  data[:id] = model.id unless data.key?(:id)
172
174
 
173
175
  Structs::Rule::Rule.new(data, model.yaml)
@@ -177,9 +179,13 @@ module Mihari
177
179
  # @param [String] yaml
178
180
  #
179
181
  # @return [Mihari::Structs::Rule::Rule]
182
+ # @param [String, nil] id
180
183
  #
181
- def from_yaml(yaml)
184
+ def from_yaml(yaml, id: nil)
182
185
  data = load_erb_yaml(yaml)
186
+ # set ID if id is given & YAML data do not have ID
187
+ data[:id] = id if !id.nil? && !data.key?(:id)
188
+
183
189
  Structs::Rule::Rule.new(data, yaml)
184
190
  end
185
191
  end
@@ -4,7 +4,7 @@ module Mihari
4
4
  module Structs
5
5
  module VirusTotalIntelligence
6
6
  class ContextAttributes < Dry::Struct
7
- attribute :url, Types.Array(Types::String).optional
7
+ attribute :url, Types::String.optional
8
8
 
9
9
  def self.from_dynamic!(d)
10
10
  d = Types::Hash[d]
@@ -25,7 +25,7 @@ module Mihari
25
25
  when "file"
26
26
  id
27
27
  when "url"
28
- (context_attributes.url || []).first
28
+ context_attributes&.url
29
29
  when "domain"
30
30
  id
31
31
  when "ip_address"
data/lib/mihari/types.rb CHANGED
@@ -21,5 +21,12 @@ module Mihari
21
21
  "database",
22
22
  "webhook"
23
23
  )
24
+
25
+ EnricherTypes = Types::String.enum(
26
+ "whois",
27
+ "ipinfo",
28
+ "shodan",
29
+ "google_public_dns"
30
+ )
24
31
  end
25
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "4.6.0"
4
+ VERSION = "4.7.1"
5
5
  end
@@ -53,6 +53,9 @@ module Mihari
53
53
 
54
54
  Rack::Handler::Puma.run(instance, Port: port, Host: host, Threads: threads, Verbose: verbose) do |_launcher|
55
55
  Launchy.open(url) if ENV["RACK_ENV"] != "development"
56
+ rescue Launchy::CommandNotFoundError
57
+ # ref. https://github.com/ninoseki/mihari/issues/477
58
+ # do nothing
56
59
  end
57
60
  end
58
61
  end
@@ -144,8 +144,7 @@ module Mihari
144
144
  error!({ message: "ID:#{id} is not found" }, 404)
145
145
  end
146
146
 
147
- rule = Structs::Rule::Rule.from_yaml(yaml)
148
- rule.id = id
147
+ rule = Structs::Rule::Rule.from_yaml(yaml, id: id)
149
148
 
150
149
  begin
151
150
  rule.validate!
data/lib/mihari.rb CHANGED
@@ -173,6 +173,7 @@ require "mihari/types"
173
173
  # Structs
174
174
  require "mihari/structs/alert"
175
175
  require "mihari/structs/censys"
176
+ require "mihari/structs/google_public_dns"
176
177
  require "mihari/structs/greynoise"
177
178
  require "mihari/structs/ipinfo"
178
179
  require "mihari/structs/onyphe"
@@ -189,8 +190,10 @@ require "mihari/schemas/rule"
189
190
 
190
191
  # Enrichers
191
192
  require "mihari/enrichers/base"
193
+ require "mihari/enrichers/google_public_dns"
192
194
  require "mihari/enrichers/ipinfo"
193
195
  require "mihari/enrichers/shodan"
196
+ require "mihari/enrichers/whois"
194
197
 
195
198
  # Models
196
199
  require "mihari/models/alert"
data/mihari.gemspec CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  spec.add_development_dependency "bundler", "~> 2.3"
31
31
  spec.add_development_dependency "coveralls_reborn", "~> 0.24"
32
- spec.add_development_dependency "fakefs", "~> 1.4"
32
+ spec.add_development_dependency "fakefs", "~> 1.5"
33
33
  spec.add_development_dependency "mysql2", "~> 0.5"
34
34
  spec.add_development_dependency "overcommit", "~> 0.59"
35
35
  spec.add_development_dependency "pg", "~> 1.3"
@@ -40,7 +40,7 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency "rspec", "~> 3.11"
41
41
  spec.add_development_dependency "simplecov-lcov", "~> 0.8.0"
42
42
  spec.add_development_dependency "standard", "~> 1.12"
43
- spec.add_development_dependency "steep", "~> 0.52"
43
+ spec.add_development_dependency "steep", "~> 1.0"
44
44
  spec.add_development_dependency "timecop", "~> 0.9"
45
45
  spec.add_development_dependency "vcr", "~> 6.1"
46
46
  spec.add_development_dependency "webmock", "~> 3.14"
@@ -58,9 +58,9 @@ Gem::Specification.new do |spec|
58
58
  spec.add_dependency "dry-container", "0.9.0"
59
59
  spec.add_dependency "dry-files", "0.1.0"
60
60
  spec.add_dependency "dry-initializer", "3.1.1"
61
- spec.add_dependency "dry-schema", "1.9.1"
61
+ spec.add_dependency "dry-schema", "1.9.2"
62
62
  spec.add_dependency "dry-struct", "1.4.0"
63
- spec.add_dependency "dry-validation", "1.8.0"
63
+ spec.add_dependency "dry-validation", "1.8.1"
64
64
  spec.add_dependency "email_address", "0.2.3"
65
65
  spec.add_dependency "grape", "1.6.2"
66
66
  spec.add_dependency "grape-entity", "0.10.1"
@@ -76,7 +76,6 @@ Gem::Specification.new do |spec|
76
76
  spec.add_dependency "net-ping", "2.0.8"
77
77
  spec.add_dependency "normalize_country", "0.3.2"
78
78
  spec.add_dependency "onyphe", "2.0.0"
79
- spec.add_dependency "otx_ruby", "0.9.9"
80
79
  spec.add_dependency "parallel", "1.22.1"
81
80
  spec.add_dependency "passive_circl", "0.1.0"
82
81
  spec.add_dependency "passivetotalx", "0.1.1"
@@ -84,16 +83,16 @@ Gem::Specification.new do |spec|
84
83
  spec.add_dependency "public_suffix", "4.0.7"
85
84
  spec.add_dependency "pulsedive", "0.1.5"
86
85
  spec.add_dependency "puma", "5.6.4"
87
- spec.add_dependency "rack", "2.2.3"
86
+ spec.add_dependency "rack", "2.2.3.1"
88
87
  spec.add_dependency "rack-contrib", "2.3.0"
89
88
  spec.add_dependency "rack-cors", "1.1.1"
90
89
  spec.add_dependency "securitytrails", "1.0.0"
91
90
  spec.add_dependency "semantic_logger", "4.11.0"
92
- spec.add_dependency "sentry-ruby", "5.3.0"
91
+ spec.add_dependency "sentry-ruby", "5.3.1"
93
92
  spec.add_dependency "shodanx", "0.2.1"
94
93
  spec.add_dependency "slack-notifier", "2.4.0"
95
94
  spec.add_dependency "spysex", "0.2.0"
96
- spec.add_dependency "sqlite3", "1.4.2"
95
+ spec.add_dependency "sqlite3", "1.4.4"
97
96
  spec.add_dependency "thor", "1.2.1"
98
97
  spec.add_dependency "urlscan", "0.8.0"
99
98
  spec.add_dependency "uuidtools", "2.2.0"
@@ -0,0 +1,18 @@
1
+ module Mihari
2
+ module Enrichers
3
+ class GooglePublicDNS < Base
4
+ # @return [Boolean]
5
+ def valid?: () -> true
6
+
7
+ #
8
+ # Query Google Public DNS
9
+ #
10
+ # @param [String] name
11
+ # @param [String] resource_type
12
+ #
13
+ # @return [Mihari::Structs::Shodan::GooglePublicDNS::Response, nil]
14
+ #
15
+ def self.query: (String name, String resource_type) -> Mihari::Structs::Shodan::GooglePublicDNS::Response?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module Mihari
2
+ module Structs
3
+ module GooglePublicDNS
4
+ INT_TYPE_TO_TYPE: { 1 => "A", 2 => "NS", 5 => "CNAME", 16 => "TXT", 28 => "AAAA" }
5
+
6
+ class Answer < Dry::Struct
7
+ attr_reader name: String
8
+ attr_reader data: String
9
+ attr_reader resource_type: String
10
+
11
+ def self.from_dynamic!: (Hash[(String | Symbol), untyped] d) -> Mihari::Structs::GooglePublicDNS::Answer
12
+ end
13
+
14
+ class Response < Dry::Struct
15
+ attr_reader answers: Array[Mihari::Structs::GooglePublicDNS::Answer]
16
+
17
+ def self.from_dynamic!: (Hash[(String | Symbol), untyped] d) -> Mihari::Structs::GooglePublicDNS::Response
18
+ end
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mihari
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.0
4
+ version: 4.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manabu Niseki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-15 00:00:00.000000000 Z
11
+ date: 2022-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.4'
47
+ version: '1.5'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.4'
54
+ version: '1.5'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: mysql2
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -198,14 +198,14 @@ dependencies:
198
198
  requirements:
199
199
  - - "~>"
200
200
  - !ruby/object:Gem::Version
201
- version: '0.52'
201
+ version: '1.0'
202
202
  type: :development
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
- version: '0.52'
208
+ version: '1.0'
209
209
  - !ruby/object:Gem::Dependency
210
210
  name: timecop
211
211
  requirement: !ruby/object:Gem::Requirement
@@ -436,14 +436,14 @@ dependencies:
436
436
  requirements:
437
437
  - - '='
438
438
  - !ruby/object:Gem::Version
439
- version: 1.9.1
439
+ version: 1.9.2
440
440
  type: :runtime
441
441
  prerelease: false
442
442
  version_requirements: !ruby/object:Gem::Requirement
443
443
  requirements:
444
444
  - - '='
445
445
  - !ruby/object:Gem::Version
446
- version: 1.9.1
446
+ version: 1.9.2
447
447
  - !ruby/object:Gem::Dependency
448
448
  name: dry-struct
449
449
  requirement: !ruby/object:Gem::Requirement
@@ -464,14 +464,14 @@ dependencies:
464
464
  requirements:
465
465
  - - '='
466
466
  - !ruby/object:Gem::Version
467
- version: 1.8.0
467
+ version: 1.8.1
468
468
  type: :runtime
469
469
  prerelease: false
470
470
  version_requirements: !ruby/object:Gem::Requirement
471
471
  requirements:
472
472
  - - '='
473
473
  - !ruby/object:Gem::Version
474
- version: 1.8.0
474
+ version: 1.8.1
475
475
  - !ruby/object:Gem::Dependency
476
476
  name: email_address
477
477
  requirement: !ruby/object:Gem::Requirement
@@ -682,20 +682,6 @@ dependencies:
682
682
  - - '='
683
683
  - !ruby/object:Gem::Version
684
684
  version: 2.0.0
685
- - !ruby/object:Gem::Dependency
686
- name: otx_ruby
687
- requirement: !ruby/object:Gem::Requirement
688
- requirements:
689
- - - '='
690
- - !ruby/object:Gem::Version
691
- version: 0.9.9
692
- type: :runtime
693
- prerelease: false
694
- version_requirements: !ruby/object:Gem::Requirement
695
- requirements:
696
- - - '='
697
- - !ruby/object:Gem::Version
698
- version: 0.9.9
699
685
  - !ruby/object:Gem::Dependency
700
686
  name: parallel
701
687
  requirement: !ruby/object:Gem::Requirement
@@ -800,14 +786,14 @@ dependencies:
800
786
  requirements:
801
787
  - - '='
802
788
  - !ruby/object:Gem::Version
803
- version: 2.2.3
789
+ version: 2.2.3.1
804
790
  type: :runtime
805
791
  prerelease: false
806
792
  version_requirements: !ruby/object:Gem::Requirement
807
793
  requirements:
808
794
  - - '='
809
795
  - !ruby/object:Gem::Version
810
- version: 2.2.3
796
+ version: 2.2.3.1
811
797
  - !ruby/object:Gem::Dependency
812
798
  name: rack-contrib
813
799
  requirement: !ruby/object:Gem::Requirement
@@ -870,14 +856,14 @@ dependencies:
870
856
  requirements:
871
857
  - - '='
872
858
  - !ruby/object:Gem::Version
873
- version: 5.3.0
859
+ version: 5.3.1
874
860
  type: :runtime
875
861
  prerelease: false
876
862
  version_requirements: !ruby/object:Gem::Requirement
877
863
  requirements:
878
864
  - - '='
879
865
  - !ruby/object:Gem::Version
880
- version: 5.3.0
866
+ version: 5.3.1
881
867
  - !ruby/object:Gem::Dependency
882
868
  name: shodanx
883
869
  requirement: !ruby/object:Gem::Requirement
@@ -926,14 +912,14 @@ dependencies:
926
912
  requirements:
927
913
  - - '='
928
914
  - !ruby/object:Gem::Version
929
- version: 1.4.2
915
+ version: 1.4.4
930
916
  type: :runtime
931
917
  prerelease: false
932
918
  version_requirements: !ruby/object:Gem::Requirement
933
919
  requirements:
934
920
  - - '='
935
921
  - !ruby/object:Gem::Version
936
- version: 1.4.2
922
+ version: 1.4.4
937
923
  - !ruby/object:Gem::Dependency
938
924
  name: thor
939
925
  requirement: !ruby/object:Gem::Requirement
@@ -1074,6 +1060,7 @@ files:
1074
1060
  - lib/mihari/analyzers/binaryedge.rb
1075
1061
  - lib/mihari/analyzers/censys.rb
1076
1062
  - lib/mihari/analyzers/circl.rb
1063
+ - lib/mihari/analyzers/clients/otx.rb
1077
1064
  - lib/mihari/analyzers/crtsh.rb
1078
1065
  - lib/mihari/analyzers/dnpedia.rb
1079
1066
  - lib/mihari/analyzers/dnstwister.rb
@@ -1110,8 +1097,10 @@ files:
1110
1097
  - lib/mihari/emitters/the_hive.rb
1111
1098
  - lib/mihari/emitters/webhook.rb
1112
1099
  - lib/mihari/enrichers/base.rb
1100
+ - lib/mihari/enrichers/google_public_dns.rb
1113
1101
  - lib/mihari/enrichers/ipinfo.rb
1114
1102
  - lib/mihari/enrichers/shodan.rb
1103
+ - lib/mihari/enrichers/whois.rb
1115
1104
  - lib/mihari/entities/alert.rb
1116
1105
  - lib/mihari/entities/artifact.rb
1117
1106
  - lib/mihari/entities/autonomous_system.rb
@@ -1153,11 +1142,13 @@ files:
1153
1142
  - lib/mihari/models/whois.rb
1154
1143
  - lib/mihari/schemas/analyzer.rb
1155
1144
  - lib/mihari/schemas/emitter.rb
1145
+ - lib/mihari/schemas/enricher.rb
1156
1146
  - lib/mihari/schemas/macros.rb
1157
1147
  - lib/mihari/schemas/rule.rb
1158
1148
  - lib/mihari/status.rb
1159
1149
  - lib/mihari/structs/alert.rb
1160
1150
  - lib/mihari/structs/censys.rb
1151
+ - lib/mihari/structs/google_public_dns.rb
1161
1152
  - lib/mihari/structs/greynoise.rb
1162
1153
  - lib/mihari/structs/ipinfo.rb
1163
1154
  - lib/mihari/structs/onyphe.rb
@@ -1243,6 +1234,7 @@ files:
1243
1234
  - sig/lib/mihari/emitters/the_hive.rbs
1244
1235
  - sig/lib/mihari/emitters/webhook.rbs
1245
1236
  - sig/lib/mihari/enrichers/base.rbs
1237
+ - sig/lib/mihari/enrichers/google_public_dns.rbs
1246
1238
  - sig/lib/mihari/enrichers/ipinfo.rbs
1247
1239
  - sig/lib/mihari/errors.rbs
1248
1240
  - sig/lib/mihari/feed/parser.rbs
@@ -1272,6 +1264,7 @@ files:
1272
1264
  - sig/lib/mihari/status.rbs
1273
1265
  - sig/lib/mihari/structs/alert.rbs
1274
1266
  - sig/lib/mihari/structs/censys.rbs
1267
+ - sig/lib/mihari/structs/google_public_dns.rbs
1275
1268
  - sig/lib/mihari/structs/greynoise.rbs
1276
1269
  - sig/lib/mihari/structs/ipinfo.rbs
1277
1270
  - sig/lib/mihari/structs/onyphe.rbs