mihari 4.5.0 → 4.5.1

Sign up to get free protection for your applications and to get access to all the features.
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