mihari 8.2.1 → 8.3.0
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/lib/mihari/analyzers/censys.rb +64 -27
- data/lib/mihari/clients/censys.rb +91 -58
- data/lib/mihari/config.rb +3 -0
- data/lib/mihari/schemas/analyzer.rb +8 -1
- data/lib/mihari/sidekiq/application.rb +2 -1
- data/lib/mihari/structs/censys.rb +278 -184
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/alerts.rb +3 -3
- data/lib/mihari/web/endpoints/artifacts.rb +3 -3
- data/lib/mihari/web/endpoints/ip_addresses.rb +1 -1
- data/lib/mihari/web/endpoints/rules.rb +5 -5
- data/lib/mihari/web/endpoints/tags.rb +1 -1
- data/lib/mihari/web/public/assets/index-DPwW50wG.js +1571 -0
- data/lib/mihari/web/public/assets/index-DZXEbm8D.css +1 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/mihari.gemspec +21 -21
- data/requirements.txt +1 -1
- metadata +42 -42
- data/lib/mihari/web/public/assets/index-Cl2cB52m.js +0 -1621
- data/lib/mihari/web/public/assets/index-DzpJMEJU.css +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b337f264e337771e4d543b77153a57cdd13d5ce8960f75f70fc6fd7c70215903
|
|
4
|
+
data.tar.gz: 5013b97ab578766604d0ce2f1329b2209e1f234d5d018e9b642eda053b8ce4d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 942a3f9dab169e653d6c4a75a32e6bda6f54f35207c356c69d2c7b2412031ff7f2ca5f10c92dc8713cc6d567731a8ea420ad2d21f02ecfd1fc1a853996e881b2
|
|
7
|
+
data.tar.gz: 01e710c03b08ec63f3365a370148e3b1861ea3a961a4a9b53cf5d4eef92b65c7a7afebb623c4f9fb0524c0076b4c0e8c600312fb937b6d2fe30be0c01152418d
|
|
@@ -12,42 +12,69 @@ module Mihari
|
|
|
12
12
|
# @return [String, nil]
|
|
13
13
|
attr_reader :secret
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# @
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
# @return [String, nil]
|
|
16
|
+
attr_reader :pat
|
|
17
|
+
|
|
18
|
+
# @return [String, nil]
|
|
19
|
+
attr_reader :organization_id
|
|
20
|
+
|
|
21
|
+
# @return [Integer, nil]
|
|
22
|
+
attr_reader :version
|
|
23
|
+
|
|
24
|
+
def initialize(query, version: nil, options: nil, id: nil, secret: nil, pat: nil, organization_id: nil)
|
|
22
25
|
super(query, options:)
|
|
23
26
|
|
|
27
|
+
@version = version || Mihari.config.censys_version
|
|
28
|
+
|
|
29
|
+
# v2
|
|
24
30
|
@id = id || Mihari.config.censys_id
|
|
25
31
|
@secret = secret || Mihari.config.censys_secret
|
|
32
|
+
# v3
|
|
33
|
+
@pat = pat || Mihari.config.censys_pat
|
|
34
|
+
@organization_id = organization_id || Mihari.config.censys_organization_id
|
|
26
35
|
end
|
|
27
36
|
|
|
28
|
-
#
|
|
29
|
-
# @return [Array<Mihari::Models::Artifact>]
|
|
30
|
-
#
|
|
31
37
|
def artifacts
|
|
32
|
-
client.search_with_pagination(query, pagination_limit:).
|
|
33
|
-
res.
|
|
34
|
-
end.
|
|
38
|
+
client.search_with_pagination(query, pagination_limit:).flat_map do |res|
|
|
39
|
+
res.artifacts
|
|
40
|
+
end.uniq(&:data)
|
|
35
41
|
end
|
|
36
42
|
|
|
37
|
-
#
|
|
38
|
-
# @return [Boolean]
|
|
39
|
-
#
|
|
40
43
|
def configured?
|
|
41
|
-
|
|
44
|
+
case version
|
|
45
|
+
when 2
|
|
46
|
+
v2_configured?
|
|
47
|
+
when 3
|
|
48
|
+
v3_configured?
|
|
49
|
+
else
|
|
50
|
+
false
|
|
51
|
+
end
|
|
42
52
|
end
|
|
43
53
|
|
|
44
54
|
private
|
|
45
55
|
|
|
46
|
-
#
|
|
47
|
-
# @return [Mihari::Clients::Censys]
|
|
48
|
-
#
|
|
49
56
|
def client
|
|
50
|
-
|
|
57
|
+
case version
|
|
58
|
+
when 2
|
|
59
|
+
v2_client
|
|
60
|
+
when 3
|
|
61
|
+
v3_client
|
|
62
|
+
else
|
|
63
|
+
raise "Unsupported Censys version: #{version}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def v3_client
|
|
68
|
+
Clients::Censys::V3.new(
|
|
69
|
+
pat:,
|
|
70
|
+
organization_id:,
|
|
71
|
+
pagination_interval:,
|
|
72
|
+
timeout:
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def v2_client
|
|
77
|
+
Clients::Censys::V2.new(
|
|
51
78
|
id:,
|
|
52
79
|
secret:,
|
|
53
80
|
pagination_interval:,
|
|
@@ -55,19 +82,29 @@ module Mihari
|
|
|
55
82
|
)
|
|
56
83
|
end
|
|
57
84
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
85
|
+
def v2_configured?
|
|
86
|
+
id? && secret?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def v3_configured?
|
|
90
|
+
pat? && organization_id?
|
|
91
|
+
end
|
|
92
|
+
|
|
61
93
|
def id?
|
|
62
94
|
!id.nil?
|
|
63
95
|
end
|
|
64
96
|
|
|
65
|
-
#
|
|
66
|
-
# @return [Boolean]
|
|
67
|
-
#
|
|
68
97
|
def secret?
|
|
69
98
|
!secret.nil?
|
|
70
99
|
end
|
|
100
|
+
|
|
101
|
+
def pat?
|
|
102
|
+
!pat.nil?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def organization_id?
|
|
106
|
+
!organization_id.nil?
|
|
107
|
+
end
|
|
71
108
|
end
|
|
72
109
|
end
|
|
73
110
|
end
|
|
@@ -7,73 +7,106 @@ module Mihari
|
|
|
7
7
|
#
|
|
8
8
|
# Censys API client
|
|
9
9
|
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
10
|
+
module Censys
|
|
11
|
+
class V2 < Base
|
|
12
|
+
#
|
|
13
|
+
# @param [String] base_url
|
|
14
|
+
# @param [String, nil] id
|
|
15
|
+
# @param [String, nil] secret
|
|
16
|
+
# @param [Hash] headers
|
|
17
|
+
# @param [Integer] pagination_interval
|
|
18
|
+
# @param [Integer, nil] timeout
|
|
19
|
+
#
|
|
20
|
+
def initialize(
|
|
21
|
+
base_url = "https://search.censys.io",
|
|
22
|
+
id:,
|
|
23
|
+
secret:,
|
|
24
|
+
headers: {},
|
|
25
|
+
pagination_interval: Mihari.config.pagination_interval,
|
|
26
|
+
timeout: nil
|
|
27
|
+
)
|
|
28
|
+
raise(ArgumentError, "id is required") if id.nil?
|
|
29
|
+
raise(ArgumentError, "secret is required") if secret.nil?
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
headers["authorization"] = "Basic #{Base64.strict_encode64("#{id}:#{secret}")}"
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
super(base_url, headers:, pagination_interval:, timeout:)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Search current index.
|
|
38
|
+
#
|
|
39
|
+
# Searches the given index for all records that match the given query.
|
|
40
|
+
# For more details, see our documentation: https://search.censys.io/api/v2/docs
|
|
41
|
+
#
|
|
42
|
+
# @param [String] query the query to be executed.
|
|
43
|
+
# @param [Integer, nil] per_page the number of results to be returned for each page.
|
|
44
|
+
# @param [Integer, nil] cursor the cursor of the desired result set.
|
|
45
|
+
#
|
|
46
|
+
# @return [Mihari::Structs::Censys::Response]
|
|
47
|
+
#
|
|
48
|
+
def search(query, per_page: nil, cursor: nil)
|
|
49
|
+
params = {q: query, per_page:, cursor:}.compact
|
|
50
|
+
Structs::Censys::V2::Response.from_dynamic! get_json("/api/v2/hosts/search", params:)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# @param [String] query
|
|
55
|
+
# @param [Integer, nil] per_page
|
|
56
|
+
# @param [Integer] pagination_limit
|
|
57
|
+
#
|
|
58
|
+
# @return [Enumerable<Mihari::Structs::Censys::Response>]
|
|
59
|
+
#
|
|
60
|
+
def search_with_pagination(query, per_page: nil, pagination_limit: Mihari.config.pagination_limit)
|
|
61
|
+
cursor = nil
|
|
62
|
+
|
|
63
|
+
Enumerator.new do |y|
|
|
64
|
+
pagination_limit.times do
|
|
65
|
+
res = search(query, per_page:, cursor:)
|
|
34
66
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Structs::Censys::Response.from_dynamic! get_json("/api/v2/hosts/search", params:)
|
|
67
|
+
y.yield res
|
|
68
|
+
|
|
69
|
+
cursor = res.result.links.next
|
|
70
|
+
# NOTE: Censys's search API is unstable recently
|
|
71
|
+
# it may returns empty links or empty string cursors
|
|
72
|
+
# - Empty links: "links": {}
|
|
73
|
+
# - Empty cursors: "links": { "next": "", "prev": "" }
|
|
74
|
+
# So it needs to check both cases
|
|
75
|
+
break if cursor.nil? || cursor.empty?
|
|
76
|
+
|
|
77
|
+
sleep_pagination_interval
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
50
81
|
end
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
# @return [Enumerable<Mihari::Structs::Censys::Response>]
|
|
58
|
-
#
|
|
59
|
-
def search_with_pagination(query, per_page: nil, pagination_limit: Mihari.config.pagination_limit)
|
|
60
|
-
cursor = nil
|
|
83
|
+
class V3 < Base
|
|
84
|
+
def initialize(base_url = "https://api.platform.censys.io", pat:, organization_id:, headers: {}, pagination_interval: Mihari.config.pagination_interval, timeout: nil)
|
|
85
|
+
raise(ArgumentError, "pat is required") if pat.nil?
|
|
86
|
+
raise(ArgumentError, "organization_id is required") if organization_id.nil?
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
headers["Authorization"] = "Bearer #{pat}"
|
|
89
|
+
headers["Accept"] = "application/vnd.censys.api.v3.host.v1+json"
|
|
90
|
+
headers["X-Organization-ID"] = organization_id
|
|
65
91
|
|
|
66
|
-
|
|
92
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
|
93
|
+
end
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# - Empty cursors: "links": { "next": "", "prev": "" }
|
|
73
|
-
# So it needs to check both cases
|
|
74
|
-
break if cursor.nil? || cursor.empty?
|
|
95
|
+
def search(query, page_size: nil, page_token: nil)
|
|
96
|
+
json = {query: query, page_size:, page_token:}.compact
|
|
97
|
+
Structs::Censys::V3::Response.from_dynamic! post_json("/v3/global/search/query", json:)
|
|
98
|
+
end
|
|
75
99
|
|
|
76
|
-
|
|
100
|
+
def search_with_pagination(query, page_size: nil, pagination_limit: Mihari.config.pagination_limit)
|
|
101
|
+
page_token = nil
|
|
102
|
+
Enumerator.new do |y|
|
|
103
|
+
pagination_limit.times do
|
|
104
|
+
res = search(query, page_size:, page_token:)
|
|
105
|
+
y.yield res
|
|
106
|
+
page_token = res.result&.next_page_token
|
|
107
|
+
break if page_token.nil? || page_token.empty?
|
|
108
|
+
sleep_pagination_interval
|
|
109
|
+
end
|
|
77
110
|
end
|
|
78
111
|
end
|
|
79
112
|
end
|
data/lib/mihari/config.rb
CHANGED
|
@@ -11,6 +11,9 @@ module Mihari
|
|
|
11
11
|
# analyzers, emitters & enrichers
|
|
12
12
|
censys_id: nil,
|
|
13
13
|
censys_secret: nil,
|
|
14
|
+
censys_pat: nil,
|
|
15
|
+
censys_version: 2,
|
|
16
|
+
censys_organization_id: nil,
|
|
14
17
|
circl_passive_password: nil,
|
|
15
18
|
circl_passive_username: nil,
|
|
16
19
|
database_url: URI("sqlite3:mihari.db"),
|
|
@@ -50,9 +50,14 @@ module Mihari
|
|
|
50
50
|
Censys = Dry::Schema.Params do
|
|
51
51
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Censys.keys))
|
|
52
52
|
required(:query).filled(:string)
|
|
53
|
+
optional(:version).value(Types::Coercible::Integer.enum(2, 3)).default(2)
|
|
54
|
+
optional(:options).hash(AnalyzerPaginationOptions)
|
|
55
|
+
# v2
|
|
53
56
|
optional(:id).filled(:string)
|
|
54
57
|
optional(:secret).filled(:string)
|
|
55
|
-
|
|
58
|
+
# v3
|
|
59
|
+
optional(:pat).filled(:string)
|
|
60
|
+
optional(:organization_id).filled(:string)
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
CIRCL = Dry::Schema.Params do
|
|
@@ -82,6 +87,7 @@ module Mihari
|
|
|
82
87
|
Urlscan = Dry::Schema.Params do
|
|
83
88
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Urlscan.keys))
|
|
84
89
|
required(:query).filled(:string)
|
|
90
|
+
optional(:api_key).filled(:string)
|
|
85
91
|
optional(:data_types).filled(array[Types::NetworkDataTypes]).default(Types::NetworkDataTypes.values)
|
|
86
92
|
optional(:options).hash(AnalyzerPaginationOptions)
|
|
87
93
|
end
|
|
@@ -89,6 +95,7 @@ module Mihari
|
|
|
89
95
|
ZoomEye = Dry::Schema.Params do
|
|
90
96
|
required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::ZoomEye.keys))
|
|
91
97
|
required(:query).filled(:string)
|
|
98
|
+
optional(:api_key).filled(:string)
|
|
92
99
|
optional(:data_types).filled(array[Types::NetworkDataTypes]).default(Types::NetworkDataTypes.values)
|
|
93
100
|
optional(:options).hash(AnalyzerPaginationOptions)
|
|
94
101
|
end
|
|
@@ -6,9 +6,10 @@ require "mihari/sidekiq/jobs"
|
|
|
6
6
|
|
|
7
7
|
Sidekiq.configure_server do |config|
|
|
8
8
|
config.redis = {url: Mihari.config.sidekiq_redis_url.to_s}
|
|
9
|
-
config.default_job_options = {retry: Mihari.config.sidekiq_retry}
|
|
10
9
|
end
|
|
11
10
|
|
|
11
|
+
Sidekiq.default_job_options = {retry: Mihari.config.sidekiq_retry}
|
|
12
|
+
|
|
12
13
|
Sidekiq.configure_client do |config|
|
|
13
14
|
config.redis = {url: Mihari.config.sidekiq_redis_url.to_s}
|
|
14
15
|
end
|