mihari 5.1.0 → 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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/.rubocop.yml +6 -0
  4. data/README.md +0 -1
  5. data/lib/mihari/analyzers/base.rb +32 -27
  6. data/lib/mihari/analyzers/binaryedge.rb +17 -9
  7. data/lib/mihari/analyzers/censys.rb +10 -54
  8. data/lib/mihari/analyzers/circl.rb +7 -6
  9. data/lib/mihari/analyzers/crtsh.rb +12 -7
  10. data/lib/mihari/analyzers/dnstwister.rb +7 -7
  11. data/lib/mihari/analyzers/feed.rb +33 -10
  12. data/lib/mihari/analyzers/greynoise.rb +8 -33
  13. data/lib/mihari/analyzers/onyphe.rb +10 -36
  14. data/lib/mihari/analyzers/otx.rb +4 -3
  15. data/lib/mihari/analyzers/passivetotal.rb +8 -7
  16. data/lib/mihari/analyzers/pulsedive.rb +8 -7
  17. data/lib/mihari/analyzers/rule.rb +0 -1
  18. data/lib/mihari/analyzers/securitytrails.rb +8 -10
  19. data/lib/mihari/analyzers/shodan.rb +16 -90
  20. data/lib/mihari/analyzers/urlscan.rb +16 -6
  21. data/lib/mihari/analyzers/virustotal.rb +8 -6
  22. data/lib/mihari/analyzers/virustotal_intelligence.rb +12 -7
  23. data/lib/mihari/analyzers/zoomeye.rb +13 -10
  24. data/lib/mihari/clients/base.rb +53 -0
  25. data/lib/mihari/clients/binaryedge.rb +38 -0
  26. data/lib/mihari/clients/censys.rb +42 -0
  27. data/lib/mihari/clients/circl.rb +59 -0
  28. data/lib/mihari/clients/crtsh.rb +31 -0
  29. data/lib/mihari/clients/dnstwister.rb +40 -0
  30. data/lib/mihari/clients/greynoise.rb +34 -0
  31. data/lib/mihari/clients/misp.rb +29 -0
  32. data/lib/mihari/clients/onyphe.rb +35 -0
  33. data/lib/mihari/clients/otx.rb +49 -0
  34. data/lib/mihari/clients/passivetotal.rb +69 -0
  35. data/lib/mihari/clients/publsedive.rb +56 -0
  36. data/lib/mihari/clients/securitytrails.rb +94 -0
  37. data/lib/mihari/clients/shodan.rb +41 -0
  38. data/lib/mihari/clients/the_hive.rb +33 -0
  39. data/lib/mihari/clients/urlscan.rb +33 -0
  40. data/lib/mihari/clients/virustotal.rb +62 -0
  41. data/lib/mihari/clients/zoomeye.rb +74 -0
  42. data/lib/mihari/commands/database.rb +1 -6
  43. data/lib/mihari/commands/searcher.rb +1 -2
  44. data/lib/mihari/database.rb +9 -0
  45. data/lib/mihari/emitters/misp.rb +13 -20
  46. data/lib/mihari/emitters/the_hive.rb +3 -5
  47. data/lib/mihari/emitters/webhook.rb +2 -2
  48. data/lib/mihari/feed/reader.rb +14 -11
  49. data/lib/mihari/http.rb +29 -21
  50. data/lib/mihari/mixins/retriable.rb +3 -1
  51. data/lib/mihari/schemas/analyzer.rb +5 -4
  52. data/lib/mihari/structs/censys.rb +62 -0
  53. data/lib/mihari/structs/greynoise.rb +43 -0
  54. data/lib/mihari/structs/onyphe.rb +45 -0
  55. data/lib/mihari/structs/shodan.rb +83 -0
  56. data/lib/mihari/version.rb +1 -1
  57. data/lib/mihari/web/middleware/connection_adapter.rb +1 -3
  58. data/lib/mihari/web/public/assets/{index-63900d73.js → index-7d0fb8c4.js} +2 -2
  59. data/lib/mihari/web/public/index.html +1 -1
  60. data/lib/mihari/web/public/redoc-static.html +2 -2
  61. data/lib/mihari.rb +21 -2
  62. data/mihari.gemspec +15 -23
  63. metadata +55 -264
  64. data/lib/mihari/analyzers/clients/otx.rb +0 -36
  65. data/lib/mihari/analyzers/dnpedia.rb +0 -37
  66. data/lib/mihari/mixins/database.rb +0 -16
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "passivetotal"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class PassiveTotal < Base
@@ -18,6 +16,9 @@ module Mihari
18
16
  # @return [String, nil]
