mihari 4.5.0 → 4.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mihari/analyzers/censys.rb +6 -1
  3. data/lib/mihari/analyzers/rule.rb +6 -3
  4. data/lib/mihari/analyzers/shodan.rb +39 -5
  5. data/lib/mihari/enrichers/ipinfo.rb +1 -1
  6. data/lib/mihari/enrichers/shodan.rb +1 -1
  7. data/lib/mihari/errors.rb +10 -2
  8. data/lib/mihari/http.rb +7 -1
  9. data/lib/mihari/schemas/analyzer.rb +2 -2
  10. data/lib/mihari/schemas/emitter.rb +1 -1
  11. data/lib/mihari/structs/censys.rb +14 -1
  12. data/lib/mihari/structs/rule.rb +3 -0
  13. data/lib/mihari/structs/shodan.rb +2 -0
  14. data/lib/mihari/types.rb +2 -4
  15. data/lib/mihari/version.rb +1 -1
  16. data/lib/mihari/web/app.rb +7 -1
  17. data/lib/mihari/web/endpoints/rules.rb +2 -1
  18. data/lib/mihari/web/public/index.html +1 -1
  19. data/lib/mihari/web/public/redoc-static.html +317 -314
  20. data/lib/mihari/web/public/static/css/{chunk-vendors.da2a7bfc.css → chunk-vendors.06251949.css} +2 -2
  21. data/lib/mihari/web/public/static/fonts/{fa-brands-400.f7223235.ttf → fa-brands-400.7fa789ab.ttf} +0 -0
  22. data/lib/mihari/web/public/static/fonts/fa-brands-400.859fc388.woff2 +0 -0
  23. data/lib/mihari/web/public/static/fonts/fa-regular-400.2ffd018f.woff2 +0 -0
  24. data/lib/mihari/web/public/static/fonts/{fa-regular-400.a7fde52b.ttf → fa-regular-400.da02cb7e.ttf} +0 -0
  25. data/lib/mihari/web/public/static/fonts/{fa-solid-900.5b03221c.ttf → fa-solid-900.3a463ec3.ttf} +0 -0
  26. data/lib/mihari/web/public/static/fonts/fa-solid-900.40ddefd7.woff2 +0 -0
  27. data/lib/mihari/web/public/static/fonts/{fa-v4compatibility.42932bea.ttf → fa-v4compatibility.924588dc.ttf} +0 -0
  28. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js +2 -0
  29. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js.map +1 -0
  30. data/lib/mihari/web/public/static/js/app.823b5af7.js +2 -0
  31. data/lib/mihari/web/public/static/js/app.823b5af7.js.map +1 -0
  32. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js +25 -0
  33. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js.map +1 -0
  34. data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js +31 -0
  35. data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js.map +1 -0
  36. data/mihari.gemspec +7 -8
  37. data/sig/lib/mihari/structs/censys.rbs +8 -0
  38. data/sig/lib/mihari/structs/shodan.rbs +2 -0
  39. metadata +32 -46
  40. data/lib/mihari/web/public/static/fonts/fa-brands-400.edf40f86.woff2 +0 -0
  41. data/lib/mihari/web/public/static/fonts/fa-regular-400.3665ebc7.woff2 +0 -0
  42. data/lib/mihari/web/public/static/fonts/fa-solid-900.0d2abd43.woff2 +0 -0
  43. data/lib/mihari/web/public/static/js/app-legacy.c3595dce.js +0 -2
  44. data/lib/mihari/web/public/static/js/app-legacy.c3595dce.js.map +0 -1
  45. data/lib/mihari/web/public/static/js/app.afd5025f.js +0 -2
  46. data/lib/mihari/web/public/static/js/app.afd5025f.js.map +0 -1
  47. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js +0 -25
  48. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js.map +0 -1
  49. data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js +0 -31
  50. 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: dfd4bafdc33911546dce3477184426c581b9e3a40a762f32d1944870589ffcc9
4
- data.tar.gz: 8765422118bcc6fe914439b416e6362551c9de0492397ce90c2c5bf7487d982a
3
+ metadata.gz: 02c4e60250ffc8e40e4d0c08fd3721101ab943abdfeb1f73ec267cc345bc0dd2
4
+ data.tar.gz: 5e65063ca7b9cc8c5b9c6d137abe6820abb6b179c3fb8582d1381d583eb27280
5
5
  SHA512:
6
- metadata.gz: a0788f87ab0ef4838243e25ca71496408187add826900d0428af6f4a8e7f81093e81944d809425b2c716c778c35b725f757b656cec046adc2294b4a07d764b4f
7
- data.tar.gz: 3c0c001e91aaec0090daeb117a7595a84172a355d527af0dab7f81dfee557b271697a0fbcf75c8a44d5e33b6fb1ff1f8b82e0c057dff91e85b4fb4a0a30d69ca
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.any? do |disallowed_data_value|
131
- return value == disallowed_data_value if disallowed_data_value.is_a?(String)
132
- return disallowed_data_value.match?(value) if disallowed_data_value.is_a?(Regexp)
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 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
@@ -36,6 +36,9 @@ module Mihari
36
36
  # @return [Array, nil]
37
37
  attr_reader :errors
38
38
 
39
+ # @return [String]
40
+ attr_writer :id
41
+
39
42
  def initialize(data, yaml)
40
43
  @data = data.deep_symbolize_keys
41
44
  @yaml = yaml
@@ -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.3"
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
@@ -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:#{rule.id} is already registered" }, 400)
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.3bdbaffb.js"></script><script defer="defer" type="module" src="/static/js/app.afd5025f.js"></script><link href="/static/css/chunk-vendors.da2a7bfc.css" rel="stylesheet"><link href="/static/css/app.2a5d3d21.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.d6b76c57.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.c3595dce.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>
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>