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 +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/sqreen/encoding_sanitizer.rb +22 -0
- data/lib/sqreen/events/request_record.rb +1 -21
- data/lib/sqreen/log.rb +20 -0
- data/lib/sqreen/rules_callbacks.rb +1 -0
- data/lib/sqreen/rules_callbacks/waf.rb +102 -0
- data/lib/sqreen/runtime_infos.rb +26 -1
- data/lib/sqreen/session.rb +10 -10
- data/lib/sqreen/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a588437667070c3e175082755b4f63a1bcc2a1c1f599142fc56ac2348d05a1bf
|
4
|
+
data.tar.gz: 1dd5d9a28a0d4285c807587d1a0740342561950c85c4fc84e273ced99b90a6c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e2d876f635ceadf46df13484f50ec6dfd418ebece431916330d2319be79f6a0f38c27a73f2a4261809af30fc3d32e6024a68c415b372ad7214b245f2e2443f6
|
7
|
+
data.tar.gz: 38a0ee125e1ded9a6bbddd440f30a3011b7c36051ac7393f5cd8294a128952fd9f7a93735b20496394e77a042a14c95199817fad6598625d5583030f8c50aace
|
data/CHANGELOG.md
CHANGED
@@ -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
|
data/lib/sqreen/log.rb
CHANGED
@@ -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
|
@@ -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
|
data/lib/sqreen/runtime_infos.rb
CHANGED
@@ -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
|
data/lib/sqreen/session.rb
CHANGED
@@ -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
|
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
|
data/lib/sqreen/version.rb
CHANGED
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.
|
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-
|
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
|