mihari 4.6.0 → 4.7.1

Sign up to get free protection for your applications and to get access to all the features.
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