mihari 4.5.0 → 4.5.3
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 +6 -1
- data/lib/mihari/analyzers/rule.rb +6 -3
- data/lib/mihari/analyzers/shodan.rb +39 -5
- data/lib/mihari/enrichers/ipinfo.rb +1 -1
- data/lib/mihari/enrichers/shodan.rb +1 -1
- data/lib/mihari/errors.rb +10 -2
- data/lib/mihari/http.rb +7 -1
- data/lib/mihari/schemas/analyzer.rb +2 -2
- data/lib/mihari/schemas/emitter.rb +1 -1
- data/lib/mihari/structs/censys.rb +14 -1
- data/lib/mihari/structs/rule.rb +3 -0
- data/lib/mihari/structs/shodan.rb +2 -0
- data/lib/mihari/types.rb +2 -4
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/app.rb +7 -1
- data/lib/mihari/web/endpoints/rules.rb +2 -1
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +317 -314
- data/lib/mihari/web/public/static/css/{chunk-vendors.da2a7bfc.css → chunk-vendors.06251949.css} +2 -2
- data/lib/mihari/web/public/static/fonts/{fa-brands-400.f7223235.ttf → fa-brands-400.7fa789ab.ttf} +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.859fc388.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.2ffd018f.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/{fa-regular-400.a7fde52b.ttf → fa-regular-400.da02cb7e.ttf} +0 -0
- data/lib/mihari/web/public/static/fonts/{fa-solid-900.5b03221c.ttf → fa-solid-900.3a463ec3.ttf} +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.40ddefd7.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/{fa-v4compatibility.42932bea.ttf → fa-v4compatibility.924588dc.ttf} +0 -0
- data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js +2 -0
- data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js.map +1 -0
- data/lib/mihari/web/public/static/js/app.823b5af7.js +2 -0
- data/lib/mihari/web/public/static/js/app.823b5af7.js.map +1 -0
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js +25 -0
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js.map +1 -0
- data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js +31 -0
- data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js.map +1 -0
- data/mihari.gemspec +7 -8
- data/sig/lib/mihari/structs/censys.rbs +8 -0
- data/sig/lib/mihari/structs/shodan.rbs +2 -0
- metadata +32 -46
- data/lib/mihari/web/public/static/fonts/fa-brands-400.edf40f86.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.3665ebc7.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.0d2abd43.woff2 +0 -0
- data/lib/mihari/web/public/static/js/app-legacy.c3595dce.js +0 -2
- data/lib/mihari/web/public/static/js/app-legacy.c3595dce.js.map +0 -1
- data/lib/mihari/web/public/static/js/app.afd5025f.js +0 -2
- data/lib/mihari/web/public/static/js/app.afd5025f.js.map +0 -1
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js +0 -25
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js.map +0 -1
- data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js +0 -31
- data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js.map +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02c4e60250ffc8e40e4d0c08fd3721101ab943abdfeb1f73ec267cc345bc0dd2
|
4
|
+
data.tar.gz: 5e65063ca7b9cc8c5b9c6d137abe6820abb6b179c3fb8582d1381d583eb27280
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 142cc9ec86582d37e7e5bd472f1a4f085582441e2204d406fa6ff9d2e94331cbc27513a410c00174950750e066ff9c53b100bd4cfe23039e4cb2be7d8f69ed33
|
7
|
+
data.tar.gz: 49a02b02e298b94d8d847d74ea3389a3e873e72e17fdb93d1e5392992be05db73b37f6cea05ead35b04573400b6956e5b34d323008efb679fba60dcf53d6b372
|
@@ -88,12 +88,17 @@ module Mihari
|
|
88
88
|
)
|
89
89
|
end
|
90
90
|
|
91
|
+
ports = hit.services.map(&:port).map do |port|
|
92
|
+
Port.new(port: port)
|
93
|
+
end
|
94
|
+
|
91
95
|
Artifact.new(
|
92
96
|
data: hit.ip,
|
93
97
|
source: source,
|
94
98
|
metadata: hit.metadata,
|
95
99
|
autonomous_system: as,
|
96
|
-
geolocation: geolocation
|
100
|
+
geolocation: geolocation,
|
101
|
+
ports: ports
|
97
102
|
)
|
98
103
|
end
|
99
104
|
|
@@ -127,9 +127,12 @@ module Mihari
|
|
127
127
|
# @return [Boolean]
|
128
128
|
#
|
129
129
|
def disallowed_data_value?(value)
|
130
|
-
normalized_disallowed_data_values.
|
131
|
-
|
132
|
-
|
130
|
+
return true if normalized_disallowed_data_values.include?(value)
|
131
|
+
|
132
|
+
normalized_disallowed_data_values.select do |disallowed_data_value|
|
133
|
+
disallowed_data_value.is_a?(Regexp)
|
134
|
+
end.any? do |disallowed_data_value|
|
135
|
+
disallowed_data_value.match?(value)
|
133
136
|
end
|
134
137
|
end
|
135
138
|
|
@@ -23,10 +23,10 @@ module Mihari
|
|
23
23
|
return [] unless results || results.empty?
|
24
24
|
|
25
25
|
results = results.map { |result| Structs::Shodan::Result.from_dynamic!(result) }
|
26
|
-
results.map
|
27
|
-
|
28
|
-
|
29
|
-
|
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) }
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
@@ -94,6 +94,30 @@ module Mihari
|
|
94
94
|
matches.select { |match| match.ip_str == ip }.map(&:metadata)
|
95
95
|
end
|
96
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
|
+
|
97
121
|
#
|
98
122
|
# Build an artifact from a Shodan search API response
|
99
123
|
#
|
@@ -116,12 +140,22 @@ module Mihari
|
|
116
140
|
|
117
141
|
metadata = collect_metadata_by_ip(matches, match.ip_str)
|
118
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
|
+
|
119
151
|
Artifact.new(
|
120
152
|
data: match.ip_str,
|
121
153
|
source: source,
|
122
154
|
metadata: metadata,
|
123
155
|
autonomous_system: as,
|
124
|
-
geolocation: geolocation
|
156
|
+
geolocation: geolocation,
|
157
|
+
ports: ports,
|
158
|
+
reverse_dns_names: reverse_dns_names
|
125
159
|
)
|
126
160
|
end
|
127
161
|
end
|
data/lib/mihari/errors.rb
CHANGED
@@ -11,11 +11,19 @@ module Mihari
|
|
11
11
|
|
12
12
|
class FileNotFoundError < Error; end
|
13
13
|
|
14
|
-
class HttpError < Error; end
|
15
|
-
|
16
14
|
class FeedParseError < Error; end
|
17
15
|
|
18
16
|
class RuleValidationError < Error; end
|
19
17
|
|
20
18
|
class ConfigurationError < Error; end
|
19
|
+
|
20
|
+
class HTTPError < Error; end
|
21
|
+
|
22
|
+
class UnsuccessfulStatusCodeError < HTTPError; end
|
23
|
+
|
24
|
+
class NetworkError < HTTPError; end
|
25
|
+
|
26
|
+
class TimeoutError < HTTPError; end
|
27
|
+
|
28
|
+
class SSLError < HTTPError; end
|
21
29
|
end
|
data/lib/mihari/http.rb
CHANGED
@@ -90,11 +90,17 @@ module Mihari
|
|
90
90
|
|
91
91
|
unless res.is_a?(Net::HTTPSuccess)
|
92
92
|
code = res.code.to_i
|
93
|
-
raise
|
93
|
+
raise UnsuccessfulStatusCodeError, "Unsuccessful response code returned: #{code}"
|
94
94
|
end
|
95
95
|
|
96
96
|
res
|
97
97
|
end
|
98
|
+
rescue Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, EOFError, SocketError, Net::ProtocolError => e
|
99
|
+
raise NetworkError, e
|
100
|
+
rescue Timeout::Error => e
|
101
|
+
raise TimeoutError, e
|
102
|
+
rescue OpenSSL::SSL::SSLError => e
|
103
|
+
raise SSLError, e
|
98
104
|
end
|
99
105
|
end
|
100
106
|
end
|
@@ -89,10 +89,10 @@ module Mihari
|
|
89
89
|
required(:analyzer).value(Types::String.enum("feed"))
|
90
90
|
required(:query).value(:string)
|
91
91
|
required(:selector).value(:string)
|
92
|
-
optional(:http_request_method).value(Types::
|
92
|
+
optional(:http_request_method).value(Types::HTTPRequestMethods).default("GET")
|
93
93
|
optional(:http_request_headers).value(:hash).default({})
|
94
94
|
optional(:http_request_payload).value(:hash).default({})
|
95
|
-
optional(:http_request_payload_type).value(Types::
|
95
|
+
optional(:http_request_payload_type).value(Types::HTTPRequestPayloadTypes)
|
96
96
|
optional(:options).hash(AnalyzerOptions)
|
97
97
|
end
|
98
98
|
end
|
@@ -27,7 +27,7 @@ module Mihari
|
|
27
27
|
HTTP = Dry::Schema.Params do
|
28
28
|
required(:emitter).value(Types::String.enum("http"))
|
29
29
|
required(:url).value(:string)
|
30
|
-
optional(:http_request_method).value(Types::
|
30
|
+
optional(:http_request_method).value(Types::HTTPRequestMethods).default("POST")
|
31
31
|
optional(:http_request_headers).value(:hash).default({})
|
32
32
|
optional(:template).value(:string)
|
33
33
|
end
|
@@ -27,11 +27,23 @@ module Mihari
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
class Service < Dry::Struct
|
31
|
+
attribute :port, Types::Integer
|
32
|
+
|
33
|
+
def self.from_dynamic!(d)
|
34
|
+
d = Types::Hash[d]
|
35
|
+
new(
|
36
|
+
port: d.fetch("port")
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
30
41
|
class Hit < Dry::Struct
|
31
42
|
attribute :ip, Types::String
|
32
43
|
attribute :location, Location
|
33
44
|
attribute :autonomous_system, AutonomousSystem
|
34
45
|
attribute :metadata, Types::Hash
|
46
|
+
attribute :services, Types.Array(Service)
|
35
47
|
|
36
48
|
def self.from_dynamic!(d)
|
37
49
|
d = Types::Hash[d]
|
@@ -39,7 +51,8 @@ module Mihari
|
|
39
51
|
ip: d.fetch("ip"),
|
40
52
|
location: Location.from_dynamic!(d.fetch("location")),
|
41
53
|
autonomous_system: AutonomousSystem.from_dynamic!(d.fetch("autonomous_system")),
|
42
|
-
metadata: d
|
54
|
+
metadata: d,
|
55
|
+
services: d.fetch("services", []).map { |x| Service.from_dynamic!(x) }
|
43
56
|
)
|
44
57
|
end
|
45
58
|
end
|
data/lib/mihari/structs/rule.rb
CHANGED
@@ -22,6 +22,7 @@ module Mihari
|
|
22
22
|
attribute :location, Location
|
23
23
|
attribute :domains, Types.Array(Types::String)
|
24
24
|
attribute :ip_str, Types::String
|
25
|
+
attribute :port, Types::Integer
|
25
26
|
attribute :metadata, Types::Hash
|
26
27
|
|
27
28
|
def self.from_dynamic!(d)
|
@@ -40,6 +41,7 @@ module Mihari
|
|
40
41
|
location: Location.from_dynamic!(d.fetch("location")),
|
41
42
|
domains: d.fetch("domains"),
|
42
43
|
ip_str: d.fetch("ip_str"),
|
44
|
+
port: d.fetch("port"),
|
43
45
|
metadata: d
|
44
46
|
)
|
45
47
|
end
|
data/lib/mihari/types.rb
CHANGED
@@ -14,10 +14,8 @@ module Mihari
|
|
14
14
|
|
15
15
|
DataTypes = Types::String.enum(*ALLOWED_DATA_TYPES)
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
HttpRequestMethods = Types::String.enum("GET", "POST")
|
20
|
-
HttpRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
|
17
|
+
HTTPRequestMethods = Types::String.enum("GET", "POST")
|
18
|
+
HTTPRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
|
21
19
|
|
22
20
|
EmitterTypes = Types::String.enum(
|
23
21
|
"database",
|
data/lib/mihari/version.rb
CHANGED
data/lib/mihari/web/app.rb
CHANGED
@@ -42,9 +42,15 @@ module Mihari
|
|
42
42
|
end.to_app
|
43
43
|
end
|
44
44
|
|
45
|
-
def run!(port: 9292, host: "localhost", threads: "
|
45
|
+
def run!(port: 9292, host: "localhost", threads: "1:1", verbose: false)
|
46
46
|
url = "http://#{host}:#{port}"
|
47
47
|
|
48
|
+
# set maximum number of threads to use as PARALLEL_PROCESSOR_COUNT (if it is not set)
|
49
|
+
# ref. https://github.com/grosser/parallel#tips
|
50
|
+
# TODO: is this the best way?
|
51
|
+
_min_thread, max_thread = threads.split(":")
|
52
|
+
ENV["PARALLEL_PROCESSOR_COUNT"] = max_thread if ENV["PARALLEL_PROCESSOR_COUNT"].nil?
|
53
|
+
|
48
54
|
Rack::Handler::Puma.run(instance, Port: port, Host: host, Threads: threads, Verbose: verbose) do |_launcher|
|
49
55
|
Launchy.open(url) if ENV["RACK_ENV"] != "development"
|
50
56
|
end
|
@@ -145,6 +145,7 @@ module Mihari
|
|
145
145
|
end
|
146
146
|
|
147
147
|
rule = Structs::Rule::Rule.from_yaml(yaml)
|
148
|
+
rule.id = id
|
148
149
|
|
149
150
|
begin
|
150
151
|
rule.validate!
|
@@ -159,7 +160,7 @@ module Mihari
|
|
159
160
|
model = rule.to_model
|
160
161
|
model.save
|
161
162
|
rescue ActiveRecord::RecordNotUnique
|
162
|
-
error!({ message: "ID:#{
|
163
|
+
error!({ message: "ID:#{model.id} is already registered" }, 400)
|
163
164
|
end
|
164
165
|
|
165
166
|
status 201
|
@@ -1 +1 @@
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="/static/favicon.ico"/><title>Mihari</title><script defer="defer" type="module" src="/static/js/chunk-vendors.
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="/static/favicon.ico"/><title>Mihari</title><script defer="defer" type="module" src="/static/js/chunk-vendors.dde2116c.js"></script><script defer="defer" type="module" src="/static/js/app.823b5af7.js"></script><link href="/static/css/chunk-vendors.06251949.css" rel="stylesheet"><link href="/static/css/app.2a5d3d21.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.b110c129.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.9d5c9c3d.js" nomodule></script></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|