mihari 4.5.0 → 4.5.1
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/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/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/mihari.gemspec +4 -4
- data/sig/lib/mihari/structs/censys.rbs +8 -0
- data/sig/lib/mihari/structs/shodan.rbs +2 -0
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd4057ff69b6e8ee676c7ebf7e672b6830d26a02e22ebe372d2786f5220ae8d3
|
4
|
+
data.tar.gz: 202c7bce234c823fb10fe40fc1f7af2c25d676f587871d0a49740fd8f3af4325
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed3c42b58a305f20e7d981a1ae5e19d6efe493d7252645cf54f78c16fe38607305497e983f80d1d04ab8964f0166f19ace1ba62bece5ecdeadb0b6041d4be49f
|
7
|
+
data.tar.gz: 12966b0cc1143bcff8e9a44dd5904c7fa70c234f26613df35e5be162c5e18805e15adb1a8fd1ddd8c1ad84d221b528d411daa29b764e67eed4357a86f6bf0cc7
|
@@ -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
|
|
@@ -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
|
@@ -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
|
data/mihari.gemspec
CHANGED
@@ -39,8 +39,8 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_development_dependency "rerun", "~> 0.13"
|
40
40
|
spec.add_development_dependency "rspec", "~> 3.11"
|
41
41
|
spec.add_development_dependency "simplecov-lcov", "~> 0.8.0"
|
42
|
-
spec.add_development_dependency "standard", "~> 1.
|
43
|
-
spec.add_development_dependency "steep", "~> 0.
|
42
|
+
spec.add_development_dependency "standard", "~> 1.10"
|
43
|
+
spec.add_development_dependency "steep", "~> 0.52"
|
44
44
|
spec.add_development_dependency "timecop", "~> 0.9"
|
45
45
|
spec.add_development_dependency "vcr", "~> 6.1"
|
46
46
|
spec.add_development_dependency "webmock", "~> 3.14"
|
@@ -54,7 +54,7 @@ Gem::Specification.new do |spec|
|
|
54
54
|
spec.add_dependency "dnpedia", "0.1.0"
|
55
55
|
spec.add_dependency "dnstwister", "0.1.0"
|
56
56
|
spec.add_dependency "dotenv", "2.7.6"
|
57
|
-
spec.add_dependency "dry-configurable", "0.
|
57
|
+
spec.add_dependency "dry-configurable", "0.15.0"
|
58
58
|
spec.add_dependency "dry-container", "0.9.0"
|
59
59
|
spec.add_dependency "dry-files", "0.1.0"
|
60
60
|
spec.add_dependency "dry-initializer", "3.1.1"
|
@@ -81,7 +81,7 @@ Gem::Specification.new do |spec|
|
|
81
81
|
spec.add_dependency "passive_circl", "0.1.0"
|
82
82
|
spec.add_dependency "passivetotalx", "0.1.1"
|
83
83
|
spec.add_dependency "plissken", "2.0.1"
|
84
|
-
spec.add_dependency "public_suffix", "4.0.
|
84
|
+
spec.add_dependency "public_suffix", "4.0.7"
|
85
85
|
spec.add_dependency "pulsedive", "0.1.5"
|
86
86
|
spec.add_dependency "puma", "5.6.4"
|
87
87
|
spec.add_dependency "rack", "2.2.3"
|
@@ -14,10 +14,18 @@ module Mihari
|
|
14
14
|
def self.from_dynamic!: (Hash[(String | Symbol), untyped] d) -> Mihari::Structs::Censys::Location
|
15
15
|
end
|
16
16
|
|
17
|
+
class Service
|
18
|
+
attr_reader port: Integer
|
19
|
+
|
20
|
+
def self.from_dynamic!: (Hash[(String | Symbol), untyped] d) -> Mihari::Structs::Censys::Service
|
21
|
+
end
|
22
|
+
|
17
23
|
class Hit
|
18
24
|
attr_reader ip: String
|
19
25
|
attr_reader location: Mihari::Structs::Censys::Location
|
20
26
|
attr_reader autonomous_system: Mihari::Structs::Censys::AutonomousSystem
|
27
|
+
attr_reader metadata: Hash[(String | Symbol), untyped]
|
28
|
+
attr_reader services: Array[Mihari::Structs::Censys::Service]
|
21
29
|
|
22
30
|
def self.from_dynamic!: (Hash[(String | Symbol), untyped] d) -> Mihari::Structs::Censys::Hit
|
23
31
|
end
|
@@ -14,6 +14,8 @@ module Mihari
|
|
14
14
|
attr_reader location: Mihari::Structs::Shodan::Location
|
15
15
|
attr_reader domains: Array[String]
|
16
16
|
attr_reader ip_str: String
|
17
|
+
attr_reader port: Integer
|
18
|
+
|
17
19
|
def self.from_dynamic!: (Hash[(String | Symbol), untyped] d) -> Mihari::Structs::Shodan::Match
|
18
20
|
end
|
19
21
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mihari
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.5.
|
4
|
+
version: 4.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manabu Niseki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -184,28 +184,28 @@ dependencies:
|
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: '1.
|
187
|
+
version: '1.10'
|
188
188
|
type: :development
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: '1.
|
194
|
+
version: '1.10'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: steep
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
198
198
|
requirements:
|
199
199
|
- - "~>"
|
200
200
|
- !ruby/object:Gem::Version
|
201
|
-
version: '0.
|
201
|
+
version: '0.52'
|
202
202
|
type: :development
|
203
203
|
prerelease: false
|
204
204
|
version_requirements: !ruby/object:Gem::Requirement
|
205
205
|
requirements:
|
206
206
|
- - "~>"
|
207
207
|
- !ruby/object:Gem::Version
|
208
|
-
version: '0.
|
208
|
+
version: '0.52'
|
209
209
|
- !ruby/object:Gem::Dependency
|
210
210
|
name: timecop
|
211
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -380,14 +380,14 @@ dependencies:
|
|
380
380
|
requirements:
|
381
381
|
- - '='
|
382
382
|
- !ruby/object:Gem::Version
|
383
|
-
version: 0.
|
383
|
+
version: 0.15.0
|
384
384
|
type: :runtime
|
385
385
|
prerelease: false
|
386
386
|
version_requirements: !ruby/object:Gem::Requirement
|
387
387
|
requirements:
|
388
388
|
- - '='
|
389
389
|
- !ruby/object:Gem::Version
|
390
|
-
version: 0.
|
390
|
+
version: 0.15.0
|
391
391
|
- !ruby/object:Gem::Dependency
|
392
392
|
name: dry-container
|
393
393
|
requirement: !ruby/object:Gem::Requirement
|
@@ -758,14 +758,14 @@ dependencies:
|
|
758
758
|
requirements:
|
759
759
|
- - '='
|
760
760
|
- !ruby/object:Gem::Version
|
761
|
-
version: 4.0.
|
761
|
+
version: 4.0.7
|
762
762
|
type: :runtime
|
763
763
|
prerelease: false
|
764
764
|
version_requirements: !ruby/object:Gem::Requirement
|
765
765
|
requirements:
|
766
766
|
- - '='
|
767
767
|
- !ruby/object:Gem::Version
|
768
|
-
version: 4.0.
|
768
|
+
version: 4.0.7
|
769
769
|
- !ruby/object:Gem::Dependency
|
770
770
|
name: pulsedive
|
771
771
|
requirement: !ruby/object:Gem::Requirement
|