19
17
  attr_reader :api_key
20
18
 
19
+ # @return [String]
20
+ attr_reader :query
21
+
21
22
  def initialize(*args, **kwargs)
22
23
  super(*args, **kwargs)
23
24
 
@@ -42,8 +43,8 @@ module Mihari
42
43
  %w[passivetotal_username passivetotal_api_key]
43
44
  end
44
45
 
45
- def api
46
- @api ||= ::PassiveTotal::API.new(username: username, api_key: api_key)
46
+ def client
47
+ @client ||= Clients::PassiveTotal.new(username: username, api_key: api_key)
47
48
  end
48
49
 
49
50
  #
@@ -79,7 +80,7 @@ module Mihari
79
80
  # @return [Array<String>]
80
81
  #
81
82
  def passive_dns_search
82
- res = api.dns.passive_unique(query)
83
+ res = client.passive_dns_search(query)
83
84
  res["results"] || []
84
85
  end
85
86
 
@@ -89,7 +90,7 @@ module Mihari
89
90
  # @return [Array<Mihari::Artifact>]
90
91
  #
91
92
  def reverse_whois_search
92
- res = api.whois.search(query: query, field: "email")
93
+ res = client.reverse_whois_search(query: query, field: "email")
93
94
  results = res["results"] || []
94
95
  results.map do |result|
95
96
  data = result["domain"]
@@ -103,7 +104,7 @@ module Mihari
103
104
  # @return [Array<Mihari::Artifact>]
104
105
  #
105
106
  def ssl_search
106
- res = api.ssl.history(query)
107
+ res = client.ssl_search(query)
107
108
  results = res["results"] || []
108
109
  results.map do |result|
109
110
  data = result["ipAddresses"]
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pulsedive"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class Pulsedive < Base
@@ -15,6 +13,9 @@ module Mihari
15
13
  # @return [String, nil]
16
14
  attr_reader :api_key
17
15
 
16
+ # @return [Integer]
17
+ attr_reader :query
18
+
18
19
  def initialize(*args, **kwargs)
19
20
  super
20
21
 
@@ -34,8 +35,8 @@ module Mihari
34
35
  %w[pulsedive_api_key]
35
36
  end
36
37
 
37
- def api
38
- @api ||= ::Pulsedive::API.new(api_key)
38
+ def client
39
+ @client ||= Clients::PulseDive.new(api_key: api_key)
39
40
  end
40
41
 
41
42
  #
@@ -55,12 +56,12 @@ module Mihari
55
56
  def search
56
57
  raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
57
58
 
58
- indicator = api.indicator.get_by_value(query)
59
+ indicator = client.get_indicator(query)
59
60
  iid = indicator["iid"]
60
61
 
61
- properties = api.indicator.get_properties_by_id(iid)
62
+ properties = client.get_properties(iid)
62
63
  (properties["dns"] || []).filter_map do |property|
63
- if ["A", "PTR"].include?(property["name"])
64
+ if %w[A PTR].include?(property["name"])
64
65
  nil
65
66
  else
66
67
  data = property["value"]
@@ -7,7 +7,6 @@ module Mihari
7
7
  "censys" => Censys,
8
8
  "circl" => CIRCL,
9
9
  "crtsh" => Crtsh,
10
- "dnpedia" => DNPedia,
11
10
  "dnstwister" => DNSTwister,
12
11
  "feed" => Feed,
13
12
  "greynoise" => GreyNoise,
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securitytrails"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class SecurityTrails < Base
@@ -15,6 +13,9 @@ module Mihari
15
13
  # @return [String, nil]
16
14
  attr_reader :api_key
17
15
 
16
+ # @return [String]
17
+ attr_reader :query
18
+
18
19
  def initialize(*args, **kwargs)
19
20
  super
20
21
 
@@ -34,8 +35,8 @@ module Mihari
34
35
  %w[securitytrails_api_key]
35
36
  end
36
37
 
37
- def api
38
- @api ||= ::SecurityTrails::API.new(api_key)
38
+ def client
39
+ @client ||= Clients::SecurityTrails.new(api_key: api_key)
39
40
  end
40
41
 
41
42
  #
@@ -71,8 +72,7 @@ module Mihari
71
72
  # @return [Array<String>]
72
73
  #
73
74
  def domain_search
74
- result = api.history.get_all_dns_history(query, type: "a")
75
- records = result["records"] || []
75
+ records = client.get_all_dns_history(query, type: "a")
76
76
  records.map do |record|
