sqreen 1.17.2 → 1.18.0

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: a76d5247720780e68021b3bf5b252d5ce95bb141bec396eb4dfbd32858c4cbc3
4
- data.tar.gz: 84a2aa50afcff20f86a4232bf62a9a90c468d71c5eff8caf8028554721bcd965
3
+ metadata.gz: a588437667070c3e175082755b4f63a1bcc2a1c1f599142fc56ac2348d05a1bf
4
+ data.tar.gz: 1dd5d9a28a0d4285c807587d1a0740342561950c85c4fc84e273ced99b90a6c7
5
5
  SHA512:
6
- metadata.gz: 0de1fbb01aadba0da7fe66a12aba82b5e388fd0d1a2a1c66da27d5ed4a09e1f211218a65c84718d0406215a70a8448dd1cf26e524d3920593e911e6a9417faef
7
- data.tar.gz: 8fe62157f44cd084bcaf090782f1e28f70b3d274af2498d2625e25eba9c82b56986be087ab600be70b22730247dc15c964a6a784d16117d51a3aaf76464aa87f
6
+ metadata.gz: 4e2d876f635ceadf46df13484f50ec6dfd418ebece431916330d2319be79f6a0f38c27a73f2a4261809af30fc3d32e6024a68c415b372ad7214b245f2e2443f6
7
+ data.tar.gz: 38a0ee125e1ded9a6bbddd440f30a3011b7c36051ac7393f5cd8294a128952fd9f7a93735b20496394e77a042a14c95199817fad6598625d5583030f8c50aace
@@ -1,3 +1,7 @@
1
+ ## 1.18.0
2
+
3
+ * Support In-App WAF
4
+
1
5
  ## 1.17.2
2
6
 
3
7
  * Support Rails 6.0 (single database)
@@ -0,0 +1,22 @@
1
+ module Sqreen
2
+ class EncodingSanitizer
3
+ def self.sanitize(obj)
4
+ case obj
5
+ when String
6
+ sanitize_string(obj)
7
+ when Array
8
+ obj.map { |e| sanitize(e) }
9
+ when Hash
10
+ obj.each_with_object({}) { |(k, v), h| h[k] = sanitize(v) }
11
+ else
12
+ obj
13
+ end
14
+ end
15
+
16
+ def self.sanitize_string(s)
17
+ return s if s.encoding.name == 'UTF-8' && s.valid_encoding?
18
+
19
+ s.encode('UTF-16', :invalid => :replace, :undef => :replace).encode('UTF-8')
20
+ end
21
+ end
22
+ end
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'json'
5
5
  require 'sqreen/event'
6
+ require 'sqreen/encoding_sanitizer'
6
7
 
7
8
  module Sqreen
8
9
  # When a request is deeemed worthy of being sent to the backend
@@ -115,27 +116,6 @@ module Sqreen
115
116
  end
116
117
  end
117
118
 
118
- class EncodingSanitizer
119
- def self.sanitize(obj)
120
- case obj
121
- when String
122
- sanitize_string(obj)
123
- when Array
124
- obj.map { |e| sanitize(e) }
125
- when Hash
126
- obj.each_with_object({}) { |(k, v), h| h[k] = sanitize(v) }
127
- else
128
- obj
129
- end
130
- end
131
-
132
- def self.sanitize_string(s)
133
- return s if s.encoding.name == 'UTF-8' && s.valid_encoding?
134
-
135
- s.encode('UTF-16', :invalid => :replace, :undef => :replace).encode('UTF-8')
136
- end
137
- end
138
-
139
119
  # For redacting sensitive data and avoid having it sent to our servers
140
120
  class SensitiveDataRedactor
141
121
  DEFAULT_SENSITIVE_KEYS = Set.new(%w[password secret passwd authorization api_key apikey access_token]).freeze
@@ -62,6 +62,10 @@ module Sqreen
62
62
 
63
63
  # Wrapper class for sqreen logging
64
64
  class Logger
65
+ SEVERITY_TO_METHOD = ::Logger::Severity.constants.each_with_object({}) do |s, h|
66
+ h[::Logger::Severity.const_get(s)] = s.downcase
67
+ end
68
+
65
69
  def initialize(desired_level, log_location, force_logger = nil)
