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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5415ee0f5bb820e8073383e4771589bbbaa39fc313af3aa434c5754f34bb9056
|
4
|
+
data.tar.gz: a4da4ce859fa718c900572b865518110f92649698a76ca64c55e38ebad2f9856
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0b95e672f17d5ba0035624b035da278c863dc0b1a0860e3393cd8f001125964270233da7bf5ffb08c9057889b4a51698829de059089ed4d31a0f4fd7d0aa3e8
|
7
|
+
data.tar.gz: 30cbba0170104212e9244dfeac820a1a223468bae940719c242778d5bffd95b147f482548aebcc2386713fe43cc0c1b97bf2d71d08d318ad62a4b1a707f3e605
|
data/.gitmodules
CHANGED
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -45,7 +45,6 @@ Mihari supports the following services by default.
|
|
45
45
|
- [Censys](http://censys.io)
|
46
46
|
- [CIRCL passive DNS](https://www.circl.lu/services/passive-dns/) / [passive SSL](https://www.circl.lu/services/passive-ssl/)
|
47
47
|
- [crt.sh](https://crt.sh/)
|
48
|
-
- [DN Pedia](https://dnpedia.com/)
|
49
48
|
- [dnstwister](https://dnstwister.report/)
|
50
49
|
- [GreyNoise](https://www.greynoise.io/)
|
51
50
|
- [Onyphe](https://onyphe.io)
|
@@ -7,7 +7,6 @@ module Mihari
|
|
7
7
|
|
8
8
|
option :rule, default: proc {}
|
9
9
|
|
10
|
-
include Mixins::AutonomousSystem
|
11
10
|
include Mixins::Configurable
|
12
11
|
include Mixins::Retriable
|
13
12
|
|
@@ -20,6 +19,15 @@ module Mihari
|
|
20
19
|
@base_time = Time.now.utc
|
21
20
|
end
|
22
21
|
|
22
|
+
#
|
23
|
+
# Load/overwrite rule
|
24
|
+
#
|
25
|
+
# @param [String] path_or_id
|
26
|
+
#
|
27
|
+
def load_rule(path_or_id)
|
28
|
+
@rule = Structs::Rule.from_path_or_id path_or_id
|
29
|
+
end
|
30
|
+
|
23
31
|
# @return [Array<String>, Array<Mihari::Artifact>]
|
24
32
|
def artifacts
|
25
33
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
@@ -30,35 +38,41 @@ module Mihari
|
|
30
38
|
self.class.to_s.split("::").last.to_s
|
31
39
|
end
|
32
40
|
|
41
|
+
# @return [String]
|
42
|
+
def class_name
|
43
|
+
self.class.to_s.split("::").last
|
44
|
+
end
|
45
|
+
|
33
46
|
#
|
34
47
|
# Set artifacts & run emitters in parallel
|
35
48
|
#
|
36
49
|
# @return [Mihari::Alert, nil]
|
37
50
|
#
|
38
51
|
def run
|
39
|
-
unless configured?
|
40
|
-
class_name = self.class.to_s.split("::").last
|
41
|
-
raise ConfigurationError, "#{class_name} is not configured correctly"
|
42
|
-
end
|
43
|
-
|
44
|
-
set_enriched_artifacts
|
45
|
-
|
46
|
-
responses = Parallel.map(valid_emitters) do |emitter|
|
47
|
-
run_emitter emitter
|
48
|
-
end
|
52
|
+
raise ConfigurationError, "#{class_name} is not configured correctly" unless configured?
|
49
53
|
|
54
|
+
alert_or_something = bulk_emit
|
50
55
|
# returns Mihari::Alert created by the database emitter
|
51
|
-
|
56
|
+
alert_or_something.find { |res| res.is_a?(Mihari::Alert) }
|
52
57
|
end
|
53
58
|
|
54
59
|
#
|
55
|
-
#
|
60
|
+
# Bulk emit
|
61
|
+
#
|
62
|
+
# @return [Array<Mihari::Alert>]
|
63
|
+
#
|
64
|
+
def bulk_emit
|
65
|
+
Parallel.map(valid_emitters) { |emitter| emit emitter }.compact
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Emit an alert
|
56
70
|
#
|
57
71
|
# @param [Mihari::Emitters::Base] emitter
|
58
72
|
#
|
59
73
|
# @return [Mihari::Alert, nil]
|
60
74
|
#
|
61
|
-
def
|
75
|
+
def emit(emitter)
|
62
76
|
return if enriched_artifacts.empty?
|
63
77
|
|
64
78
|
alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
|
@@ -80,6 +94,7 @@ module Mihari
|
|
80
94
|
#
|
81
95
|
# Normalize artifacts
|
82
96
|
# - Convert data (string) into an artifact
|
97
|
+
# - Set rule ID
|
83
98
|
# - Reject an invalid artifact
|
84
99
|
# - Uniquefy artifacts by data
|
85
100
|
#
|
@@ -89,17 +104,16 @@ module Mihari
|
|
89
104
|
@normalized_artifacts ||= artifacts.compact.sort.map do |artifact|
|
90
105
|
# No need to set data_type manually
|
91
106
|
# It is set automatically in #initialize
|
92
|
-
artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
|
93
|
-
end.select(&:valid?).uniq(&:data).map do |artifact|
|
107
|
+
artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
|
94
108
|
artifact.rule_id = rule&.id
|
95
109
|
artifact
|
96
|
-
end
|
110
|
+
end.select(&:valid?).uniq(&:data)
|
97
111
|
end
|
98
112
|
|
99
113
|
private
|
100
114
|
|
101
115
|
#
|
102
|
-
# Uniquefy artifacts
|
116
|
+
# Uniquefy artifacts (assure rule level uniqueness)
|
103
117
|
#
|
104
118
|
# @return [Array<Mihari::Artifact>]
|
105
119
|
#
|
@@ -121,15 +135,6 @@ module Mihari
|
|
121
135
|
end
|
122
136
|
end
|
123
137
|
|
124
|
-
#
|
125
|
-
# Set enriched artifacts
|
126
|
-
#
|
127
|
-
# @return [nil]
|
128
|
-
#
|
129
|
-
def set_enriched_artifacts
|
130
|
-
retry_on_error { enriched_artifacts }
|
131
|
-
end
|
132
|
-
|
133
138
|
#
|
134
139
|
# Select valid emitters
|
135
140
|
#
|
@@ -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(*args, **kwargs)
|
15
21
|
|
@@ -41,7 +47,7 @@ module Mihari
|
|
41
47
|
#
|
42
48
|
# @return [Hash]
|
43
49
|
#
|
44
|
-
def search_with_page(
|
50
|
+
def search_with_page(page: 1)
|
45
51
|
client.search(query, page: page)
|
46
52
|
rescue UnsuccessfulStatusCodeError => e
|
47
53
|
raise RetryableError, e if e.message.include?("Request time limit exceeded")
|
@@ -57,7 +63,7 @@ module Mihari
|
|
57
63
|
def search
|
58
64
|
responses = []
|
59
65
|
(1..500).each do |page|
|
60
|
-
res = search_with_page(
|
66
|
+
res = search_with_page(page: page)
|
61
67
|
total = res["total"].to_i
|
62
68
|
|
63
69
|
responses << res
|
@@ -13,6 +13,12 @@ module Mihari
|
|
13
13
|
# @return [String, nil]
|
14
14
|
attr_reader :secret
|
15
15
|
|
16
|
+
# @return [Integer]
|
17
|
+
attr_reader :interval
|
18
|
+
|
19
|
+
# @return [String]
|
20
|
+
attr_reader :query
|
21
|
+
|
16
22
|
def initialize(*args, **kwargs)
|
17
23
|
super(*args, **kwargs)
|
18
24
|
|
@@ -41,10 +47,7 @@ module Mihari
|
|
41
47
|
cursor = nil
|
42
48
|
loop do
|
43
49
|
response = client.search(query, cursor: cursor)
|
44
|
-
|
45
|
-
|
46
|
-
artifacts << response_to_artifacts(response)
|
47
|
-
|
50
|
+
artifacts << response.result.to_artifacts(source)
|
48
51
|
cursor = response.result.links.next
|
49
52
|
break if cursor == ""
|
50
53
|
|
@@ -55,51 +58,6 @@ module Mihari
|
|
55
58
|
artifacts.flatten.uniq(&:data)
|
56
59
|
end
|
57
60
|
|
58
|
-
#
|
59
|
-
# Extract IPv4s from Censys search API response
|
60
|
-
#
|
61
|
-
# @param [Structs::Censys::Response] response
|
62
|
-
#
|
63
|
-
# @return [Array<String>]
|
64
|
-
#
|
65
|
-
def response_to_artifacts(response)
|
66
|
-
response.result.hits.map { |hit| build_artifact(hit) }
|
67
|
-
end
|
68
|
-
|
69
|
-
#
|
70
|
-
# Build an artifact from a Shodan search API response
|
71
|
-
#
|
72
|
-
# @param [Structs::Censys::Hit] hit
|
73
|
-
#
|
74
|
-
# @return [Artifact]
|
75
|
-
#
|
76
|
-
def build_artifact(hit)
|
77
|
-
as = AutonomousSystem.new(asn: normalize_asn(hit.autonomous_system.asn))
|
78
|
-
|
79
|
-
# sometimes Censys overlooks country
|
80
|
-
# then set geolocation as nil
|
81
|
-
geolocation = nil
|
82
|
-
unless hit.location.country.nil?
|
83
|
-
geolocation = Geolocation.new(
|
84
|
-
country: hit.location.country,
|
85
|
-
country_code: hit.location.country_code
|
86
|
-
)
|
87
|
-
end
|
88
|
-
|
89
|
-
ports = hit.services.map(&:port).map do |port|
|
90
|
-
Port.new(port: port)
|
91
|
-
end
|
92
|
-
|
93
|
-
Artifact.new(
|
94
|
-
data: hit.ip,
|
95
|
-
source: source,
|
96
|
-
metadata: hit.metadata,
|
97
|
-
autonomous_system: as,
|
98
|
-
geolocation: geolocation,
|
99
|
-
ports: ports
|
100
|
-
)
|
101
|
-
end
|
102
|
-
|
103
61
|
def configuration_keys
|
104
62
|
%w[censys_id censys_secret]
|
105
63
|
end
|
@@ -16,6 +16,9 @@ module Mihari
|
|
16
16
|
# @return [String, nil]
|
17
17
|
attr_reader :password
|
18
18
|
|
19
|
+
# @return [String]
|
20
|
+
attr_reader :query
|
21
|
+
|
19
22
|
def initialize(*args, **kwargs)
|
20
23
|
super
|
21
24
|
|
@@ -66,7 +69,7 @@ module Mihari
|
|
66
69
|
# @return [Array<String>]
|
67
70
|
#
|
68
71
|
def passive_dns_search
|
69
|
-
results = client.dns_query(
|
72
|
+
results = client.dns_query(query)
|
70
73
|
results.filter_map do |result|
|
71
74
|
type = result["rrtype"]
|
72
75
|
(type == "A") ? result["rdata"] : nil
|
@@ -79,7 +82,7 @@ module Mihari
|
|
79
82
|
# @return [Array<String>]
|
80
83
|
#
|
81
84
|
def passive_ssl_search
|
82
|
-
result = client.ssl_cquery(
|
85
|
+
result = client.ssl_cquery(query)
|
83
86
|
seen = result["seen"] || []
|
84
87
|
seen.uniq
|
85
88
|
end
|
@@ -16,6 +16,27 @@ module Mihari
|
|
16
16
|
|
17
17
|
option :selector, default: proc { "" }
|
18
18
|
|
19
|
+
# @return [Hash, nil]
|
20
|
+
attr_reader :data
|
21
|
+
|
22
|
+
# @return [Hash, nil]
|
23
|
+
attr_reader :json
|
24
|
+
|
25
|
+
# @return [Hash, nil]
|
26
|
+
attr_reader :params
|
27
|
+
|
28
|
+
# @return [Hash, nil]
|
29
|
+
attr_reader :headers
|
30
|
+
|
31
|
+
# @return [String]
|
32
|
+
attr_reader :method
|
33
|
+
|
34
|
+
# @return [String]
|
35
|
+
attr_reader :selector
|
36
|
+
|
37
|
+
# @return [String]
|
38
|
+
attr_reader :query
|
39
|
+
|
19
40
|
def artifacts
|
20
41
|
Mihari::Feed::Parser.new(results).parse selector
|
21
42
|
end
|
@@ -8,6 +8,9 @@ module Mihari
|
|
8
8
|
# @return [String, nil]
|
9
9
|
attr_reader :api_key
|
10
10
|
|
11
|
+
# @return [String]
|
12
|
+
attr_reader :query
|
13
|
+
|
11
14
|
def initialize(*args, **kwargs)
|
12
15
|
super(*args, **kwargs)
|
13
16
|
|
@@ -15,10 +18,8 @@ module Mihari
|
|
15
18
|
end
|
16
19
|
|
17
20
|
def artifacts
|
18
|
-
res =
|
19
|
-
res.
|
20
|
-
build_artifact datum
|
21
|
-
end
|
21
|
+
res = search
|
22
|
+
res.to_artifacts
|
22
23
|
end
|
23
24
|
|
24
25
|
private
|
@@ -41,30 +42,6 @@ module Mihari
|
|
41
42
|
def search
|
42
43
|
client.gnql_search(query, size: PAGE_SIZE)
|
43
44
|
end
|
44
|
-
|
45
|
-
#
|
46
|
-
# Build an artifact from a GreyNoise search API response
|
47
|
-
#
|
48
|
-
# @param [Structs::GreyNoise::Datum] datum
|
49
|
-
#
|
50
|
-
# @return [Artifact]
|
51
|
-
#
|
52
|
-
def build_artifact(datum)
|
53
|
-
as = AutonomousSystem.new(asn: normalize_asn(datum.metadata.asn))
|
54
|
-
|
55
|
-
geolocation = Geolocation.new(
|
56
|
-
country: datum.metadata.country,
|
57
|
-
country_code: datum.metadata.country_code
|
58
|
-
)
|
59
|
-
|
60
|
-
Artifact.new(
|
61
|
-
data: datum.ip,
|
62
|
-
source: source,
|
63
|
-
metadata: datum.metadata_,
|
64
|
-
autonomous_system: as,
|
65
|
-
geolocation: geolocation
|
66
|
-
)
|
67
|
-
end
|
68
45
|
end
|
69
46
|
end
|
70
47
|
end
|
@@ -12,6 +12,12 @@ 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 [Integer]
|
19
|
+
attr_reader :interval
|
20
|
+
|
15
21
|
def initialize(*args, **kwargs)
|
16
22
|
super(*args, **kwargs)
|
17
23
|
|
@@ -22,10 +28,7 @@ module Mihari
|
|
22
28
|
responses = search
|
23
29
|
return [] unless responses
|
24
30
|
|
25
|
-
|
26
|
-
results.map do |result|
|
27
|
-
build_artifact result
|
28
|
-
end
|
31
|
+
responses.map { |response| response.to_artifacts(source) }.flatten
|
29
32
|
end
|
30
33
|
|
31
34
|
private
|
@@ -49,8 +52,7 @@ module Mihari
|
|
49
52
|
# @return [Structs::Onyphe::Response]
|
50
53
|
#
|
51
54
|
def search_with_page(query, page: 1)
|
52
|
-
|
53
|
-
Structs::Onyphe::Response.from_dynamic!(res)
|
55
|
+
client.datascan(query, page: page)
|
54
56
|
end
|
55
57
|
|
56
58
|
#
|
@@ -72,33 +74,6 @@ module Mihari
|
|
72
74
|
end
|
73
75
|
responses
|
74
76
|
end
|
75
|
-
|
76
|
-
#
|
77
|
-
# Build an artifact from an Onyphe search API result
|
78
|
-
#
|
79
|
-
# @param [Structs::Onyphe::Result] result
|
80
|
-
#
|
81
|
-
# @return [Artifact]
|
82
|
-
#
|
83
|
-
def build_artifact(result)
|
84
|
-
as = AutonomousSystem.new(asn: normalize_asn(result.asn))
|
85
|
-
|
86
|
-
geolocation = nil
|
87
|
-
unless result.country_code.nil?
|
88
|
-
geolocation = Geolocation.new(
|
89
|
-
country: NormalizeCountry(result.country_code, to: :short),
|
90
|
-
country_code: result.country_code
|
91
|
-
)
|
92
|
-
end
|
93
|
-
|
94
|
-
Artifact.new(
|
95
|
-
data: result.ip,
|
96
|
-
source: source,
|
97
|
-
metadata: result.metadata,
|
98
|
-
autonomous_system: as,
|
99
|
-
geolocation: geolocation
|
100
|
-
)
|
101
|
-
end
|
102
77
|
end
|
103
78
|
end
|
104
79
|
end
|
data/lib/mihari/analyzers/otx.rb
CHANGED
@@ -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
|
38
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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)
|