77
77
  (record["values"] || []).map { |value| value["ip"] }
78
78
  end.flatten.compact.uniq
@@ -84,8 +84,7 @@ module Mihari
84
84
  # @return [Array<Mihari::Artifact>]
85
85
  #
86
86
  def ip_search
87
- result = api.domains.search(filter: { ipv4: query })
88
- records = result["records"] || []
87
+ records = client.search_by_ip(query)
89
88
  records.filter_map do |record|
90
89
  data = record["hostname"]
91
90
  Artifact.new(data: data, source: source, metadata: record)
@@ -98,8 +97,7 @@ module Mihari
98
97
  # @return [Array<String>]
99
98
  #
100
99
  def mail_search
101
- result = api.domains.search(filter: { whois_email: query })
102
- records = result["records"] || []
100
+ records = client.search_by_mail(query)
103
101
  records.filter_map do |record|
104
102
  data = record["hostname"]
105
103
  Artifact.new(data: data, source: source, metadata: record)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "shodan"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class Shodan < Base
@@ -12,6 +10,12 @@ module Mihari
12
10
  # @return [String, nil]
13
11
  attr_reader :api_key
14
12
 
13
+ # @return [Integer]
14
+ attr_reader :interval
15
+
16
+ # @return [String]
17
+ attr_reader :query
18
+
15
19
  def initialize(*args, **kwargs)
16
20
  super(*args, **kwargs)
17
21
 
@@ -20,13 +24,9 @@ module Mihari
20
24
 
21
25
  def artifacts
22
26
  results = search
23
- return [] unless results || results.empty?
27
+ return [] if results.empty?
24
28
 
25
- results = results.map { |result| Structs::Shodan::Result.from_dynamic!(result) }
26
- matches = results.map { |result| result.matches || [] }.flatten
27
-
28
- uniq_matches = matches.uniq(&:ip_str)
29
- uniq_matches.map { |match| build_artifact(match, matches) }
29
+ results.map { |result| result.to_artifacts(source) }.flatten.uniq(&:data)
30
30
  end
31
31
 
32
32
  private
@@ -37,40 +37,32 @@ module Mihari
37
37
  %w[shodan_api_key]
38
38
  end
39
39
 
40
- def api
41
- @api ||= ::Shodan::API.new(key: api_key)
40
+ def client
41
+ @client ||= Clients::Shodan.new(api_key: api_key)
42
42
  end
43
43
 
44
44
  #
45
45
  # Search with pagination
46
46
  #
47
- # @param [String] query
48
47
  # @param [Integer] page
49
48
  #
50
- # @return [Hash]
49
+ # @return [Structs::Shodan::Result]
51
50
  #
52
- def search_with_page(query, page: 1)
53
- api.host.search(query, page: page)
54
- rescue ::Shodan::Error => e
55
- raise RetryableError, e if e.message.include?("request timed out")
56
-
57
- raise e
51
+ def search_with_page(page: 1)
52
+ client.search(query, page: page)
58
53
  end
59
54
 
60
55
  #
61
56
  # Search
62
57
  #
63
- # @return [Array<Hash>]
58
+ # @return [Array<Structs::Shodan::Result>]
64
59
  #
65
60
  def search
66
61
  responses = []
67
62
  (1..Float::INFINITY).each do |page|
68
- res = search_with_page(query, page: page)
69
-
70
- break unless res
71
-
63
+ res = search_with_page(page: page)
72
64
  responses << res
73
- break if res["total"].to_i <= page * PAGE_SIZE
65
+ break if res.total <= page * PAGE_SIZE
74
66
 
75
67
  # sleep #{interval} seconds to avoid the rate limitation (if it is set)
76
68
  sleep interval
@@ -82,42 +74,6 @@ module Mihari
82
74
  responses
83
75
  end
84
76
 
85
- #
86
- # Collect metadata from matches
87
- #
88
- # @param [Array<Structs::Shodan::Match>] matches
89
- # @param [String] ip
90
- #
91
- # @return [Array<Hash>]
92
- #
93
- def collect_metadata_by_ip(matches, ip)
94
- matches.select { |match| match.ip_str == ip }.map(&:metadata)
95
- end
96
-
97
- #
98
- # Collect ports from matches
99
- #
100
- # @param [Array<Structs::Shodan::Match>] matches
101
- # @param [String] ip
102
- #
103
- # @return [Array<String>]
104
- #
105
- def collect_ports_by_ip(matches, ip)
106
- matches.select { |match| match.ip_str == ip }.map(&:port)
107
- end
108
-
109
- #
110
- # Collect hostnames from matches
111
- #
112
- # @param [Array<Structs::Shodan::Match>] matches
113
- # @param [String] ip
114
- #
115
- # @return [Array<String>]
116
- #
117
- def collect_hostnames_by_ip(matches, ip)
118
- matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
119
- end
120
-
121
77
  #