66
70
  if force_logger
67
71
  @logger = force_logger
@@ -90,6 +94,10 @@ module Sqreen
90
94
  @logger.error(msg, &block)
91
95
  end
92
96
 
97
+ def add(severity, msg = nil, &block)
98
+ send(SEVERITY_TO_METHOD[severity], msg, &block)
99
+ end
100
+
93
101
  protected
94
102
 
95
103
  def init_logger_output(path)
@@ -135,6 +143,10 @@ module Sqreen
135
143
 
136
144
  def error(_msg = nil); end
137
145
 
146
+ def fatal(_msg = nil); end
147
+
148
+ def add(_severity, _msg = nil); end
149
+
138
150
  def formatter=(_); end
139
151
  end
140
152
 
@@ -162,6 +174,14 @@ module Sqreen
162
174
  @logger.error(msg, &block)
163
175
  end
164
176
 
177
+ def fatal(msg = nil, &block)
178
+ @logger.error(msg, &block)
179
+ end
180
+
181
+ def add(severity, msg = nil, &block)
182
+ send(Sqreen::Logger::SEVERITY_TO_METHOD[severity], msg, &block)
183
+ end
184
+
165
185
  def formatter=(value)
166
186
  @logger.formatter = value
167
187
  end
@@ -31,3 +31,4 @@ require 'sqreen/rules_callbacks/devise_auth_track'
31
31
  require 'sqreen/rules_callbacks/devise_signup_track'
32
32
 
33
33
  require 'sqreen/rules_callbacks/custom_error'
