mihari 5.1.0 → 5.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 +17 -9
- data/lib/mihari/analyzers/censys.rb +10 -54
- data/lib/mihari/analyzers/circl.rb +7 -6
- data/lib/mihari/analyzers/crtsh.rb +12 -7
- data/lib/mihari/analyzers/dnstwister.rb +7 -7
- data/lib/mihari/analyzers/feed.rb +33 -10
- data/lib/mihari/analyzers/greynoise.rb +8 -33
- data/lib/mihari/analyzers/onyphe.rb +10 -36
- data/lib/mihari/analyzers/otx.rb +4 -3
- data/lib/mihari/analyzers/passivetotal.rb +8 -7
- data/lib/mihari/analyzers/pulsedive.rb +8 -7
- data/lib/mihari/analyzers/rule.rb +0 -1
- data/lib/mihari/analyzers/securitytrails.rb +8 -10
- data/lib/mihari/analyzers/shodan.rb +16 -90
- data/lib/mihari/analyzers/urlscan.rb +16 -6
- data/lib/mihari/analyzers/virustotal.rb +8 -6
- data/lib/mihari/analyzers/virustotal_intelligence.rb +12 -7
- data/lib/mihari/analyzers/zoomeye.rb +13 -10
- data/lib/mihari/clients/base.rb +53 -0
- data/lib/mihari/clients/binaryedge.rb +38 -0
- data/lib/mihari/clients/censys.rb +42 -0
- data/lib/mihari/clients/circl.rb +59 -0
- data/lib/mihari/clients/crtsh.rb +31 -0
- data/lib/mihari/clients/dnstwister.rb +40 -0
- data/lib/mihari/clients/greynoise.rb +34 -0
- data/lib/mihari/clients/misp.rb +29 -0
- data/lib/mihari/clients/onyphe.rb +35 -0
- data/lib/mihari/clients/otx.rb +49 -0
- data/lib/mihari/clients/passivetotal.rb +69 -0
- data/lib/mihari/clients/publsedive.rb +56 -0
- data/lib/mihari/clients/securitytrails.rb +94 -0
- data/lib/mihari/clients/shodan.rb +41 -0
- data/lib/mihari/clients/the_hive.rb +33 -0
- data/lib/mihari/clients/urlscan.rb +33 -0
- data/lib/mihari/clients/virustotal.rb +62 -0
- data/lib/mihari/clients/zoomeye.rb +74 -0
- 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/emitters/misp.rb +13 -20
- data/lib/mihari/emitters/the_hive.rb +3 -5
- data/lib/mihari/emitters/webhook.rb +2 -2
- data/lib/mihari/feed/reader.rb +14 -11
- data/lib/mihari/http.rb +29 -21
- data/lib/mihari/mixins/retriable.rb +3 -1
- data/lib/mihari/schemas/analyzer.rb +5 -4
- 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 +21 -2
- data/mihari.gemspec +15 -23
- metadata +55 -264
- data/lib/mihari/analyzers/clients/otx.rb +0 -36
- data/lib/mihari/analyzers/dnpedia.rb +0 -37
- data/lib/mihari/mixins/database.rb +0 -16
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Clients
|
7
|
+
class CIRCL < Base
|
8
|
+
#
|
9
|
+
# @param [String] base_url
|
10
|
+
# @param [String, nil] username
|
11
|
+
# @param [String, nil] password
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
14
|
+
def initialize(base_url = "https://www.circl.lu", username:, password:, headers: {})
|
15
|
+
raise(ArgumentError, "'username' argument is required") if username.nil?
|
16
|
+
raise(ArgumentError, "'password' argument is required") if password.nil?
|
17
|
+
|
18
|
+
headers["authorization"] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
19
|
+
|
20
|
+
super(base_url, headers: headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# @param [String] query
|
25
|
+
#
|
26
|
+
# @return [Hash]
|
27
|
+
#
|
28
|
+
def dns_query(query)
|
29
|
+
_get("/pdns/query/#{query}")
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# @param [String] query
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
#
|
37
|
+
def ssl_cquery(query)
|
38
|
+
_get("/v2pssl/cquery/#{query}")
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
#
|
44
|
+
#
|
45
|
+
# @param [String] path
|
46
|
+
# @param [Hash] params
|
47
|
+
#
|
48
|
+
def _get(path, params: {})
|
49
|
+
res = get(path, params: params)
|
50
|
+
body = res.body.to_s
|
51
|
+
content_type = res["Content-Type"].to_s
|
52
|
+
|
53
|
+
return JSON.parse(body) if content_type.include?("application/json")
|
54
|
+
|
55
|
+
body.lines.map { |line| JSON.parse line }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class Crtsh < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [Hash] headers
|
9
|
+
#
|
10
|
+
def initialize(base_url = "https://crt.sh", headers: {})
|
11
|
+
super(base_url, headers: headers)
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Search crt.sh by a given identity
|
16
|
+
#
|
17
|
+
# @param [String] identity
|
18
|
+
# @param [String, nil] match "=", "ILIKE", "LIKE", "single", "any" or nil
|
19
|
+
# @param [String, nil] exclude "expired" or nil
|
20
|
+
#
|
21
|
+
# @return [Array<Hash>]
|
22
|
+
#
|
23
|
+
def search(identity, match: nil, exclude: nil)
|
24
|
+
params = { identity: identity, match: match, exclude: exclude, output: "json" }.compact
|
25
|
+
|
26
|
+
res = get("/", params: params)
|
27
|
+
JSON.parse(res.body.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class DNSTwister < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [Hash] headers
|
9
|
+
#
|
10
|
+
def initialize(base_url = "https://dnstwister.report", headers: {})
|
11
|
+
super(base_url, headers: headers)
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Get fuzzy domains
|
16
|
+
#
|
17
|
+
# @param [String] domain
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
def fuzz(domain)
|
22
|
+
res = get("/api/fuzz/#{to_hex(domain)}")
|
23
|
+
JSON.parse(res.body.to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
#
|
29
|
+
# Converts string to hex
|
30
|
+
#
|
31
|
+
# @param [String] str String
|
32
|
+
#
|
33
|
+
# @return [String] Hex
|
34
|
+
#
|
35
|
+
def to_hex(str)
|
36
|
+
str.each_byte.map { |b| b.to_s(16) }.join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class GreyNoise < 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.greynoise.io", api_key:, headers: {})
|
12
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
13
|
+
|
14
|
+
headers["key"] = api_key
|
15
|
+
super(base_url, headers: headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# GNQL (GreyNoise Query Language) is a domain-specific query language that uses Lucene deep under the hood
|
20
|
+
#
|
21
|
+
# @param [String] query GNQL query string
|
22
|
+
# @param [Integer, nil] size Maximum amount of results to grab
|
23
|
+
# @param [Integer, nil] scroll Scroll token to paginate through results
|
24
|
+
#
|
25
|
+
# @return [Hash]
|
26
|
+
#
|
27
|
+
def gnql_search(query, size: nil, scroll: nil)
|
28
|
+
params = { query: query, size: size, scroll: scroll }.compact
|
29
|
+
res = get("/v2/experimental/gnql", params: params)
|
30
|
+
Structs::GreyNoise::Response.from_dynamic! JSON.parse(res.body.to_s)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class MISP < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
11
|
+
def initialize(base_url, api_key:, headers: {})
|
12
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
13
|
+
|
14
|
+
headers["authorization"] = api_key
|
15
|
+
super(base_url, headers: headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# @param [Hash] payload
|
20
|
+
#
|
21
|
+
# @return [Hash]
|
22
|
+
#
|
23
|
+
def create_event(payload)
|
24
|
+
res = post("/events/add", json: payload)
|
25
|
+
JSON.parse(res.body.to_s)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class Onyphe < Base
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :api_key
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param [String] base_url
|
11
|
+
# @param [String, nil] api_key
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
14
|
+
def initialize(base_url = "https://www.onyphe.io", api_key:, headers: {})
|
15
|
+
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
16
|
+
|
17
|
+
super(base_url, headers: headers)
|
18
|
+
|
19
|
+
@api_key = api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# @param [String] query
|
24
|
+
# @param [Integer] page
|
25
|
+
#
|
26
|
+
# @return [Hash]
|
27
|
+
#
|
28
|
+
def datascan(query, page: 1)
|
29
|
+
params = { page: page, apikey: api_key }
|
30
|
+
res = get("/api/v2/simple/datascan/#{query}", params: params)
|
31
|
+
Structs::Onyphe::Response.from_dynamic! JSON.parse(res.body.to_s)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class OTX < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
11
|
+
def initialize(base_url = "https://otx.alienvault.com", api_key:, headers: {})
|
12
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
13
|
+
|
14
|
+
headers["x-otx-api-key"] = api_key
|
15
|
+
super(base_url, headers: headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# @param [String] ip
|
20
|
+
#
|
21
|
+
# @return [Hash]
|
22
|
+
#
|
23
|
+
def query_by_ip(ip)
|
24
|
+
_get "/api/v1/indicators/IPv4/#{ip}/passive_dns"
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# @param [String] domain
|
29
|
+
#
|
30
|
+
# @return [Hash]
|
31
|
+
#
|
32
|
+
def query_by_domain(domain)
|
33
|
+
_get "/api/v1/indicators/domain/#{domain}/passive_dns"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
#
|
39
|
+
# @param [String] path
|
40
|
+
#
|
41
|
+
# @return [Hash]
|
42
|
+
#
|
43
|
+
def _get(path)
|
44
|
+
res = get(path)
|
45
|
+
JSON.parse(res.body.to_s)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Clients
|
7
|
+
class PassiveTotal < Base
|
8
|
+
#
|
9
|
+
# @param [String] base_url
|
10
|
+
# @param [String, nil] username
|
11
|
+
# @param [String, nil] api_key
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
14
|
+
def initialize(base_url = "https://api.passivetotal.org", username:, api_key:, headers: {})
|
15
|
+
raise(ArgumentError, "'username' argument is required") if username.nil?
|
16
|
+
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
17
|
+
|
18
|
+
headers["authorization"] = "Basic #{Base64.strict_encode64("#{username}:#{api_key}")}"
|
19
|
+
|
20
|
+
super(base_url, headers: headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# @param [String] query
|
25
|
+
#
|
26
|
+
def ssl_search(query)
|
27
|
+
params = { query: query }
|
28
|
+
_get("/v2/ssl-certificate/history", params: params)
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# @param [String] query
|
33
|
+
#
|
34
|
+
# @return [Hash]
|
35
|
+
#
|
36
|
+
def passive_dns_search(query)
|
37
|
+
params = { query: query }
|
38
|
+
_get("/v2/dns/passive/unique", params: params)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# @param [String] query the domain being queried
|
43
|
+
# @param [String] field whether to return historical results
|
44
|
+
#
|
45
|
+
# @return [Hash]
|
46
|
+
#
|
47
|
+
def reverse_whois_search(query:, field:)
|
48
|
+
params = {
|
49
|
+
query: query,
|
50
|
+
field: field
|
51
|
+
}.compact
|
52
|
+
_get("/v2/whois/search", params: params)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
#
|
58
|
+
# @param [String] path
|
59
|
+
# @param [Hash] params
|
60
|
+
#
|
61
|
+
# @return [Hash]
|
62
|
+
#
|
63
|
+
def _get(path, params: {})
|
64
|
+
res = get(path, params: params)
|
65
|
+
JSON.parse(res.body.to_s)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class PulseDive < Base
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :api_key
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param [String] base_url
|
11
|
+
# @param [String, nil] api_key
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
14
|
+
def initialize(base_url = "https://pulsedive.com", api_key:, headers: {})
|
15
|
+
super(base_url, headers: headers)
|
16
|
+
|
17
|
+
@api_key = api_key
|
18
|
+
|
19
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# @param [String] indicator_id
|
24
|
+
#
|
25
|
+
# @return [Hash]
|
26
|
+
#
|
27
|
+
def get_indicator(ip_or_domain)
|
28
|
+
_get "/api/info.php", params: { indicator: ip_or_domain }
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# @param [String] indicator_id
|
33
|
+
#
|
34
|
+
# @return [Hash]
|
35
|
+
#
|
36
|
+
def get_properties(indicator_id)
|
37
|
+
_get "/api/info.php", params: { iid: indicator_id, get: "properties" }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
#
|
43
|
+
# @param [String] path
|
44
|
+
# @param [Hash] params
|
45
|
+
#
|
46
|
+
# @return [Hash]
|
47
|
+
#
|
48
|
+
def _get(path, params: {})
|
49
|
+
params["key"] = api_key
|
50
|
+
|
51
|
+
res = get(path, params: params)
|
52
|
+
JSON.parse(res.body.to_s)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -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
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class Shodan < Base
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :api_key
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param [String] base_url
|
11
|
+
# @param [String, nil] api_key
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
14
|
+
def initialize(base_url = "https://api.shodan.io", api_key:, headers: {})
|
15
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
16
|
+
|
17
|
+
super(base_url, headers: headers)
|
18
|
+
|
19
|
+
@api_key = api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# @param [String] query
|
24
|
+
# @param [Integer] page
|
25
|
+
# @param [Boolean] minify
|
26
|
+
#
|
27
|
+
# @return [Structs::Shodan::Result]
|
28
|
+
#
|
29
|
+
def search(query, page: 1, minify: true)
|
30
|
+
params = {
|
31
|
+
query: query,
|
32
|
+
page: page,
|
33
|
+
minify: minify,
|
34
|
+
key: api_key
|
35
|
+
}
|
36
|
+
res = get("/shodan/host/search", params: params)
|
37
|
+
Structs::Shodan::Result.from_dynamic! JSON.parse(res.body.to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class TheHive < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [String, nil] api_version
|
10
|
+
# @param [Hash] headers
|
11
|
+
#
|
12
|
+
def initialize(base_url, api_key:, api_version:, headers: {})
|
13
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
14
|
+
|
15
|
+
base_url += "/#{api_version}" unless api_version.nil?
|
16
|
+
headers["authorization"] = "Bearer #{api_key}"
|
17
|
+
|
18
|
+
super(base_url, headers: headers)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# @param [Hash] json
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
#
|
26
|
+
def alert(json)
|
27
|
+
json = json.to_camelback_keys.compact
|
28
|
+
res = post("/alert", json: json)
|
29
|
+
JSON.parse(res.body.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class UrlScan < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
11
|
+
def initialize(base_url = "https://urlscan.io", api_key:, headers: {})
|
12
|
+
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
13
|
+
|
14
|
+
headers["api-key"] = api_key
|
15
|
+
|
16
|
+
super(base_url, headers: headers)
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# @param [String] q
|
21
|
+
# @param [Integer] size
|
22
|
+
# @param [String, nil] search_after
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
#
|
26
|
+
def search(q, size: 100, search_after: nil)
|
27
|
+
params = { q: q, size: size, search_after: search_after }.compact
|
28
|
+
res = get("/api/v1/search/", params: params)
|
29
|
+
JSON.parse res.body.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class VirusTotal < Base
|
6
|
+
#
|
7
|
+
# @param [String] base_url
|
8
|
+
# @param [String, nil] api_key
|
9
|
+
# @param [Hash] headers
|
10
|
+
#
|
11
|
+
def initialize(base_url = "https://www.virustotal.com", api_key:, headers: {})
|
12
|
+
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
13
|
+
|
14
|
+
headers["x-apikey"] = api_key
|
15
|
+
|
16
|
+
super(base_url, headers: headers)
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# @param [String] query
|
21
|
+
#
|
22
|
+
# @return [Hash]
|
23
|
+
#
|
24
|
+
def domain_search(query)
|
25
|
+
_get("/api/v3/domains/#{query}/resolutions")
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# @param [String] query
|
30
|
+
#
|
31
|
+
# @return [Hash]
|
32
|
+
#
|
33
|
+
def ip_search(query)
|
34
|
+
_get("/api/v3/ip_addresses/#{query}/resolutions")
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# @param [String] query
|
39
|
+
# @param [String, nil] cursor
|
40
|
+
#
|
41
|
+
# @return [Hash]
|
42
|
+
#
|
43
|
+
def intel_search(query, cursor: nil)
|
44
|
+
params = { query: query, cursor: cursor }.compact
|
45
|
+
_get("/api/v3/intelligence/search", params: params)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
#
|
51
|
+
# @param [String] path
|
52
|
+
# @param [Hash] params
|
53
|
+
#
|
54
|
+
# @return [Hash]
|
55
|
+
#
|
56
|
+
def _get(path, params: {})
|
57
|
+
res = get(path, params: params)
|
58
|
+
JSON.parse(res.body.to_s)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|