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 +4 -4
- data/lib/mihari/analyzers/clients/otx.rb +36 -0
- data/lib/mihari/analyzers/otx.rb +19 -11
- data/lib/mihari/analyzers/rule.rb +17 -0
- data/lib/mihari/constants.rb +2 -0
- data/lib/mihari/enrichers/google_public_dns.rb +36 -0
- data/lib/mihari/enrichers/whois.rb +126 -0
- data/lib/mihari/http.rb +2 -2
- data/lib/mihari/models/artifact.rb +30 -0
- data/lib/mihari/models/cpe.rb +1 -1
- data/lib/mihari/models/dns.rb +5 -21
- data/lib/mihari/models/reverse_dns.rb +1 -1
- data/lib/mihari/models/whois.rb +1 -96
- data/lib/mihari/schemas/enricher.rb +9 -0
- data/lib/mihari/schemas/rule.rb +6 -0
- data/lib/mihari/structs/google_public_dns.rb +42 -0
- data/lib/mihari/structs/rule.rb +8 -2
- data/lib/mihari/structs/virustotal_intelligence.rb +2 -2
- data/lib/mihari/types.rb +7 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/app.rb +3 -0
- data/lib/mihari/web/endpoints/rules.rb +1 -2
- data/lib/mihari.rb +3 -0
- data/mihari.gemspec +7 -8
- data/sig/lib/mihari/enrichers/google_public_dns.rbs +18 -0
- data/sig/lib/mihari/structs/google_public_dns.rbs +21 -0
- metadata +23 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38f68ec091b2469095f321f104c1e238599c56afa7f55763920cfeef0df03ca0
|
4
|
+
data.tar.gz: 625bbd99f92e4af768b76db15ab72cd53dba6a04150acc3ddfe7feb7d1a82bf7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/mihari/analyzers/otx.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
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
|
38
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
#
|
data/lib/mihari/constants.rb
CHANGED
@@ -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: {},
|
48
|
-
client = new(uri, headers: headers, 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)
|
data/lib/mihari/models/cpe.rb
CHANGED
data/lib/mihari/models/dns.rb
CHANGED
@@ -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
|
-
|
35
|
-
|
27
|
+
response = Enrichers::GooglePublicDNS.query(domain, resource_type)
|
28
|
+
answers = response.answers || []
|
36
29
|
|
37
|
-
|
38
|
-
|
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
|
data/lib/mihari/models/whois.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/mihari/schemas/rule.rb
CHANGED
@@ -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
|
data/lib/mihari/structs/rule.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
28
|
+
context_attributes&.url
|
29
29
|
when "domain"
|
30
30
|
id
|
31
31
|
when "ip_address"
|
data/lib/mihari/types.rb
CHANGED
data/lib/mihari/version.rb
CHANGED
data/lib/mihari/web/app.rb
CHANGED
@@ -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
|
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.
|
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
|
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.
|
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.
|
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.
|
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.
|
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.
|
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-
|
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.
|
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.
|
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
|
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
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|