122
78
  # Build an artifact from a Shodan search API response
123
79
  #
@@ -127,36 +83,6 @@ module Mihari
127
83
  # @return [Artifact]
128
84
  #
129
85
  def build_artifact(match, matches)
130
- as = nil
131
- as = AutonomousSystem.new(asn: normalize_asn(match.asn)) unless match.asn.nil?
132
-
133
- geolocation = nil
134
- if !match.location.country_name.nil? && !match.location.country_code.nil?
135
- geolocation = Geolocation.new(
136
- country: match.location.country_name,
137
- country_code: match.location.country_code
138
- )
139
- end
140
-
141
- metadata = collect_metadata_by_ip(matches, match.ip_str)
142
-
143
- ports = collect_ports_by_ip(matches, match.ip_str).map do |port|
144
- Port.new(port: port)
145
- end
146
-
147
- reverse_dns_names = collect_hostnames_by_ip(matches, match.ip_str).map do |name|
148
- ReverseDnsName.new(name: name)
149
- end
150
-
151
- Artifact.new(
152
- data: match.ip_str,
153
- source: source,
154
- metadata: metadata,
155
- autonomous_system: as,
156
- geolocation: geolocation,
157
- ports: ports,
158
- reverse_dns_names: reverse_dns_names
159
- )
160
86
  end
161
87
  end
162
88
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "urlscan"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class Urlscan < Base
@@ -17,10 +15,22 @@ module Mihari
17
15
  # @return [String, nil]
18
16
  attr_reader :api_key
19
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
+
20
27
  def initialize(*args, **kwargs)
21
28
  super
22
29
 
23
- raise InvalidInputError, "allowed_data_types should be any of url, domain and ip." unless valid_alllowed_data_types?
30
+ unless valid_alllowed_data_types?
31
+ raise InvalidInputError,
32
+ "allowed_data_types should be any of url, domain and ip."
33
+ end
24
34
 
25
35
  @api_key = kwargs[:api_key] || Mihari.config.urlscan_api_key
26
36
  end
@@ -44,8 +54,8 @@ module Mihari
44
54
  %w[urlscan_api_key]
45
55
  end
46
56
 
47
- def api
48
- @api ||= ::UrlScan::API.new(api_key)
57
+ def client
58
+ @client ||= Clients::UrlScan.new(api_key: api_key)
49
59
  end
50
60
 
51
61
  #
@@ -54,7 +64,7 @@ module Mihari
54
64
  # @return [Structs::Urlscan::Response]
55
65
  #
56
66
  def search_with_search_after(search_after: nil)
57
- res = api.search(query, size: SIZE, search_after: search_after)
67
+ res = client.search(query, size: SIZE, search_after: search_after)
58
68
  Structs::Urlscan::Response.from_dynamic! res
59
69
  end
60
70
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "virustotal"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class VirusTotal < Base
@@ -9,11 +7,15 @@ module Mihari
9
7
 
10
8
  param :query
11
9
 
10
+ # @return [String]
12
11
  attr_reader :type
13
12
 
14
13
  # @return [String, nil]
15
14
  attr_reader :api_key
16
15
 
16
+ # @return [String]
17
+ attr_reader :query
18
+
17
19
  def initialize(*args, **kwargs)
18
20
  super(*args, **kwargs)
19
21
 
@@ -33,8 +35,8 @@ module Mihari
33
35
  %w[virustotal_api_key]
34
36
  end
35
37
 
36
- def api
37
- @api = ::VirusTotal::API.new(key: api_key)
38
+ def client
39
+ @client = Clients::VirusTotal.new(api_key: api_key)
38
40
  end
39
41
 
40
42
  #
@@ -68,7 +70,7 @@ module Mihari
68
70
  # @return [Array<Mihari::Artifact>]
69
71
  #
70
72
  def domain_search
71
- res = api.domain.resolutions(query)
73
+ res = client.domain_search(query)
72
74
 
73
75
  data = res["data"] || []
74
76
  data.filter_map do |item|
@@ -83,7 +85,7 @@ module Mihari
83
85
  # @return [Array<Mihari::Artifact>]
84
86
  #
85
87
  def ip_search
86
- res = api.ip_address.resolutions(query)
88
+ res = client.ip_search(query)
87
89
 