34
+ require 'sqreen/rules_callbacks/waf'
@@ -0,0 +1,102 @@
1
+ require 'securerandom'
2
+ require 'sqreen/rule_attributes'
3
+ require 'sqreen/binding_accessor'
4
+ require 'sqreen/rule_callback'
5
+ require 'sqreen/safe_json'
6
+
7
+ module Sqreen
8
+ module Rules
9
+ class WAFCB < RuleCB
10
+ BUDGET_MAX = 5000
11
+
12
+ # TODO: move to Dependency
13
+ begin
14
+ require 'libsqreen'
15
+ @libsqreen = true
16
+ rescue LoadError
17
+ Sqreen.log.warn('libsqreen gem not found')
18
+ @libsqreen = false
19
+ end
20
+
21
+ def self.libsqreen?
22
+ @libsqreen
23
+ end
24
+
25
+ attr_reader :binding_accessors, :budget, :waf_rule_name
26
+
27
+ def initialize(*args)
28
+ super(*args)
29
+ @overtimeable = false
30
+
31
+ unless WAFCB.libsqreen?
32
+ Sqreen.log.warn('libsqreen gem not found')
33
+ return
34
+ end
35
+
36
+ unless @data['values']
37
+ Sqreen.log.warn('no values in data')
38
+ return
39
+ end
40
+
41
+ ::LibSqreen::WAF.logger = Sqreen.log
42
+
43
+ name = format("%s_%s", SecureRandom.uuid, rule_name)
44
+ unless @data['values']['waf_rules'] && (::LibSqreen::WAF[name] = @data['values']['waf_rules'])
45
+ Sqreen.log.error("WAF rule #{name} failed to be set, from #<#{self.class.name}:0x#{object_id.to_s(16).rjust(16, '0')}>")
46
+ return
47
+ end
48
+ @waf_rule_name = name
49
+ Sqreen.log.debug("WAF rule #{name} set, from #<#{self.class.name}:0x#{object_id.to_s(16).rjust(16, '0')}>")
50
+
51
+ @binding_accessors = @data['values'].fetch('binding_accessors', []).each_with_object({}) do |e, h|
52
+ h[e] = BindingAccessor.new(e)
53
+ end
54
+ @budget = @data['values'].fetch('budget', BUDGET_MAX)
55
+
56
+ ObjectSpace.define_finalizer(self, WAFCB.finalizer(@waf_rule_name.dup))
57
+ end
58
+
59
+ def pre(instance, args, _budget)
60
+ unless WAFCB.libsqreen?
61
+ Sqreen.log.warn('libsqreen not required')
62
+ return
63
+ end
64
+
65
+ request = framework.request
66
+ return if !waf_rule_name || !request
67
+
68
+ env = [binding, framework, instance, args]
69
+
70
+ waf_args = Hash[binding_accessors.map { |e, b| [e, b.resolve(*env)] }]
71
+ waf_args = Sqreen::EncodingSanitizer.sanitize(waf_args)
72
+ action, data = ::LibSqreen::WAF.run(waf_rule_name, waf_args, budget)
73
+
74
+ case action
75
+ when :monitor
76
+ record_event({ 'waf_data' => data })
77
+ advise_action(nil)
78
+ when :block
79
+ record_event({ 'waf_data' => data })
80
+ advise_action(:raise)
81
+ when :good
82
+ advise_action(nil)
83
+ when :timeout, :invalid_call, :invalid_rule, :invalid_flow, :no_rule
84
+ Sqreen.log.warn("error from waf: #{action}")
85
+ advise_action(nil)
86
+ else
87
+ Sqreen.log.warn("unexpected action returned from waf")
88
+ advise_action(nil)
89
+ end
90
+ end
91
+
92
+ def self.finalizer(rule_name)
93
+ lambda do |object_id|
94
+ return unless WAFCB.libsqreen?
95
+
96
+ ::LibSqreen::WAF.delete(waf_rule_name, waf_args, budget)
97
+ Sqreen.log.debug("WAF rule #{rule_name} deleted, from #<#{name}:0x#{object_id.to_s(16).rjust(16, '0')}>")
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -65,7 +65,32 @@ module Sqreen
65
65
  {
66
66
  :agent_type => :ruby,
67
67
  :agent_version => ::Sqreen::VERSION,
68
- }
68
+ }.tap do |h|
69
+ h[:libsqreen_version] = libsqreen_version if libsqreen?
70
+ end
71
+ end
72
+
73
+ def libsqreen?
74
+ libsqreen_loaded? && !libsqreen_stub?
75
+ end
76
+
77
+ def libsqreen_loaded?
78
+ Kernel.const_defined?('LibSqreen')
79
+ end
80
+
81
+ def libsqreen_stub?
82
+ !::LibSqreen.respond_to?(:version)
83
+ end
84
+
85
+ def libsqreen_version
86
+ return unless libsqreen?
87
+
88
+ @libsqreen_version ||= case (version = ::LibSqreen.version)
89
+ when Array
90
+ version.map(&:to_s).join('.')
91
+ else
92
+ version
93
+ end
69
94
  end
70
95
 
71
96
  def os
@@ -100,7 +100,7 @@ module Sqreen
100
100
  rescue StandardError => e
101
101
  Sqreen.log.debug { "Caught exception during request: #{e.inspect}" }
102
102
  Sqreen.log.debug { e.backtrace }
103
- Sqreen.log.debug "Cannot connect, retry in #{RETRY_CONNECT_SECONDS} seconds"
103
+ Sqreen.log.debug { "Cannot connect, retry in #{RETRY_CONNECT_SECONDS} seconds" }
104
104
  sleep RETRY_CONNECT_SECONDS
105
105
  @conn_retry += 1
106
106
  retry
@@ -129,7 +129,7 @@ module Sqreen
129
129
  raise e if max_retry != RETRY_FOREVER && current_retry >= max_retry || e.is_a?(Sqreen::NotImplementedYet) || e.is_a?(Sqreen::Unauthorized)
130
130
 
131
131
  sleep_delay = [MAX_DELAY, retry_request_seconds * current_retry].min
132
- Sqreen.log.debug format("Sleeping %ds before retry #{current_retry}/#{max_retry}", sleep_delay)
132
+ Sqreen.log.debug { format("Sleeping %ds before retry #{current_retry}/#{max_retry}", sleep_delay) }
133
133
  sleep(sleep_delay)
134
134
 
135
135
  retry
@@ -164,10 +164,10 @@ module Sqreen
164
164
  @req_nb += 1
165
165
 
166
166
  path = prefix_path(path)
167
- Sqreen.log.debug format('%s %s (%s)', method, path, @token)
168
167
 
