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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfd4bafdc33911546dce3477184426c581b9e3a40a762f32d1944870589ffcc9
4
- data.tar.gz: 8765422118bcc6fe914439b416e6362551c9de0492397ce90c2c5bf7487d982a
3
+ metadata.gz: dd4057ff69b6e8ee676c7ebf7e672b6830d26a02e22ebe372d2786f5220ae8d3
4
+ data.tar.gz: 202c7bce234c823fb10fe40fc1f7af2c25d676f587871d0a49740fd8f3af4325
5
5
  SHA512:
6
- metadata.gz: a0788f87ab0ef4838243e25ca71496408187add826900d0428af6f4a8e7f81093e81944d809425b2c716c778c35b725f757b656cec046adc2294b4a07d764b4f
7
- data.tar.gz: 3c0c001e91aaec0090daeb117a7595a84172a355d527af0dab7f81dfee557b271697a0fbcf75c8a44d5e33b6fb1ff1f8b82e0c057dff91e85b4fb4a0a30d69ca
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 do |result|
27
- matches = result.matches || []
28
- matches.map { |match| build_artifact(match, matches) }
29
- end.flatten.uniq(&:data)
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
@@ -39,7 +39,7 @@ module Mihari
39
39
  data = JSON.parse(res.body.to_s)
40
40
 
41
41
  Structs::IPInfo::Response.from_dynamic! data
42
- rescue HttpError
42
+ rescue HTTPError
43
43
  nil
44
44
  end
45
45
  end
@@ -26,7 +26,7 @@ module Mihari
26
26
  data = JSON.parse(res.body.to_s)
27
27
 
28
28
  Structs::Shodan::InternetDBResponse.from_dynamic! data
29
- rescue HttpError
29
+ rescue HTTPError
30
30
  nil
31
31
  end
32
32
  memoize :query
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 HttpError, "Unsuccessful response code returned: #{code}"
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::HttpRequestMethods).default("GET")
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::HttpRequestPayloadTypes)
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::HttpRequestMethods).default("POST")
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
- UrlscanDataTypes = Types::String.enum("ip", "domain", "url")
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",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "4.5.0"
4
+ VERSION = "4.5.1"
5
5
  end
@@ -42,9 +42,15 @@ module Mihari
42
42
  end.to_app
43
43
  end
44
44
 
45
- def run!(port: 9292, host: "localhost", threads: "0:5", verbose: false)
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.9"
43
- spec.add_development_dependency "steep", "~> 0.51"
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.14.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.6"
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.0
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-14 00:00:00.000000000 Z
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.9'
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.9'
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.51'
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.51'
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.14.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.14.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.6
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.6
768
+ version: 4.0.7
769
769
  - !ruby/object:Gem::Dependency
770
770
  name: pulsedive
771
771
  requirement: !ruby/object:Gem::Requirement