88
90
  data = res["data"] || []
89
91
  data.filter_map do |item|
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "virustotal"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class VirusTotalIntelligence < Base
@@ -12,6 +10,12 @@ module Mihari
12
10
  # @return [String, nil]
13
11
  attr_reader :api_key
14
12
 
13
+ # @return [String]
14
+ attr_reader :query
15
+
16
+ # @return [Integer]
17
+ attr_reader :interval
18
+
15
19
  def initialize(*args, **kwargs)
16
20
  super
17
21
 
@@ -21,7 +25,7 @@ module Mihari
21
25
  end
22
26
 
23
27
  def artifacts
24
- responses = search_witgh_cursor
28
+ responses = search_with_cursor
25
29
  responses.map do |response|
26
30
  response.data.map do |datum|
27
31
  Artifact.new(data: datum.value, source: source, metadata: datum.metadata)
@@ -40,8 +44,8 @@ module Mihari
40
44
  #
41
45
  # @return [::VirusTotal::API]
42
46
  #
43
- def api
44
- @api = ::VirusTotal::API.new(key: api_key)
47
+ def client
48
+ @client = Clients::VirusTotal.new(api_key: api_key)
45
49
  end
46
50
 
47
51
  #
@@ -49,12 +53,13 @@ module Mihari
49
53
  #
50
54
  # @return [Array<Structs::VirusTotalIntelligence::Response>]
51
55
  #
52
- def search_witgh_cursor
56
+ def search_with_cursor
53
57
  cursor = nil
54
58
  responses = []
55
59
 
56
60
  loop do
57
- response = Structs::VirusTotalIntelligence::Response.from_dynamic!(api.intelligence.search(query, cursor: cursor))
61
+ response = Structs::VirusTotalIntelligence::Response.from_dynamic!(client.intel_search(query,
62
+ cursor: cursor))
58
63
  responses << response
59
64
 
60
65
  break if response.meta.cursor.nil?
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "zoomeye"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class ZoomEye < Base
@@ -14,6 +12,15 @@ module Mihari
14
12
  # @return [String, nil]
15
13
  attr_reader :api_key
16
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
+
17
24
  def initialize(*args, **kwargs)
18
25
  super(*args, **kwargs)
19
26
 
@@ -48,8 +55,8 @@ module Mihari
48
55
  %w[zoomeye_api_key]
49
56
  end
50
57
 
51
- def api
52
- @api ||= ::ZoomEye::API.new(api_key: api_key)
58
+ def client
59
+ @client ||= Clients::ZoomEye.new(api_key: api_key)
53
60
  end
54
61
 
55
62
  #
@@ -83,9 +90,7 @@ module Mihari
83
90
  # @return [Hash, nil]
84
91
  #
85
92
  def _host_search(query, page: 1)
86
- api.host.search(query, page: page)
87
- rescue ::ZoomEye::Error => _e
88
- nil
93
+ client.host_search(query, page: page)
89
94
  end
90
95
 
91
96
  #
@@ -118,9 +123,7 @@ module Mihari
118
123
  # @return [Hash, nil]
119
124
  #
120
125
  def _web_search(query, page: 1)
121
- api.web.search(query, page: page)
122
- rescue ::ZoomEye::Error => _e
123
- nil
126
+ client.web_search(query, page: page)
124
127
  end
125
128
 
126
129
  #
@@ -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,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Clients
5
+ class BinaryEdge < 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.binaryedge.io/v2", api_key:, headers: {})
12
+ raise(ArgumentError, "'api_key' argument is required") unless api_key
13
+
14
+ headers["x-key"] = api_key
15
+
16
+ super(base_url, headers: headers)
17
+ end
18
+
19
+ #
20
+ # @param [String] query String used to query our data
21
+ # @param [Integer] page Default 1, Maximum: 500
22
+ # @param [Integer, nil] only_ips If selected, only output IP addresses, ports and protocols.
23
+ #
24
+ # @return [Hash]
25
+ #
26
+ def search(query, page: 1, only_ips: nil)
27
+ params = {
28
+ query: query,
29
+ page: page,
30
+ only_ips: only_ips
31
+ }.compact
32
+
33
+ res = get("/query/search", params: params)
34
+ JSON.parse(res.body.to_s)
35
+ end
36
+ end
37
+ end
38
+ 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, nil] id
11
+ # @param [String, nil] 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 [Structs::Censys::Response]
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
+ Structs::Censys::Response.from_dynamic! JSON.parse(res.body.to_s)
39
+ end
40
+ end
41
+ end
42
+ end