mihari 5.4.2 → 5.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/frontend/package-lock.json +2399 -1504
- data/frontend/package.json +22 -22
- data/lib/mihari/analyzers/base.rb +25 -14
- data/lib/mihari/analyzers/binaryedge.rb +2 -48
- data/lib/mihari/analyzers/censys.rb +4 -20
- data/lib/mihari/analyzers/circl.rb +3 -27
- data/lib/mihari/analyzers/crtsh.rb +2 -17
- data/lib/mihari/analyzers/dnstwister.rb +2 -4
- data/lib/mihari/analyzers/greynoise.rb +5 -4
- data/lib/mihari/analyzers/hunterhow.rb +8 -23
- data/lib/mihari/analyzers/onyphe.rb +5 -39
- data/lib/mihari/analyzers/otx.rb +3 -39
- data/lib/mihari/analyzers/passivetotal.rb +4 -42
- data/lib/mihari/analyzers/pulsedive.rb +1 -1
- data/lib/mihari/analyzers/rule.rb +18 -13
- data/lib/mihari/analyzers/securitytrails.rb +4 -42
- data/lib/mihari/analyzers/shodan.rb +7 -39
- data/lib/mihari/analyzers/urlscan.rb +3 -39
- data/lib/mihari/analyzers/virustotal.rb +1 -1
- data/lib/mihari/analyzers/virustotal_intelligence.rb +2 -25
- data/lib/mihari/analyzers/zoomeye.rb +18 -84
- data/lib/mihari/clients/base.rb +9 -1
- data/lib/mihari/clients/binaryedge.rb +26 -4
- data/lib/mihari/clients/censys.rb +32 -2
- data/lib/mihari/clients/circl.rb +28 -1
- data/lib/mihari/clients/crtsh.rb +7 -2
- data/lib/mihari/clients/dnstwister.rb +4 -2
- data/lib/mihari/clients/greynoise.rb +31 -4
- data/lib/mihari/clients/hunterhow.rb +41 -3
- data/lib/mihari/clients/onyphe.rb +25 -3
- data/lib/mihari/clients/otx.rb +40 -0
- data/lib/mihari/clients/passivetotal.rb +33 -15
- data/lib/mihari/clients/publsedive.rb +1 -1
- data/lib/mihari/clients/securitytrails.rb +44 -0
- data/lib/mihari/clients/shodan.rb +31 -3
- data/lib/mihari/clients/urlscan.rb +32 -6
- data/lib/mihari/clients/virustotal.rb +29 -4
- data/lib/mihari/clients/zoomeye.rb +53 -2
- data/lib/mihari/commands/alert.rb +42 -13
- data/lib/mihari/commands/rule.rb +11 -7
- data/lib/mihari/commands/search.rb +54 -22
- data/lib/mihari/commands/web.rb +1 -1
- data/lib/mihari/config.rb +6 -1
- data/lib/mihari/emitters/base.rb +9 -3
- data/lib/mihari/emitters/slack.rb +1 -1
- data/lib/mihari/enrichers/base.rb +13 -0
- data/lib/mihari/enrichers/google_public_dns.rb +16 -1
- data/lib/mihari/enrichers/ipinfo.rb +9 -13
- data/lib/mihari/enrichers/shodan.rb +1 -2
- data/lib/mihari/enrichers/whois.rb +2 -2
- data/lib/mihari/errors.rb +16 -10
- data/lib/mihari/feed/parser.rb +2 -2
- data/lib/mihari/models/artifact.rb +1 -1
- data/lib/mihari/models/autonomous_system.rb +11 -5
- data/lib/mihari/models/cpe.rb +10 -4
- data/lib/mihari/models/dns.rb +11 -16
- data/lib/mihari/models/geolocation.rb +11 -5
- data/lib/mihari/models/port.rb +10 -4
- data/lib/mihari/models/reverse_dns.rb +10 -4
- data/lib/mihari/models/whois.rb +4 -1
- data/lib/mihari/schemas/analyzer.rb +1 -0
- data/lib/mihari/services/alert_builder.rb +43 -0
- data/lib/mihari/services/alert_proxy.rb +7 -25
- data/lib/mihari/services/alert_runner.rb +9 -0
- data/lib/mihari/services/rule_builder.rb +47 -0
- data/lib/mihari/services/rule_proxy.rb +5 -61
- data/lib/mihari/services/rule_runner.rb +9 -4
- data/lib/mihari/structs/binaryedge.rb +89 -0
- data/lib/mihari/structs/censys.rb +11 -11
- data/lib/mihari/structs/greynoise.rb +17 -8
- data/lib/mihari/structs/onyphe.rb +7 -7
- data/lib/mihari/structs/shodan.rb +7 -6
- data/lib/mihari/structs/urlscan.rb +4 -6
- data/lib/mihari/structs/virustotal_intelligence.rb +4 -6
- data/lib/mihari/type_checker.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/alerts.rb +33 -15
- data/lib/mihari/web/endpoints/artifacts.rb +53 -25
- data/lib/mihari/web/endpoints/configs.rb +2 -2
- data/lib/mihari/web/endpoints/ip_addresses.rb +3 -5
- data/lib/mihari/web/endpoints/rules.rb +97 -71
- data/lib/mihari/web/endpoints/tags.rb +15 -5
- data/lib/mihari/web/public/assets/index-ef33a6cd.js +1738 -0
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +419 -382
- data/lib/mihari.rb +4 -0
- data/mihari.gemspec +10 -9
- metadata +38 -21
- data/lib/mihari/web/public/assets/index-4d7eda9f.js +0 -1738
@@ -60,9 +60,16 @@ module Mihari
|
|
60
60
|
# @return [Array<Mihari::Artifact>]
|
61
61
|
#
|
62
62
|
def artifacts
|
63
|
-
analyzers.flat_map
|
64
|
-
|
65
|
-
|
63
|
+
analyzers.flat_map do |analyzer|
|
64
|
+
result = analyzer.result
|
65
|
+
|
66
|
+
raise result.failure if result.failure? && !analyzer.ignore_error?
|
67
|
+
|
68
|
+
artifacts = result.value!
|
69
|
+
artifacts.map do |artifact|
|
70
|
+
artifact.rule_id = rule.id
|
71
|
+
artifact
|
72
|
+
end
|
66
73
|
end
|
67
74
|
end
|
68
75
|
|
@@ -113,11 +120,12 @@ module Mihari
|
|
113
120
|
return [] if enriched_artifacts.empty?
|
114
121
|
|
115
122
|
Parallel.map(valid_emitters) do |emitter|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
123
|
+
result = emitter.result
|
124
|
+
|
125
|
+
Mihari.logger.info "Emission by #{emitter.class} is failed: #{result.failure}" if result.failure?
|
126
|
+
Mihari.logger.info "Emission by #{emitter.class} is succeeded" if result.success?
|
127
|
+
|
128
|
+
result.value_or nil
|
121
129
|
end.compact
|
122
130
|
end
|
123
131
|
|
@@ -127,9 +135,6 @@ module Mihari
|
|
127
135
|
# @return [Mihari::Alert, nil]
|
128
136
|
#
|
129
137
|
def run
|
130
|
-
# memoize enriched artifacts
|
131
|
-
enriched_artifacts
|
132
|
-
|
133
138
|
alert_or_something = bulk_emit
|
134
139
|
# returns Mihari::Alert created by the database emitter
|
135
140
|
alert_or_something.find { |res| res.is_a?(Mihari::Alert) }
|
@@ -167,7 +172,7 @@ module Mihari
|
|
167
172
|
# @return [Array<Mihari::Analyzers::Base>]
|
168
173
|
#
|
169
174
|
def analyzers
|
170
|
-
|
175
|
+
rule.queries.map do |query_params|
|
171
176
|
analyzer_name = query_params[:analyzer]
|
172
177
|
klass = get_analyzer_class(analyzer_name)
|
173
178
|
klass.from_query(query_params)
|
@@ -207,7 +212,7 @@ module Mihari
|
|
207
212
|
# @return [Array<Mihari::Emitters::Base>]
|
208
213
|
#
|
209
214
|
def valid_emitters
|
210
|
-
|
215
|
+
emitters.select(&:valid?)
|
211
216
|
end
|
212
217
|
|
213
218
|
#
|
@@ -30,13 +30,13 @@ module Mihari
|
|
30
30
|
def artifacts
|
31
31
|
case type
|
32
32
|
when "domain"
|
33
|
-
domain_search
|
33
|
+
client.domain_search query
|
34
34
|
when "ip"
|
35
|
-
ip_search
|
35
|
+
client.ip_search query
|
36
36
|
when "mail"
|
37
|
-
mail_search
|
37
|
+
client.mail_search query
|
38
38
|
else
|
39
|
-
raise
|
39
|
+
raise ValueError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -58,44 +58,6 @@ module Mihari
|
|
58
58
|
def valid_type?
|
59
59
|
%w[ip domain mail].include? type
|
60
60
|
end
|
61
|
-
|
62
|
-
#
|
63
|
-
# Domain search
|
64
|
-
#
|
65
|
-
# @return [Array<String>]
|
66
|
-
#
|
67
|
-
def domain_search
|
68
|
-
records = client.get_all_dns_history(query, type: "a")
|
69
|
-
records.map do |record|
|
70
|
-
(record["values"] || []).map { |value| value["ip"] }
|
71
|
-
end.flatten.compact.uniq
|
72
|
-
end
|
73
|
-
|
74
|
-
#
|
75
|
-
# IP search
|
76
|
-
#
|
77
|
-
# @return [Array<Mihari::Artifact>]
|
78
|
-
#
|
79
|
-
def ip_search
|
80
|
-
records = client.search_by_ip(query)
|
81
|
-
records.filter_map do |record|
|
82
|
-
data = record["hostname"]
|
83
|
-
Artifact.new(data: data, source: source, metadata: record)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
#
|
88
|
-
# Mail search
|
89
|
-
#
|
90
|
-
# @return [Array<String>]
|
91
|
-
#
|
92
|
-
def mail_search
|
93
|
-
records = client.search_by_mail(query)
|
94
|
-
records.filter_map do |record|
|
95
|
-
data = record["hostname"]
|
96
|
-
Artifact.new(data: data, source: source, metadata: record)
|
97
|
-
end
|
98
|
-
end
|
99
61
|
end
|
100
62
|
end
|
101
63
|
end
|
@@ -18,10 +18,10 @@ module Mihari
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def artifacts
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
client.search_with_pagination(
|
22
|
+
query,
|
23
|
+
pagination_limit: pagination_limit
|
24
|
+
).map(&:artifacts).flatten.uniq(&:data)
|
25
25
|
end
|
26
26
|
|
27
27
|
def configuration_keys
|
@@ -30,43 +30,11 @@ module Mihari
|
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
PAGE_SIZE = 100
|
34
|
-
|
35
|
-
def client
|
36
|
-
@client ||= Clients::Shodan.new(api_key: api_key)
|
37
|
-
end
|
38
|
-
|
39
|
-
#
|
40
|
-
# Search with pagination
|
41
|
-
#
|
42
|
-
# @param [Integer] page
|
43
|
-
#
|
44
|
-
# @return [Structs::Shodan::Result]
|
45
|
-
#
|
46
|
-
def search_with_page(page: 1)
|
47
|
-
client.search(query, page: page)
|
48
|
-
end
|
49
|
-
|
50
33
|
#
|
51
|
-
#
|
34
|
+
# @return [Clients::Shodan]
|
52
35
|
#
|
53
|
-
|
54
|
-
|
55
|
-
def search
|
56
|
-
responses = []
|
57
|
-
(1..pagination_limit).each do |page|
|
58
|
-
res = search_with_page(page: page)
|
59
|
-
responses << res
|
60
|
-
break if res.total <= page * PAGE_SIZE
|
61
|
-
|
62
|
-
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
63
|
-
sleep_interval
|
64
|
-
rescue JSON::ParserError
|
65
|
-
# ignore JSON::ParserError
|
66
|
-
# ref. https://github.com/ninoseki/mihari/issues/197
|
67
|
-
next
|
68
|
-
end
|
69
|
-
responses
|
36
|
+
def client
|
37
|
+
@client ||= Clients::Shodan.new(api_key: api_key, interval: interval)
|
70
38
|
end
|
71
39
|
end
|
72
40
|
end
|
@@ -4,7 +4,6 @@ module Mihari
|
|
4
4
|
module Analyzers
|
5
5
|
class Urlscan < Base
|
6
6
|
SUPPORTED_DATA_TYPES = %w[url domain ip].freeze
|
7
|
-
SIZE = 1000
|
8
7
|
|
9
8
|
# @return [String, nil]
|
10
9
|
attr_reader :api_key
|
@@ -26,13 +25,12 @@ module Mihari
|
|
26
25
|
|
27
26
|
return if valid_allowed_data_types?
|
28
27
|
|
29
|
-
raise
|
28
|
+
raise ValueError, "allowed_data_types should be any of url, domain and ip."
|
30
29
|
end
|
31
30
|
|
32
31
|
def artifacts
|
33
|
-
responses = search
|
34
32
|
# @type [Array<Mihari::Artifact>]
|
35
|
-
artifacts =
|
33
|
+
artifacts = client.search_with_pagination(query, pagination_limit: pagination_limit).map(&:artifacts).flatten
|
36
34
|
|
37
35
|
artifacts.select do |artifact|
|
38
36
|
allowed_data_types.include? artifact.data_type
|
@@ -46,41 +44,7 @@ module Mihari
|
|
46
44
|
private
|
47
45
|
|
48
46
|
def client
|
49
|
-
@client ||= Clients::UrlScan.new(api_key: api_key)
|
50
|
-
end
|
51
|
-
|
52
|
-
#
|
53
|
-
# Search with search_after option
|
54
|
-
#
|
55
|
-
# @return [Structs::Urlscan::Response]
|
56
|
-
#
|
57
|
-
def search_with_search_after(search_after: nil)
|
58
|
-
res = client.search(query, size: SIZE, search_after: search_after)
|
59
|
-
Structs::Urlscan::Response.from_dynamic! res
|
60
|
-
end
|
61
|
-
|
62
|
-
#
|
63
|
-
# Search
|
64
|
-
#
|
65
|
-
# @return [Array<Structs::Urlscan::Response>]
|
66
|
-
#
|
67
|
-
def search
|
68
|
-
responses = []
|
69
|
-
|
70
|
-
search_after = nil
|
71
|
-
pagination_limit.times do
|
72
|
-
res = search_with_search_after(search_after: search_after)
|
73
|
-
responses << res
|
74
|
-
|
75
|
-
break if res.results.length < SIZE
|
76
|
-
|
77
|
-
search_after = res.results.last.sort.join(",")
|
78
|
-
|
79
|
-
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
80
|
-
sleep_interval
|
81
|
-
end
|
82
|
-
|
83
|
-
responses
|
47
|
+
@client ||= Clients::UrlScan.new(api_key: api_key, interval: interval)
|
84
48
|
end
|
85
49
|
|
86
50
|
#
|
@@ -31,7 +31,7 @@ module Mihari
|
|
31
31
|
when "ip"
|
32
32
|
ip_search
|
33
33
|
else
|
34
|
-
raise
|
34
|
+
raise ValueError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -18,7 +18,7 @@ module Mihari
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def artifacts
|
21
|
-
|
21
|
+
client.intel_search_with_pagination(query, pagination_limit: pagination_limit).map(&:artifacts).flatten
|
22
22
|
end
|
23
23
|
|
24
24
|
def configuration_keys
|
@@ -33,30 +33,7 @@ module Mihari
|
|
33
33
|
# @return [::VirusTotal::API]
|
34
34
|
#
|
35
35
|
def client
|
36
|
-
@client = Clients::VirusTotal.new(api_key: api_key)
|
37
|
-
end
|
38
|
-
|
39
|
-
#
|
40
|
-
# Search with cursor
|
41
|
-
#
|
42
|
-
# @return [Array<Structs::VirusTotalIntelligence::Response>]
|
43
|
-
#
|
44
|
-
def search_with_cursor
|
45
|
-
cursor = nil
|
46
|
-
responses = []
|
47
|
-
|
48
|
-
pagination_limit.times do
|
49
|
-
response = Structs::VirusTotalIntelligence::Response.from_dynamic!(client.intel_search(query,
|
50
|
-
cursor: cursor))
|
51
|
-
responses << response
|
52
|
-
break if response.meta.cursor.nil?
|
53
|
-
|
54
|
-
cursor = response.meta.cursor
|
55
|
-
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
56
|
-
sleep_interval
|
57
|
-
end
|
58
|
-
|
59
|
-
responses
|
36
|
+
@client = Clients::VirusTotal.new(api_key: api_key, interval: interval)
|
60
37
|
end
|
61
38
|
end
|
62
39
|
end
|
@@ -25,11 +25,15 @@ module Mihari
|
|
25
25
|
def artifacts
|
26
26
|
case type
|
27
27
|
when "host"
|
28
|
-
|
28
|
+
client.host_search_with_pagination(query).map do |res|
|
29
|
+
convert(res)
|
30
|
+
end.flatten
|
29
31
|
when "web"
|
30
|
-
|
32
|
+
client.web_search_with_pagination(query).map do |res|
|
33
|
+
convert(res)
|
34
|
+
end.flatten
|
31
35
|
else
|
32
|
-
raise
|
36
|
+
raise ValueError, "#{type} type is not supported." unless valid_type?
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
@@ -39,8 +43,6 @@ module Mihari
|
|
39
43
|
|
40
44
|
private
|
41
45
|
|
42
|
-
PAGE_SIZE = 10
|
43
|
-
|
44
46
|
#
|
45
47
|
# Check whether a type is valid or not
|
46
48
|
#
|
@@ -51,95 +53,27 @@ module Mihari
|
|
51
53
|
end
|
52
54
|
|
53
55
|
def client
|
54
|
-
@client ||= Clients::ZoomEye.new(api_key: api_key)
|
56
|
+
@client ||= Clients::ZoomEye.new(api_key: api_key, interval: interval)
|
55
57
|
end
|
56
58
|
|
57
59
|
#
|
58
60
|
# Convert responses into an array of String
|
59
61
|
#
|
60
|
-
# @param [
|
62
|
+
# @param [Hash] response
|
61
63
|
#
|
62
64
|
# @return [Array<Mihari::Artifact>]
|
63
65
|
#
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
data = match["ip"]
|
66
|
+
def convert(res)
|
67
|
+
matches = res["matches"] || []
|
68
|
+
matches.map do |match|
|
69
|
+
data = match["ip"]
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
71
|
+
if data.is_a?(Array)
|
72
|
+
data.map { |d| Artifact.new(data: d, source: source, metadata: match) }
|
73
|
+
else
|
74
|
+
Artifact.new(data: data, source: source, metadata: match)
|
75
75
|
end
|
76
|
-
end.flatten
|
77
|
-
end
|
78
|
-
|
79
|
-
#
|
80
|
-
# Host search
|
81
|
-
#
|
82
|
-
# @param [String] query
|
83
|
-
# @param [Integer] page
|
84
|
-
#
|
85
|
-
# @return [Hash, nil]
|
86
|
-
#
|
87
|
-
def _host_search(query, page: 1)
|
88
|
-
client.host_search(query, page: page)
|
89
|
-
end
|
90
|
-
|
91
|
-
#
|
92
|
-
# Host search
|
93
|
-
#
|
94
|
-
# @return [Array<String>]
|
95
|
-
#
|
96
|
-
def host_search
|
97
|
-
responses = []
|
98
|
-
(1..pagination_limit).each do |page|
|
99
|
-
res = _host_search(query, page: page)
|
100
|
-
break unless res
|
101
|
-
|
102
|
-
total = res["total"].to_i
|
103
|
-
responses << res
|
104
|
-
break if total <= page * PAGE_SIZE
|
105
|
-
|
106
|
-
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
107
|
-
sleep_interval
|
108
|
-
end
|
109
|
-
convert_responses responses.compact
|
110
|
-
end
|
111
|
-
|
112
|
-
#
|
113
|
-
# Web search
|
114
|
-
#
|
115
|
-
# @param [String] query
|
116
|
-
# @param [Integer] page
|
117
|
-
#
|
118
|
-
# @return [Hash, nil]
|
119
|
-
#
|
120
|
-
def _web_search(query, page: 1)
|
121
|
-
client.web_search(query, page: page)
|
122
|
-
end
|
123
|
-
|
124
|
-
#
|
125
|
-
# Web search
|
126
|
-
#
|
127
|
-
# @return [Array<String>]
|
128
|
-
#
|
129
|
-
def web_search
|
130
|
-
responses = []
|
131
|
-
(1..pagination_limit).each do |page|
|
132
|
-
res = _web_search(query, page: page)
|
133
|
-
break unless res
|
134
|
-
|
135
|
-
total = res["total"].to_i
|
136
|
-
responses << res
|
137
|
-
break if total <= page * PAGE_SIZE
|
138
|
-
|
139
|
-
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
140
|
-
sleep_interval
|
141
|
-
end
|
142
|
-
convert_responses responses.compact
|
76
|
+
end.flatten
|
143
77
|
end
|
144
78
|
end
|
145
79
|
end
|
data/lib/mihari/clients/base.rb
CHANGED
@@ -9,17 +9,25 @@ module Mihari
|
|
9
9
|
# @return [Hash]
|
10
10
|
attr_reader :headers
|
11
11
|
|
12
|
+
# @return [Integer, nil]
|
13
|
+
attr_reader :interval
|
14
|
+
|
12
15
|
#
|
13
16
|
# @param [String] base_url
|
14
17
|
# @param [Hash] headers
|
15
18
|
#
|
16
|
-
def initialize(base_url, headers: {})
|
19
|
+
def initialize(base_url, headers: {}, interval: nil)
|
17
20
|
@base_url = base_url
|
18
21
|
@headers = headers || {}
|
22
|
+
@interval = interval
|
19
23
|
end
|
20
24
|
|
21
25
|
private
|
22
26
|
|
27
|
+
def sleep_interval
|
28
|
+
sleep(interval) if interval
|
29
|
+
end
|
30
|
+
|
23
31
|
#
|
24
32
|
# @param [String] path
|
25
33
|
#
|
@@ -7,13 +7,14 @@ module Mihari
|
|
7
7
|
# @param [String] base_url
|
8
8
|
# @param [String, nil] api_key
|
9
9
|
# @param [Hash] headers
|
10
|
+
# @param [Integer.nil ] interval
|
10
11
|
#
|
11
|
-
def initialize(base_url = "https://api.binaryedge.io/v2", api_key:, headers: {})
|
12
|
+
def initialize(base_url = "https://api.binaryedge.io/v2", api_key:, headers: {}, interval: nil)
|
12
13
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
13
14
|
|
14
15
|
headers["x-key"] = api_key
|
15
16
|
|
16
|
-
super(base_url, headers: headers)
|
17
|
+
super(base_url, headers: headers, interval: interval)
|
17
18
|
end
|
18
19
|
|
19
20
|
#
|
@@ -21,7 +22,7 @@ module Mihari
|
|
21
22
|
# @param [Integer] page Default 1, Maximum: 500
|
22
23
|
# @param [Integer, nil] only_ips If selected, only output IP addresses, ports and protocols.
|
23
24
|
#
|
24
|
-
# @return [
|
25
|
+
# @return [Structs::BinaryEdge::Response]
|
25
26
|
#
|
26
27
|
def search(query, page: 1, only_ips: nil)
|
27
28
|
params = {
|
@@ -31,7 +32,28 @@ module Mihari
|
|
31
32
|
}.compact
|
32
33
|
|
33
34
|
res = get("/query/search", params: params)
|
34
|
-
JSON.parse(res.body.to_s)
|
35
|
+
Structs::BinaryEdge::Response.from_dynamic! JSON.parse(res.body.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# @param [String] query
|
40
|
+
# @param [Integer, nil] only_ips
|
41
|
+
# @param [Integer] pagination_limit
|
42
|
+
#
|
43
|
+
# @return [Enumerable<Structs::BinaryEdge::Response.>]
|
44
|
+
#
|
45
|
+
def search_with_pagination(query, only_ips: nil, pagination_limit: Mihari.config.pagination_limit)
|
46
|
+
Enumerator.new do |y|
|
47
|
+
(1..pagination_limit).each do |page|
|
48
|
+
res = search(query, page: page, only_ips: only_ips)
|
49
|
+
|
50
|
+
y.yield res
|
51
|
+
|
52
|
+
break if res.events.length < res.pagesize
|
53
|
+
|
54
|
+
sleep_interval
|
55
|
+
end
|
56
|
+
end
|
35
57
|
end
|
36
58
|
end
|
37
59
|
end
|
@@ -10,14 +10,15 @@ module Mihari
|
|
10
10
|
# @param [String, nil] id
|
11
11
|
# @param [String, nil] secret
|
12
12
|
# @param [Hash] headers
|
13
|
+
# @param [Integer, nil] interval
|
13
14
|
#
|
14
|
-
def initialize(base_url = "https://search.censys.io", id:, secret:, headers: {})
|
15
|
+
def initialize(base_url = "https://search.censys.io", id:, secret:, headers: {}, interval: nil)
|
15
16
|
raise(ArgumentError, "'id' argument is required") if id.nil?
|
16
17
|
raise(ArgumentError, "'secret' argument is required") if secret.nil?
|
17
18
|
|
18
19
|
headers["authorization"] = "Basic #{Base64.strict_encode64("#{id}:#{secret}")}"
|
19
20
|
|
20
|
-
super(base_url, headers: headers)
|
21
|
+
super(base_url, headers: headers, interval: interval)
|
21
22
|
end
|
22
23
|
|
23
24
|
#
|
@@ -37,6 +38,35 @@ module Mihari
|
|
37
38
|
res = get("/api/v2/hosts/search", params: params)
|
38
39
|
Structs::Censys::Response.from_dynamic! JSON.parse(res.body.to_s)
|
39
40
|
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @param [String] query
|
44
|
+
# @param [Integer, nil] per_page
|
45
|
+
# @param [Integer] pagination_limit
|
46
|
+
#
|
47
|
+
# @return [Enumerable<Structs::Censys::Response>]
|
48
|
+
#
|
49
|
+
def search_with_pagination(query, per_page: nil, pagination_limit: Mihari.config.pagination_limit)
|
50
|
+
cursor = nil
|
51
|
+
|
52
|
+
Enumerator.new do |y|
|
53
|
+
pagination_limit.times do
|
54
|
+
res = search(query, per_page: per_page, cursor: cursor)
|
55
|
+
|
56
|
+
y.yield res
|
57
|
+
|
58
|
+
cursor = res.result.links.next
|
59
|
+
# NOTE: Censys's search API is unstable recently
|
60
|
+
# it may returns empty links or empty string cursors
|
61
|
+
# - Empty links: "links": {}
|
62
|
+
# - Empty cursors: "links": { "next": "", "prev": "" }
|
63
|
+
# So it needs to check both cases
|
64
|
+
break if cursor.nil? || cursor.empty?
|
65
|
+
|
66
|
+
sleep_interval
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
40
70
|
end
|
41
71
|
end
|
42
72
|
end
|
data/lib/mihari/clients/circl.rb
CHANGED
@@ -20,6 +20,34 @@ module Mihari
|
|
20
20
|
super(base_url, headers: headers)
|
21
21
|
end
|
22
22
|
|
23
|
+
#
|
24
|
+
# Passive DNS search
|
25
|
+
#
|
26
|
+
# @param [String] query
|
27
|
+
#
|
28
|
+
# @return [Array<String>]
|
29
|
+
#
|
30
|
+
def passive_dns_search(query)
|
31
|
+
results = dns_query(query)
|
32
|
+
results.filter_map do |result|
|
33
|
+
type = result["rrtype"]
|
34
|
+
(type == "A") ? result["rdata"] : nil
|
35
|
+
end.uniq
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Passive SSL search
|
40
|
+
#
|
41
|
+
# @param [String] query
|
42
|
+
#
|
43
|
+
# @return [Array<String>]
|
44
|
+
#
|
45
|
+
def passive_ssl_search(query)
|
46
|
+
result = ssl_cquery(query)
|
47
|
+
seen = result["seen"] || []
|
48
|
+
seen.uniq
|
49
|
+
end
|
50
|
+
|
23
51
|
#
|
24
52
|
# @param [String] query
|
25
53
|
#
|
@@ -40,7 +68,6 @@ module Mihari
|
|
40
68
|
|
41
69
|
private
|
42
70
|
|
43
|
-
#
|
44
71
|
#
|
45
72
|
# @param [String] path
|
46
73
|
# @param [Hash] params
|
data/lib/mihari/clients/crtsh.rb
CHANGED
@@ -18,13 +18,18 @@ module Mihari
|
|
18
18
|
# @param [String, nil] match "=", "ILIKE", "LIKE", "single", "any" or nil
|
19
19
|
# @param [String, nil] exclude "expired" or nil
|
20
20
|
#
|
21
|
-
# @return [Array<
|
21
|
+
# @return [Array<Mihari::Artifact>]
|
22
22
|
#
|
23
23
|
def search(identity, match: nil, exclude: nil)
|
24
24
|
params = { identity: identity, match: match, exclude: exclude, output: "json" }.compact
|
25
25
|
|
26
26
|
res = get("/", params: params)
|
27
|
-
JSON.parse(res.body.to_s)
|
27
|
+
parsed = JSON.parse(res.body.to_s)
|
28
|
+
|
29
|
+
parsed.map do |result|
|
30
|
+
values = result["name_value"].to_s.lines.map(&:chomp)
|
31
|
+
values.map { |value| Artifact.new(data: value, metadata: result) }
|
32
|
+
end.flatten
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
@@ -16,11 +16,13 @@ module Mihari
|
|
16
16
|
#
|
17
17
|
# @param [String] domain
|
18
18
|
#
|
19
|
-
# @return [
|
19
|
+
# @return [Array<String>]
|
20
20
|
#
|
21
21
|
def fuzz(domain)
|
22
22
|
res = get("/api/fuzz/#{to_hex(domain)}")
|
23
|
-
JSON.parse(res.body.to_s)
|
23
|
+
res = JSON.parse(res.body.to_s)
|
24
|
+
fuzzy_domains = res["fuzzy_domains"] || []
|
25
|
+
fuzzy_domains.map { |d| d["domain"] }
|
24
26
|
end
|
25
27
|
|
26
28
|
private
|