169
168
  payload = {}
170
169
  resiliently(RETRY_REQUEST_SECONDS, max_retry) do
170
+ Sqreen.log.debug { format('%s %s %s %s (%s)', method, path, JSON.dump(data), headers.inspect, @token) }
171
171
  res = nil
172
172
  MUTEX.synchronize do
173
173
  res = case method.upcase
@@ -181,7 +181,7 @@ module Sqreen
181
181
  end
182
182
  @con.post(path, json_data, headers)
183
183
  else
184
- Sqreen.log.debug format('unknown method %s', method)
184
+ Sqreen.log.debug { format('unknown method %s', method) }
185
185
  raise Sqreen::NotImplementedYet
186
186
  end
187
187
  end
@@ -192,17 +192,17 @@ module Sqreen
192
192
  if res['Content-Type'] && res['Content-Type'].start_with?('application/json')
193
193
  payload = JSON.parse(res.body)
194
194
  unless payload['status']
195
- Sqreen.log.debug(format('Cannot %s %s. Parsed response body was: %s', method, path, payload.inspect))
195
+ Sqreen.log.debug { format('Cannot %s %s. Parsed response body was: %s', method, path, payload.inspect) }
196
196
  end
197
197
  else
198
- Sqreen.log.debug "Unexpected response Content-Type: #{res['Content-Type']}"
199
- Sqreen.log.debug "Unexpected response body: #{res.body.inspect}"
198
+ Sqreen.log.debug { "Unexpected response Content-Type: #{res['Content-Type']}" }
199
+ Sqreen.log.debug { "Unexpected response body: #{res.body.inspect}" }
200
200
  end
201
201
  else
202
- Sqreen.log.debug 'warning: empty return value'
202
+ Sqreen.log.debug { 'warning: empty return value' }
203
203
  end
204
+ Sqreen.log.debug { format('%s %s (DONE: %s in %f ms)', method, path, res && res.code, (Time.now.utc - now) * 1000) }
204
205
  end
205
- Sqreen.log.debug format('%s %s (DONE in %f ms)', method, path, (Time.now.utc - now) * 1000)
206
206
  payload
207
207
  end
208
208
 
@@ -233,7 +233,7 @@ module Sqreen
233
233
  end
234
234
  Sqreen.log.info 'Login success.'
235
235
  @session_id = res['session_id']
236
- Sqreen.log.debug "received session_id #{@session_id}"
236
+ Sqreen.log.debug { "received session_id #{@session_id}" }
237
237
  Sqreen.logged_in = true
238
238
  res
239
239
  end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
  module Sqreen
4
- VERSION = '1.17.2'.freeze
4
+ VERSION = '1.18.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqreen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.2
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-30 00:00:00.000000000 Z
11
+ date: 2019-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sq_mini_racer
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.2.4.sqreen2
27
+ - !ruby/object:Gem::Dependency
28
+ name: libsqreen
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.0.0
27
41
  description: Sqreen is a SaaS based Application protection and monitoring platform
28
42
  that integrates directly into your Ruby applications. Learn more at https://sqreen.io.
29
43
  email: contact@sqreen.io
@@ -64,6 +78,7 @@ files:
64
78
  - lib/sqreen/dependency/rails.rb
65
79
  - lib/sqreen/dependency/sentry.rb
66
80
  - lib/sqreen/dependency/sinatra.rb
81
+ - lib/sqreen/encoding_sanitizer.rb
67
82
  - lib/sqreen/event.rb
68
83
  - lib/sqreen/events/attack.rb
69
84
  - lib/sqreen/events/remote_exception.rb
@@ -126,6 +141,7 @@ files:
126
141
  - lib/sqreen/rules_callbacks/shell_env.rb
127
142
  - lib/sqreen/rules_callbacks/url_matches.rb
128
143
  - lib/sqreen/rules_callbacks/user_agent_matches.rb
144
+ - lib/sqreen/rules_callbacks/waf.rb
129
145
  - lib/sqreen/rules_signature.rb
130
146
  - lib/sqreen/runner.rb
131
147
  - lib/sqreen/runtime_infos.rb