mihari 5.0.1 → 5.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/docker/Dockerfile +1 -1
- data/lib/mihari/analyzers/binaryedge.rb +9 -7
- data/lib/mihari/analyzers/censys.rb +3 -5
- data/lib/mihari/analyzers/circl.rb +4 -6
- data/lib/mihari/analyzers/crtsh.rb +6 -7
- data/lib/mihari/analyzers/dnpedia.rb +3 -7
- data/lib/mihari/analyzers/dnstwister.rb +3 -5
- data/lib/mihari/analyzers/feed.rb +12 -10
- data/lib/mihari/analyzers/greynoise.rb +3 -5
- data/lib/mihari/analyzers/onyphe.rb +3 -4
- data/lib/mihari/analyzers/otx.rb +1 -3
- data/lib/mihari/analyzers/passivetotal.rb +5 -7
- data/lib/mihari/analyzers/pulsedive.rb +5 -7
- data/lib/mihari/analyzers/shodan.rb +3 -9
- data/lib/mihari/analyzers/urlscan.rb +7 -6
- data/lib/mihari/analyzers/virustotal.rb +4 -6
- data/lib/mihari/analyzers/virustotal_intelligence.rb +4 -5
- data/lib/mihari/analyzers/zoomeye.rb +4 -10
- data/lib/mihari/cli/database.rb +11 -0
- data/lib/mihari/cli/main.rb +10 -4
- data/lib/mihari/cli/rule.rb +11 -0
- data/lib/mihari/clients/base.rb +53 -0
- data/lib/mihari/clients/binaryedge.rb +33 -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/dnpedia.rb +64 -0
- data/lib/mihari/clients/dnstwister.rb +40 -0
- data/lib/mihari/clients/greynoise.rb +29 -0
- data/lib/mihari/clients/misp.rb +24 -0
- data/lib/mihari/clients/onyphe.rb +23 -0
- data/lib/mihari/clients/otx.rb +29 -0
- data/lib/mihari/clients/passivetotal.rb +65 -0
- data/lib/mihari/clients/publsedive.rb +39 -0
- data/lib/mihari/clients/shodan.rb +30 -0
- data/lib/mihari/clients/the_hive.rb +28 -0
- data/lib/mihari/clients/urlscan.rb +31 -0
- data/lib/mihari/clients/virustotal.rb +56 -0
- data/lib/mihari/clients/zoomeye.rb +68 -0
- data/lib/mihari/commands/database.rb +28 -0
- data/lib/mihari/commands/{initializer.rb → rule.rb} +27 -6
- data/lib/mihari/commands/searcher.rb +5 -0
- data/lib/mihari/database.rb +8 -22
- 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/database.rb +2 -0
- data/lib/mihari/mixins/retriable.rb +3 -1
- data/lib/mihari/schemas/analyzer.rb +5 -4
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari.rb +21 -0
- data/mihari.gemspec +14 -20
- metadata +61 -238
- data/lib/mihari/analyzers/clients/otx.rb +0 -36
- data/lib/mihari/commands/validator.rb +0 -31
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class Base
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :base_url
|
8
|
+
|
9
|
+
# @return [Hash]
|
10
|
+
attr_reader :headers
|
11
|
+
|
12
|
+
#
|
13
|
+
# @param [String] base_url
|
14
|
+
# @param [Hash] headers
|
15
|
+
#
|
16
|
+
def initialize(base_url, headers: {})
|
17
|
+
@base_url = base_url
|
18
|
+
@headers = headers || {}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
#
|
24
|
+
# @param [String] path
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
#
|
28
|
+
def url_for(path)
|
29
|
+
base_url + path
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# @param [String] path
|
34
|
+
# @param [Hashk, nil] params
|
35
|
+
#
|
36
|
+
# @return [String] <description>
|
37
|
+
#
|
38
|
+
def get(path, params: nil)
|
39
|
+
HTTP.get(url_for(path), headers: headers, params: params)
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @param [String] path
|
44
|
+
# @param [Hash, nil] json
|
45
|
+
#
|
46
|
+
# @return [String] <description>
|
47
|
+
#
|
48
|
+
def post(path, json: {})
|
49
|
+
HTTP.post(url_for(path), headers: headers, json: json)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class BinaryEdge < Base
|
6
|
+
def initialize(base_url = "https://api.binaryedge.io/v2", api_key:, headers: {})
|
7
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
8
|
+
|
9
|
+
headers["x-key"] = api_key
|
10
|
+
|
11
|
+
super(base_url, headers: headers)
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# @param [String] query String used to query our data
|
16
|
+
# @param [Integer] page Default 1, Maximum: 500
|
17
|
+
# @param [Integer, nil] only_ips If selected, only output IP addresses, ports and protocols.
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
def search(query, page: 1, only_ips: nil)
|
22
|
+
params = {
|
23
|
+
query: query,
|
24
|
+
page: page,
|
25
|
+
only_ips: only_ips
|
26
|
+
}.compact
|
27
|
+
|
28
|
+
res = get("/query/search", params: params)
|
29
|
+
JSON.parse(res.body.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Clients
|
7
|
+
class Censys < Base
|
8
|
+
#
|
9
|
+
# @param [String] base_url
|
10
|
+
# @param [String] id
|
11
|
+
# @param [String] secret
|
12
|
+
# @param [Hash] headers
|
13
|
+
#
|
14
|
+
def initialize(base_url = "https://search.censys.io", id:, secret:, headers: {})
|
15
|
+
raise(ArgumentError, "'id' argument is required") if id.nil?
|
16
|
+
raise(ArgumentError, "'secret' argument is required") if secret.nil?
|
17
|
+
|
18
|
+
headers["authorization"] = "Basic #{Base64.strict_encode64("#{id}:#{secret}")}"
|
19
|
+
|
20
|
+
super(base_url, headers: headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Search current index.
|
25
|
+
#
|
26
|
+
# Searches the given index for all records that match the given query.
|
27
|
+
# For more details, see our documentation: https://search.censys.io/api/v2/docs
|
28
|
+
#
|
29
|
+
# @param [String] query the query to be executed.
|
30
|
+
# @params [Integer, nil] per_page the number of results to be returned for each page.
|
31
|
+
# @params [Integer, nil] cursor the cursor of the desired result set.
|
32
|
+
#
|
33
|
+
# @return [Hash]
|
34
|
+
#
|
35
|
+
def search(query, per_page: nil, cursor: nil)
|
36
|
+
params = { q: query, per_page: per_page, cursor: cursor }.compact
|
37
|
+
res = get("/api/v2/hosts/search", params: params)
|
38
|
+
JSON.parse(res.body.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -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] username
|
11
|
+
# @param [String] 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 [Array<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,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "zlib"
|
5
|
+
|
6
|
+
module Mihari
|
7
|
+
module Clients
|
8
|
+
class DNPedia < Base
|
9
|
+
DEFAULT_HEADERS = {
|
10
|
+
"Accept-Encoding" => "gzip",
|
11
|
+
"Referer" => "https://dnpedia.com/tlds/search.php",
|
12
|
+
"X-Requested-With" => "XMLHttpRequest"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
DEFAULT_PARAMS = {
|
16
|
+
cmd: "search",
|
17
|
+
columns: "id,name,zoneid,length,idn,thedate,",
|
18
|
+
ecf: "name",
|
19
|
+
ecv: "",
|
20
|
+
days: 2,
|
21
|
+
mode: "added",
|
22
|
+
_search: false,
|
23
|
+
nd: 1_569_842_920_216,
|
24
|
+
rows: 500,
|
25
|
+
page: 1,
|
26
|
+
sidx: "length",
|
27
|
+
sord: "asc"
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
#
|
31
|
+
# @param [String] base_url
|
32
|
+
# @param [Hash] headers
|
33
|
+
#
|
34
|
+
def initialize(base_url = "https://dnpedia.com", headers: {})
|
35
|
+
headers = headers.merge(DEFAULT_HEADERS)
|
36
|
+
|
37
|
+
super(base_url, headers: headers)
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# @param [String] keyword
|
42
|
+
#
|
43
|
+
def search(keyword)
|
44
|
+
params = DEFAULT_PARAMS.merge({ ecv: normalize(keyword) })
|
45
|
+
res = get("/tlds/ajax.php", params: params)
|
46
|
+
|
47
|
+
sio = StringIO.new(res.body.to_s)
|
48
|
+
gz = Zlib::GzipReader.new(sio)
|
49
|
+
page = gz.read
|
50
|
+
|
51
|
+
JSON.parse page
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def normalize(word)
|
57
|
+
return word if word.start_with?("~")
|
58
|
+
return word unless word.include?("%")
|
59
|
+
|
60
|
+
"~#{word}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
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,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class GreyNoise < Base
|
6
|
+
def initialize(base_url = "https://api.greynoise.io", api_key:, headers: {})
|
7
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
8
|
+
|
9
|
+
headers["key"] = api_key
|
10
|
+
super(base_url, headers: headers)
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# GNQL (GreyNoise Query Language) is a domain-specific query language that uses Lucene deep under the hood
|
15
|
+
#
|
16
|
+
# @param [String] query GNQL query string
|
17
|
+
# @param [Integer, nil] size Maximum amount of results to grab
|
18
|
+
# @param [Integer, nil] scroll Scroll token to paginate through results
|
19
|
+
#
|
20
|
+
# @return [Hash]
|
21
|
+
#
|
22
|
+
def gnql_search(query, size: nil, scroll: nil)
|
23
|
+
params = { query: query, size: size, scroll: scroll }.compact
|
24
|
+
res = get("/v2/experimental/gnql", params: params)
|
25
|
+
JSON.parse res.body.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
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] 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
|
+
def create_event(payload)
|
19
|
+
res = post("/events/add", json: payload)
|
20
|
+
JSON.parse(res.body.to_s)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class Onyphe < Base
|
6
|
+
attr_reader :api_key
|
7
|
+
|
8
|
+
def initialize(base_url = "https://www.onyphe.io", api_key:, headers: {})
|
9
|
+
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
10
|
+
|
11
|
+
super(base_url, headers: headers)
|
12
|
+
|
13
|
+
@api_key = api_key
|
14
|
+
end
|
15
|
+
|
16
|
+
def datascan(query, page: 1)
|
17
|
+
params = { page: page, apikey: api_key }
|
18
|
+
res = get("/api/v2/simple/datascan/#{query}", params: params)
|
19
|
+
JSON.parse(res.body.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class OTX < Base
|
6
|
+
def initialize(base_url = "https://otx.alienvault.com", api_key:, headers: {})
|
7
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
8
|
+
|
9
|
+
headers["x-otx-api-key"] = api_key
|
10
|
+
super(base_url, headers: headers)
|
11
|
+
end
|
12
|
+
|
13
|
+
def query_by_ip(ip)
|
14
|
+
_get "/api/v1/indicators/IPv4/#{ip}/passive_dns"
|
15
|
+
end
|
16
|
+
|
17
|
+
def query_by_domain(domain)
|
18
|
+
_get "/api/v1/indicators/domain/#{domain}/passive_dns"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def _get(path)
|
24
|
+
res = get(path)
|
25
|
+
JSON.parse(res.body.to_s)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,65 @@
|
|
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] username
|
11
|
+
# @param [String] 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
|
+
def passive_dns_search(query)
|
35
|
+
params = { query: query }
|
36
|
+
_get("/v2/dns/passive/unique", params: params)
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# @param [String] query the domain being queried
|
41
|
+
# @param [String] field whether to return historical results
|
42
|
+
#
|
43
|
+
# @return [Hash]
|
44
|
+
#
|
45
|
+
def reverse_whois_search(query:, field:)
|
46
|
+
params = {
|
47
|
+
query: query,
|
48
|
+
field: field
|
49
|
+
}.compact
|
50
|
+
_get("/v2/whois/search", params: params)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
#
|
56
|
+
# @param [String] path
|
57
|
+
# @param [Hash] params
|
58
|
+
#
|
59
|
+
def _get(path, params: {})
|
60
|
+
res = get(path, params: params)
|
61
|
+
JSON.parse(res.body.to_s)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class PulseDive < Base
|
6
|
+
attr_reader :api_key
|
7
|
+
|
8
|
+
def initialize(base_url = "https://pulsedive.com", api_key:, headers: {})
|
9
|
+
super(base_url, headers: headers)
|
10
|
+
|
11
|
+
@api_key = api_key
|
12
|
+
|
13
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_indicator(ip_or_domain)
|
17
|
+
_get "/api/info.php", params: { indicator: ip_or_domain }
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_properties(indicator_id)
|
21
|
+
_get "/api/info.php", params: { iid: indicator_id, get: "properties" }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# @param [String] path
|
29
|
+
# @param [Hash] params
|
30
|
+
#
|
31
|
+
def _get(path, params: {})
|
32
|
+
params["key"] = api_key
|
33
|
+
|
34
|
+
res = get(path, params: params)
|
35
|
+
JSON.parse(res.body.to_s)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Clients
|
5
|
+
class Shodan < Base
|
6
|
+
attr_reader :api_key
|
7
|
+
|
8
|
+
def initialize(base_url = "https://api.shodan.io", api_key:, headers: {})
|
9
|
+
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
10
|
+
|
11
|
+
super(base_url, headers: headers)
|
12
|
+
|
13
|
+
@api_key = api_key
|
14
|
+
end
|
15
|
+
|
16
|
+
# Search Shodan using the same query syntax as the website and use facets
|
17
|
+
# to get summary information for different properties.
|
18
|
+
def search(query, page: 1, minify: true)
|
19
|
+
params = {
|
20
|
+
query: query,
|
21
|
+
page: page,
|
22
|
+
minify: minify,
|
23
|
+
key: api_key
|
24
|
+
}
|
25
|
+
res = get("/shodan/host/search", params: params)
|
26
|
+
JSON.parse(res.body.to_s)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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] 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
|
+
def alert(json)
|
22
|
+
json = json.to_camelback_keys.compact
|
23
|
+
res = post("/alert", json: json)
|
24
|
+
JSON.parse(res.body.to_s)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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] 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
|
+
def search(q, size: 100, search_after: nil)
|
25
|
+
params = { q: q, size: size, search_after: search_after }.compact
|
26
|
+
res = get("/api/v1/search/", params: params)
|
27
|
+
JSON.parse res.body.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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] id
|
9
|
+
# @param [String] secret
|
10
|
+
# @param [Hash] headers
|
11
|
+
#
|
12
|
+
def initialize(base_url = "https://www.virustotal.com", api_key:, headers: {})
|
13
|
+
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
14
|
+
|
15
|
+
headers["x-apikey"] = api_key
|
16
|
+
|
17
|
+
super(base_url, headers: headers)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# @param [String] query
|
22
|
+
#
|
23
|
+
def domain_search(query)
|
24
|
+
_get("/api/v3/domains/#{query}/resolutions")
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# @param [String] query
|
29
|
+
#
|
30
|
+
def ip_search(query)
|
31
|
+
_get("/api/v3/ip_addresses/#{query}/resolutions")
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# @param [String] query
|
36
|
+
# @param [String, nil] cursor
|
37
|
+
#
|
38
|
+
def intel_search(query, cursor: nil)
|
39
|
+
params = { query: query, cursor: cursor }.compact
|
40
|
+
_get("/api/v3/intelligence/search", params: params)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
#
|
46
|
+
#
|
47
|
+
# @param [String] path
|
48
|
+
# @param [Hash] params
|
49
|
+
#
|
50
|
+
def _get(path, params: {})
|
51
|
+
res = get(path, params: params)
|
52
|
+
JSON.parse(res.body.to_s)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|