mihari 5.1.1 → 5.1.2
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/.gitmodules +0 -3
- data/.rubocop.yml +6 -0
- data/README.md +0 -1
- data/lib/mihari/analyzers/base.rb +32 -27
- data/lib/mihari/analyzers/binaryedge.rb +8 -2
- data/lib/mihari/analyzers/censys.rb +7 -49
- data/lib/mihari/analyzers/circl.rb +5 -2
- data/lib/mihari/analyzers/crtsh.rb +6 -0
- data/lib/mihari/analyzers/dnstwister.rb +4 -2
- data/lib/mihari/analyzers/feed.rb +21 -0
- data/lib/mihari/analyzers/greynoise.rb +5 -28
- data/lib/mihari/analyzers/onyphe.rb +8 -33
- data/lib/mihari/analyzers/otx.rb +3 -0
- data/lib/mihari/analyzers/passivetotal.rb +3 -0
- data/lib/mihari/analyzers/pulsedive.rb +3 -0
- data/lib/mihari/analyzers/rule.rb +0 -1
- data/lib/mihari/analyzers/securitytrails.rb +8 -10
- data/lib/mihari/analyzers/shodan.rb +13 -81
- data/lib/mihari/analyzers/urlscan.rb +9 -0
- data/lib/mihari/analyzers/virustotal.rb +4 -0
- data/lib/mihari/analyzers/virustotal_intelligence.rb +8 -2
- data/lib/mihari/analyzers/zoomeye.rb +9 -0
- data/lib/mihari/clients/binaryedge.rb +5 -0
- data/lib/mihari/clients/censys.rb +4 -4
- data/lib/mihari/clients/circl.rb +3 -3
- data/lib/mihari/clients/greynoise.rb +6 -1
- data/lib/mihari/clients/misp.rb +6 -1
- data/lib/mihari/clients/onyphe.rb +13 -1
- data/lib/mihari/clients/otx.rb +20 -0
- data/lib/mihari/clients/passivetotal.rb +6 -2
- data/lib/mihari/clients/publsedive.rb +18 -1
- data/lib/mihari/clients/securitytrails.rb +94 -0
- data/lib/mihari/clients/shodan.rb +14 -3
- data/lib/mihari/clients/the_hive.rb +6 -1
- data/lib/mihari/clients/urlscan.rb +3 -1
- data/lib/mihari/clients/virustotal.rb +9 -3
- data/lib/mihari/clients/zoomeye.rb +7 -1
- data/lib/mihari/commands/database.rb +1 -6
- data/lib/mihari/commands/searcher.rb +1 -2
- data/lib/mihari/database.rb +9 -0
- data/lib/mihari/structs/censys.rb +62 -0
- data/lib/mihari/structs/greynoise.rb +43 -0
- data/lib/mihari/structs/onyphe.rb +45 -0
- data/lib/mihari/structs/shodan.rb +83 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/middleware/connection_adapter.rb +1 -3
- data/lib/mihari/web/public/assets/{index-63900d73.js → index-7d0fb8c4.js} +2 -2
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +2 -2
- data/lib/mihari.rb +1 -3
- data/mihari.gemspec +2 -3
- metadata +9 -25
- data/lib/mihari/analyzers/dnpedia.rb +0 -33
- data/lib/mihari/clients/dnpedia.rb +0 -64
- data/lib/mihari/mixins/database.rb +0 -16
@@ -10,6 +10,12 @@ module Mihari
|
|
10
10
|
# @return [String, nil]
|
11
11
|
attr_reader :api_key
|
12
12
|
|
13
|
+
# @return [Integer]
|
14
|
+
attr_reader :interval
|
15
|
+
|
16
|
+
# @return [String]
|
17
|
+
attr_reader :query
|
18
|
+
|
13
19
|
def initialize(*args, **kwargs)
|
14
20
|
super(*args, **kwargs)
|
15
21
|
|
@@ -18,13 +24,9 @@ module Mihari
|
|
18
24
|
|
19
25
|
def artifacts
|
20
26
|
results = search
|
21
|
-
return []
|
22
|
-
|
23
|
-
results = results.map { |result| Structs::Shodan::Result.from_dynamic!(result) }
|
24
|
-
matches = results.map { |result| result.matches || [] }.flatten
|
27
|
+
return [] if results.empty?
|
25
28
|
|
26
|
-
|
27
|
-
uniq_matches.map { |match| build_artifact(match, matches) }
|
29
|
+
results.map { |result| result.to_artifacts(source) }.flatten.uniq(&:data)
|
28
30
|
end
|
29
31
|
|
30
32
|
private
|
@@ -42,29 +44,25 @@ module Mihari
|
|
42
44
|
#
|
43
45
|
# Search with pagination
|
44
46
|
#
|
45
|
-
# @param [String] query
|
46
47
|
# @param [Integer] page
|
47
48
|
#
|
48
|
-
# @return [
|
49
|
+
# @return [Structs::Shodan::Result]
|
49
50
|
#
|
50
|
-
def search_with_page(
|
51
|
+
def search_with_page(page: 1)
|
51
52
|
client.search(query, page: page)
|
52
53
|
end
|
53
54
|
|
54
55
|
#
|
55
56
|
# Search
|
56
57
|
#
|
57
|
-
# @return [Array<
|
58
|
+
# @return [Array<Structs::Shodan::Result>]
|
58
59
|
#
|
59
60
|
def search
|
60
61
|
responses = []
|
61
62
|
(1..Float::INFINITY).each do |page|
|
62
|
-
res = search_with_page(
|
63
|
-
|
64
|
-
break unless res
|
65
|
-
|
63
|
+
res = search_with_page(page: page)
|
66
64
|
responses << res
|
67
|
-
break if res
|
65
|
+
break if res.total <= page * PAGE_SIZE
|
68
66
|
|
69
67
|
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
70
68
|
sleep interval
|
@@ -76,42 +74,6 @@ module Mihari
|
|
76
74
|
responses
|
77
75
|
end
|
78
76
|
|
79
|
-
#
|
80
|
-
# Collect metadata from matches
|
81
|
-
#
|
82
|
-
# @param [Array<Structs::Shodan::Match>] matches
|
83
|
-
# @param [String] ip
|
84
|
-
#
|
85
|
-
# @return [Array<Hash>]
|
86
|
-
#
|
87
|
-
def collect_metadata_by_ip(matches, ip)
|
88
|
-
matches.select { |match| match.ip_str == ip }.map(&:metadata)
|
89
|
-
end
|
90
|
-
|
91
|
-
#
|
92
|
-
# Collect ports from matches
|
93
|
-
#
|
94
|
-
# @param [Array<Structs::Shodan::Match>] matches
|
95
|
-
# @param [String] ip
|
96
|
-
#
|
97
|
-
# @return [Array<String>]
|
98
|
-
#
|
99
|
-
def collect_ports_by_ip(matches, ip)
|
100
|
-
matches.select { |match| match.ip_str == ip }.map(&:port)
|
101
|
-
end
|
102
|
-
|
103
|
-
#
|
104
|
-
# Collect hostnames from matches
|
105
|
-
#
|
106
|
-
# @param [Array<Structs::Shodan::Match>] matches
|
107
|
-
# @param [String] ip
|
108
|
-
#
|
109
|
-
# @return [Array<String>]
|
110
|
-
#
|
111
|
-
def collect_hostnames_by_ip(matches, ip)
|
112
|
-
matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
|
113
|
-
end
|
114
|
-
|
115
77
|
#
|
116
78
|
# Build an artifact from a Shodan search API response
|
117
79
|
#
|
@@ -121,36 +83,6 @@ module Mihari
|
|
121
83
|
# @return [Artifact]
|
122
84
|
#
|
123
85
|
def build_artifact(match, matches)
|
124
|
-
as = nil
|
125
|
-
as = AutonomousSystem.new(asn: normalize_asn(match.asn)) unless match.asn.nil?
|
126
|
-
|
127
|
-
geolocation = nil
|
128
|
-
if !match.location.country_name.nil? && !match.location.country_code.nil?
|
129
|
-
geolocation = Geolocation.new(
|
130
|
-
country: match.location.country_name,
|
131
|
-
country_code: match.location.country_code
|
132
|
-
)
|
133
|
-
end
|
134
|
-
|
135
|
-
metadata = collect_metadata_by_ip(matches, match.ip_str)
|
136
|
-
|
137
|
-
ports = collect_ports_by_ip(matches, match.ip_str).map do |port|
|
138
|
-
Port.new(port: port)
|
139
|
-
end
|
140
|
-
|
141
|
-
reverse_dns_names = collect_hostnames_by_ip(matches, match.ip_str).map do |name|
|
142
|
-
ReverseDnsName.new(name: name)
|
143
|
-
end
|
144
|
-
|
145
|
-
Artifact.new(
|
146
|
-
data: match.ip_str,
|
147
|
-
source: source,
|
148
|
-
metadata: metadata,
|
149
|
-
autonomous_system: as,
|
150
|
-
geolocation: geolocation,
|
151
|
-
ports: ports,
|
152
|
-
reverse_dns_names: reverse_dns_names
|
153
|
-
)
|
154
86
|
end
|
155
87
|
end
|
156
88
|
end
|
@@ -15,6 +15,15 @@ module Mihari
|
|
15
15
|
# @return [String, nil]
|
16
16
|
attr_reader :api_key
|
17
17
|
|
18
|
+
# @return [String]
|
19
|
+
attr_reader :query
|
20
|
+
|
21
|
+
# @return [Integer]
|
22
|
+
attr_reader :interval
|
23
|
+
|
24
|
+
# @return [String]
|
25
|
+
attr_reader :allowed_data_types
|
26
|
+
|
18
27
|
def initialize(*args, **kwargs)
|
19
28
|
super
|
20
29
|
|
@@ -10,6 +10,12 @@ module Mihari
|
|
10
10
|
# @return [String, nil]
|
11
11
|
attr_reader :api_key
|
12
12
|
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :query
|
15
|
+
|
16
|
+
# @return [Integer]
|
17
|
+
attr_reader :interval
|
18
|
+
|
13
19
|
def initialize(*args, **kwargs)
|
14
20
|
super
|
15
21
|
|
@@ -19,7 +25,7 @@ module Mihari
|
|
19
25
|
end
|
20
26
|
|
21
27
|
def artifacts
|
22
|
-
responses =
|
28
|
+
responses = search_with_cursor
|
23
29
|
responses.map do |response|
|
24
30
|
response.data.map do |datum|
|
25
31
|
Artifact.new(data: datum.value, source: source, metadata: datum.metadata)
|
@@ -47,7 +53,7 @@ module Mihari
|
|
47
53
|
#
|
48
54
|
# @return [Array<Structs::VirusTotalIntelligence::Response>]
|
49
55
|
#
|
50
|
-
def
|
56
|
+
def search_with_cursor
|
51
57
|
cursor = nil
|
52
58
|
responses = []
|
53
59
|
|
@@ -12,6 +12,15 @@ module Mihari
|
|
12
12
|
# @return [String, nil]
|
13
13
|
attr_reader :api_key
|
14
14
|
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :query
|
17
|
+
|
18
|
+
# @return [String]
|
19
|
+
attr_reader :type
|
20
|
+
|
21
|
+
# @return [Integer]
|
22
|
+
attr_reader :interval
|
23
|
+
|
15
24
|
def initialize(*args, **kwargs)
|
16
25
|
super(*args, **kwargs)
|
17
26
|
|
@@ -3,6 +3,11 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Clients
|
5
5
|
class BinaryEdge < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
6
11
|
def initialize(base_url = "https://api.binaryedge.io/v2", api_key:, headers: {})
|
7
12
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
8
13
|
|
@@ -7,8 +7,8 @@ module Mihari
|
|
7
7
|
class Censys < Base
|
8
8
|
#
|
9
9
|
# @param [String] base_url
|
10
|
-
# @param [String] id
|
11
|
-
# @param [String] secret
|
10
|
+
# @param [String, nil] id
|
11
|
+
# @param [String, nil] secret
|
12
12
|
# @param [Hash] headers
|
13
13
|
#
|
14
14
|
def initialize(base_url = "https://search.censys.io", id:, secret:, headers: {})
|
@@ -30,12 +30,12 @@ module Mihari
|
|
30
30
|
# @params [Integer, nil] per_page the number of results to be returned for each page.
|
31
31
|
# @params [Integer, nil] cursor the cursor of the desired result set.
|
32
32
|
#
|
33
|
-
# @return [
|
33
|
+
# @return [Structs::Censys::Response]
|
34
34
|
#
|
35
35
|
def search(query, per_page: nil, cursor: nil)
|
36
36
|
params = { q: query, per_page: per_page, cursor: cursor }.compact
|
37
37
|
res = get("/api/v2/hosts/search", params: params)
|
38
|
-
JSON.parse(res.body.to_s)
|
38
|
+
Structs::Censys::Response.from_dynamic! JSON.parse(res.body.to_s)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
data/lib/mihari/clients/circl.rb
CHANGED
@@ -7,8 +7,8 @@ module Mihari
|
|
7
7
|
class CIRCL < Base
|
8
8
|
#
|
9
9
|
# @param [String] base_url
|
10
|
-
# @param [String] username
|
11
|
-
# @param [String] password
|
10
|
+
# @param [String, nil] username
|
11
|
+
# @param [String, nil] password
|
12
12
|
# @param [Hash] headers
|
13
13
|
#
|
14
14
|
def initialize(base_url = "https://www.circl.lu", username:, password:, headers: {})
|
@@ -43,7 +43,7 @@ module Mihari
|
|
43
43
|
#
|
44
44
|
#
|
45
45
|
# @param [String] path
|
46
|
-
# @param [
|
46
|
+
# @param [Hash] params
|
47
47
|
#
|
48
48
|
def _get(path, params: {})
|
49
49
|
res = get(path, params: params)
|
@@ -3,6 +3,11 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Clients
|
5
5
|
class GreyNoise < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
6
11
|
def initialize(base_url = "https://api.greynoise.io", api_key:, headers: {})
|
7
12
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
8
13
|
|
@@ -22,7 +27,7 @@ module Mihari
|
|
22
27
|
def gnql_search(query, size: nil, scroll: nil)
|
23
28
|
params = { query: query, size: size, scroll: scroll }.compact
|
24
29
|
res = get("/v2/experimental/gnql", params: params)
|
25
|
-
JSON.parse
|
30
|
+
Structs::GreyNoise::Response.from_dynamic! JSON.parse(res.body.to_s)
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
data/lib/mihari/clients/misp.rb
CHANGED
@@ -5,7 +5,7 @@ module Mihari
|
|
5
5
|
class MISP < Base
|
6
6
|
#
|
7
7
|
# @param [String] base_url
|
8
|
-
# @param [String] api_key
|
8
|
+
# @param [String, nil] api_key
|
9
9
|
# @param [Hash] headers
|
10
10
|
#
|
11
11
|
def initialize(base_url, api_key:, headers: {})
|
@@ -15,6 +15,11 @@ module Mihari
|
|
15
15
|
super(base_url, headers: headers)
|
16
16
|
end
|
17
17
|
|
18
|
+
#
|
19
|
+
# @param [Hash] payload
|
20
|
+
#
|
21
|
+
# @return [Hash]
|
22
|
+
#
|
18
23
|
def create_event(payload)
|
19
24
|
res = post("/events/add", json: payload)
|
20
25
|
JSON.parse(res.body.to_s)
|
@@ -3,8 +3,14 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Clients
|
5
5
|
class Onyphe < Base
|
6
|
+
# @return [String]
|
6
7
|
attr_reader :api_key
|
7
8
|
|
9
|
+
#
|
10
|
+
# @param [String] base_url
|
11
|
+
# @param [String, nil] api_key
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
8
14
|
def initialize(base_url = "https://www.onyphe.io", api_key:, headers: {})
|
9
15
|
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
10
16
|
|
@@ -13,10 +19,16 @@ module Mihari
|
|
13
19
|
@api_key = api_key
|
14
20
|
end
|
15
21
|
|
22
|
+
#
|
23
|
+
# @param [String] query
|
24
|
+
# @param [Integer] page
|
25
|
+
#
|
26
|
+
# @return [Hash]
|
27
|
+
#
|
16
28
|
def datascan(query, page: 1)
|
17
29
|
params = { page: page, apikey: api_key }
|
18
30
|
res = get("/api/v2/simple/datascan/#{query}", params: params)
|
19
|
-
JSON.parse(res.body.to_s)
|
31
|
+
Structs::Onyphe::Response.from_dynamic! JSON.parse(res.body.to_s)
|
20
32
|
end
|
21
33
|
end
|
22
34
|
end
|
data/lib/mihari/clients/otx.rb
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Clients
|
5
5
|
class OTX < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
6
11
|
def initialize(base_url = "https://otx.alienvault.com", api_key:, headers: {})
|
7
12
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
8
13
|
|
@@ -10,16 +15,31 @@ module Mihari
|
|
10
15
|
super(base_url, headers: headers)
|
11
16
|
end
|
12
17
|
|
18
|
+
#
|
19
|
+
# @param [String] ip
|
20
|
+
#
|
21
|
+
# @return [Hash]
|
22
|
+
#
|
13
23
|
def query_by_ip(ip)
|
14
24
|
_get "/api/v1/indicators/IPv4/#{ip}/passive_dns"
|
15
25
|
end
|
16
26
|
|
27
|
+
#
|
28
|
+
# @param [String] domain
|
29
|
+
#
|
30
|
+
# @return [Hash]
|
31
|
+
#
|
17
32
|
def query_by_domain(domain)
|
18
33
|
_get "/api/v1/indicators/domain/#{domain}/passive_dns"
|
19
34
|
end
|
20
35
|
|
21
36
|
private
|
22
37
|
|
38
|
+
#
|
39
|
+
# @param [String] path
|
40
|
+
#
|
41
|
+
# @return [Hash]
|
42
|
+
#
|
23
43
|
def _get(path)
|
24
44
|
res = get(path)
|
25
45
|
JSON.parse(res.body.to_s)
|
@@ -7,8 +7,8 @@ module Mihari
|
|
7
7
|
class PassiveTotal < Base
|
8
8
|
#
|
9
9
|
# @param [String] base_url
|
10
|
-
# @param [String] username
|
11
|
-
# @param [String] api_key
|
10
|
+
# @param [String, nil] username
|
11
|
+
# @param [String, nil] api_key
|
12
12
|
# @param [Hash] headers
|
13
13
|
#
|
14
14
|
def initialize(base_url = "https://api.passivetotal.org", username:, api_key:, headers: {})
|
@@ -31,6 +31,8 @@ module Mihari
|
|
31
31
|
#
|
32
32
|
# @param [String] query
|
33
33
|
#
|
34
|
+
# @return [Hash]
|
35
|
+
#
|
34
36
|
def passive_dns_search(query)
|
35
37
|
params = { query: query }
|
36
38
|
_get("/v2/dns/passive/unique", params: params)
|
@@ -56,6 +58,8 @@ module Mihari
|
|
56
58
|
# @param [String] path
|
57
59
|
# @param [Hash] params
|
58
60
|
#
|
61
|
+
# @return [Hash]
|
62
|
+
#
|
59
63
|
def _get(path, params: {})
|
60
64
|
res = get(path, params: params)
|
61
65
|
JSON.parse(res.body.to_s)
|
@@ -3,8 +3,14 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Clients
|
5
5
|
class PulseDive < Base
|
6
|
+
# @return [String]
|
6
7
|
attr_reader :api_key
|
7
8
|
|
9
|
+
#
|
10
|
+
# @param [String] base_url
|
11
|
+
# @param [String, nil] api_key
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
8
14
|
def initialize(base_url = "https://pulsedive.com", api_key:, headers: {})
|
9
15
|
super(base_url, headers: headers)
|
10
16
|
|
@@ -13,21 +19,32 @@ module Mihari
|
|
13
19
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
14
20
|
end
|
15
21
|
|
22
|
+
#
|
23
|
+
# @param [String] indicator_id
|
24
|
+
#
|
25
|
+
# @return [Hash]
|
26
|
+
#
|
16
27
|
def get_indicator(ip_or_domain)
|
17
28
|
_get "/api/info.php", params: { indicator: ip_or_domain }
|
18
29
|
end
|
19
30
|
|
31
|
+
#
|
32
|
+
# @param [String] indicator_id
|
33
|
+
#
|
34
|
+
# @return [Hash]
|
35
|
+
#
|
20
36
|
def get_properties(indicator_id)
|
21
37
|
_get "/api/info.php", params: { iid: indicator_id, get: "properties" }
|
22
38
|
end
|
23
39
|
|
24
40
|
private
|
25
41
|
|
26
|
-
#
|
27
42
|
#
|
28
43
|
# @param [String] path
|
29
44
|
# @param [Hash] params
|
30
45
|
#
|
46
|
+
# @return [Hash]
|
47
|
+
#
|
31
48
|
def _get(path, params: {})
|
32
49
|
params["key"] = api_key
|
33
50
|
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class SecurityTrails < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
11
|
+
def initialize(base_url = "https://api.securitytrails.com", api_key:, headers: {})
|
12
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
13
|
+
|
14
|
+
headers["apikey"] = api_key
|
15
|
+
super(base_url, headers: headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# @param [String] mail
|
20
|
+
#
|
21
|
+
# @return [Array<Hash>]
|
22
|
+
#
|
23
|
+
def search_by_mail(mail)
|
24
|
+
res = _post "/v1/domains/list", json: { filter: { whois_email: mail } }
|
25
|
+
res["records"] || []
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# @param [String] ip
|
30
|
+
#
|
31
|
+
# @return [Array<Hash>]
|
32
|
+
#
|
33
|
+
def search_by_ip(ip)
|
34
|
+
res = _post "/v1/domains/list", json: { filter: { ipv4: ip } }
|
35
|
+
res["records"] || []
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# @param [String] domain
|
40
|
+
# @param [String] type
|
41
|
+
#
|
42
|
+
# @return [Array<Hash>]
|
43
|
+
#
|
44
|
+
def get_all_dns_history(domain, type:)
|
45
|
+
first_page = get_dns_history(domain, type: type, page: 1)
|
46
|
+
|
47
|
+
pages = first_page["pages"].to_i
|
48
|
+
records = first_page["records"] || []
|
49
|
+
|
50
|
+
(2..pages).each do |page_idx|
|
51
|
+
next_page = get_dns_history(domain, type: type, page: page_idx)
|
52
|
+
records << next_page["records"]
|
53
|
+
end
|
54
|
+
|
55
|
+
records.flatten
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
#
|
61
|
+
# @param [String] domain
|
62
|
+
# @param [String] type
|
63
|
+
# @param [Integer] page
|
64
|
+
#
|
65
|
+
# @return [Array<Hash>]
|
66
|
+
#
|
67
|
+
def get_dns_history(domain, type:, page:)
|
68
|
+
_get "/v1/history/#{domain}/dns/#{type}", params: { page: page }
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# @param [String] path
|
73
|
+
# @param [Hash, nil] params
|
74
|
+
#
|
75
|
+
# @return [Hash]
|
76
|
+
#
|
77
|
+
def _get(path, params:)
|
78
|
+
res = get(path, params: params)
|
79
|
+
JSON.parse(res.body.to_s)
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# @param [String] path
|
84
|
+
# @param [Hash, nil] json
|
85
|
+
#
|
86
|
+
# @return [Hash]
|
87
|
+
#
|
88
|
+
def _post(path, json:)
|
89
|
+
res = post(path, json: json)
|
90
|
+
JSON.parse(res.body.to_s)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -3,8 +3,14 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Clients
|
5
5
|
class Shodan < Base
|
6
|
+
# @return [String]
|
6
7
|
attr_reader :api_key
|
7
8
|
|
9
|
+
#
|
10
|
+
# @param [String] base_url
|
11
|
+
# @param [String, nil] api_key
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
8
14
|
def initialize(base_url = "https://api.shodan.io", api_key:, headers: {})
|
9
15
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
10
16
|
|
@@ -13,8 +19,13 @@ module Mihari
|
|
13
19
|
@api_key = api_key
|
14
20
|
end
|
15
21
|
|
16
|
-
#
|
17
|
-
#
|
22
|
+
#
|
23
|
+
# @param [String] query
|
24
|
+
# @param [Integer] page
|
25
|
+
# @param [Boolean] minify
|
26
|
+
#
|
27
|
+
# @return [Structs::Shodan::Result]
|
28
|
+
#
|
18
29
|
def search(query, page: 1, minify: true)
|
19
30
|
params = {
|
20
31
|
query: query,
|
@@ -23,7 +34,7 @@ module Mihari
|
|
23
34
|
key: api_key
|
24
35
|
}
|
25
36
|
res = get("/shodan/host/search", params: params)
|
26
|
-
JSON.parse(res.body.to_s)
|
37
|
+
Structs::Shodan::Result.from_dynamic! JSON.parse(res.body.to_s)
|
27
38
|
end
|
28
39
|
end
|
29
40
|
end
|
@@ -5,7 +5,7 @@ module Mihari
|
|
5
5
|
class TheHive < Base
|
6
6
|
#
|
7
7
|
# @param [String] base_url
|
8
|
-
# @param [String] api_key
|
8
|
+
# @param [String, nil] api_key
|
9
9
|
# @param [String, nil] api_version
|
10
10
|
# @param [Hash] headers
|
11
11
|
#
|
@@ -18,6 +18,11 @@ module Mihari
|
|
18
18
|
super(base_url, headers: headers)
|
19
19
|
end
|
20
20
|
|
21
|
+
#
|
22
|
+
# @param [Hash] json
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
#
|
21
26
|
def alert(json)
|
22
27
|
json = json.to_camelback_keys.compact
|
23
28
|
res = post("/alert", json: json)
|
@@ -5,7 +5,7 @@ module Mihari
|
|
5
5
|
class UrlScan < Base
|
6
6
|
#
|
7
7
|
# @param [String] base_url
|
8
|
-
# @param [String] api_key
|
8
|
+
# @param [String, nil] api_key
|
9
9
|
# @param [Hash] headers
|
10
10
|
#
|
11
11
|
def initialize(base_url = "https://urlscan.io", api_key:, headers: {})
|
@@ -21,6 +21,8 @@ module Mihari
|
|
21
21
|
# @param [Integer] size
|
22
22
|
# @param [String, nil] search_after
|
23
23
|
#
|
24
|
+
# @return [Hash]
|
25
|
+
#
|
24
26
|
def search(q, size: 100, search_after: nil)
|
25
27
|
params = { q: q, size: size, search_after: search_after }.compact
|
26
28
|
res = get("/api/v1/search/", params: params)
|