sqreen-alt 1.11.0 → 1.11.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 +4 -4
- data/lib/sqreen.rb +0 -1
- data/lib/sqreen/callback_tree.rb +38 -20
- data/lib/sqreen/callbacks.rb +6 -4
- data/lib/sqreen/conditionable.rb +3 -3
- data/lib/sqreen/frameworks/generic.rb +38 -18
- data/lib/sqreen/frameworks/request_recorder.rb +0 -2
- data/lib/sqreen/instrumentation.rb +217 -160
- data/lib/sqreen/performance_notifications.rb +10 -36
- data/lib/sqreen/performance_notifications/log.rb +1 -2
- data/lib/sqreen/performance_notifications/log_performance.rb +1 -2
- data/lib/sqreen/performance_notifications/metrics.rb +1 -2
- data/lib/sqreen/performance_notifications/newrelic.rb +1 -2
- data/lib/sqreen/remote_command.rb +7 -7
- data/lib/sqreen/rule_callback.rb +4 -0
- data/lib/sqreen/rules.rb +2 -2
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +7 -1
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +3 -3
- data/lib/sqreen/rules_callbacks/blacklist_ips.rb +1 -1
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +7 -6
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +1 -1
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +1 -1
- data/lib/sqreen/rules_callbacks/custom_error.rb +2 -5
- data/lib/sqreen/rules_callbacks/execjs.rb +76 -37
- data/lib/sqreen/rules_callbacks/headers_insert.rb +1 -1
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +3 -3
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +18 -17
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +1 -1
- data/lib/sqreen/rules_callbacks/record_request_context.rb +9 -8
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -34
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +13 -4
- data/lib/sqreen/rules_callbacks/shell_env.rb +2 -2
- data/lib/sqreen/rules_callbacks/url_matches.rb +1 -1
- data/lib/sqreen/rules_callbacks/user_agent_matches.rb +1 -1
- data/lib/sqreen/session.rb +1 -1
- data/lib/sqreen/shared_storage.rb +16 -12
- data/lib/sqreen/{stats.rb → shared_storage23.rb} +3 -11
- data/lib/sqreen/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a04af20485331ca42056930bb78f81c6f11f0e4b4385f73b81f71f6e6af413e3
|
4
|
+
data.tar.gz: 0cbe16e0bdbc373ec5f948dfebbcb39e7379a57efea2bd2b93a1dceff9c389b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b1eda52335145dbf4bf4dbc279c36ab63f4f10b6e673d96f8da395d6e6a369febf869fc42ff43b3a7a27233a6ce287305045bccea1657524398f39b2f987db9
|
7
|
+
data.tar.gz: 840409d8a53ff8d1d3e362f6f3721d26051ee1ce7cae2677aaa5044ab9815e454361277896a3ae29665a6a92d24e2c002d985ab734f99930cbda419c6ff36abc
|
data/lib/sqreen.rb
CHANGED
data/lib/sqreen/callback_tree.rb
CHANGED
@@ -19,50 +19,68 @@ module Sqreen
|
|
19
19
|
@by_class[cb.klass] = {} unless @by_class[cb.klass]
|
20
20
|
|
21
21
|
cb_klass = @by_class[cb.klass]
|
22
|
-
|
23
|
-
cb_klass[cb.method] = { :pre => [], :post => [], :failing => [] }
|
24
|
-
end
|
22
|
+
cb_klass[cb.method] ||= [nil, nil, nil]
|
25
23
|
|
26
24
|
methods = cb_klass[cb.method]
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
if cb.pre?
|
27
|
+
methods[0] ||= []
|
28
|
+
methods[0] << cb
|
29
|
+
end
|
30
|
+
if cb.post?
|
31
|
+
methods[1] ||= []
|
32
|
+
methods[1] << cb
|
33
|
+
end
|
34
|
+
if cb.failing?
|
35
|
+
methods[2] ||= []
|
36
|
+
methods[2] << cb
|
37
|
+
end
|
31
38
|
end
|
32
39
|
|
33
40
|
def remove(cb)
|
34
|
-
|
41
|
+
by_meth = @by_class[cb.klass]
|
42
|
+
return unless by_meth
|
43
|
+
types = by_meth[cb.method]
|
35
44
|
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
if cb.pre? && types[0]
|
46
|
+
types[0].delete(cb)
|
47
|
+
types[0] = nil if types[0].empty?
|
48
|
+
end
|
49
|
+
if cb.post? && types[1]
|
50
|
+
types[1].delete(cb)
|
51
|
+
types[1] = nil if types[1].empty?
|
52
|
+
end
|
53
|
+
if cb.failing? && types[2]
|
54
|
+
types[2].delete(cb)
|
55
|
+
types[2] = nil if types[2].empty?
|
56
|
+
end
|
39
57
|
end
|
40
58
|
|
41
|
-
def get(klass, method
|
59
|
+
def get(klass, method)
|
42
60
|
k = @by_class[klass]
|
43
61
|
unless k
|
44
62
|
Sqreen.log.debug { format('Error: no cb registered for class %s (%s)', klass.inspect, klass.class) }
|
45
63
|
Sqreen.log.debug { inspect }
|
46
|
-
return []
|
64
|
+
return [nil, nil, nil]
|
47
65
|
end
|
48
66
|
cbs = k[method]
|
49
67
|
unless cbs
|
50
68
|
Sqreen.log.debug { format('Error: no cbs registered for method %s.%s', klass, method) }
|
51
69
|
Sqreen.log.debug { inspect }
|
52
|
-
return []
|
70
|
+
return [nil, nil, nil]
|
53
71
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
res.to_a
|
72
|
+
cbs
|
73
|
+
rescue StandardError => e
|
74
|
+
Sqreen.log.warn "we catched an exception getting cbs: #{e.inspect}"
|
75
|
+
Sqreen::RemoteException.record(e)
|
76
|
+
[nil, nil, nil]
|
60
77
|
end
|
61
78
|
|
62
79
|
def each
|
63
80
|
@by_class.each_value do |values|
|
64
81
|
values.each_value do |cbs|
|
65
|
-
cbs.
|
82
|
+
cbs.each do |cb_ary|
|
83
|
+
next unless cb_ary
|
66
84
|
cb_ary.each do |cb|
|
67
85
|
yield cb
|
68
86
|
end
|
data/lib/sqreen/callbacks.rb
CHANGED
@@ -37,6 +37,7 @@ module Sqreen
|
|
37
37
|
# CB can also declare that they are whitelisted and should not be run at the moment.
|
38
38
|
|
39
39
|
attr_reader :klass, :method
|
40
|
+
attr_reader :overtimeable
|
40
41
|
|
41
42
|
def initialize(klass, method)
|
42
43
|
@method = method
|
@@ -45,6 +46,7 @@ module Sqreen
|
|
45
46
|
@has_pre = respond_to? :pre
|
46
47
|
@has_post = respond_to? :post
|
47
48
|
@has_failing = respond_to? :failing
|
49
|
+
@overtimeable = false
|
48
50
|
|
49
51
|
raise(Sqreen::Exception, 'No callback provided') unless @has_pre || @has_post || @has_failing
|
50
52
|
end
|
@@ -71,7 +73,7 @@ module Sqreen
|
|
71
73
|
|
72
74
|
def overtime!
|
73
75
|
Sqreen.log.debug { "#{self} is overtime!" }
|
74
|
-
|
76
|
+
@overtimeable
|
75
77
|
end
|
76
78
|
|
77
79
|
def framework
|
@@ -81,13 +83,13 @@ module Sqreen
|
|
81
83
|
# target_method, position, callback, callback class
|
82
84
|
|
83
85
|
class DefaultCB < CB
|
84
|
-
def pre(_inst,
|
86
|
+
def pre(_inst, args, _budget = nil, &_block)
|
85
87
|
Sqreen.log.debug "<< #{@klass} #{@method} #{Thread.current}"
|
86
88
|
Sqreen.log.debug args.join ' '
|
87
89
|
# log params
|
88
90
|
end
|
89
91
|
|
90
|
-
def post(_rv, _inst,
|
92
|
+
def post(_rv, _inst, _args, _budget = nil, &_block)
|
91
93
|
# log "#{rv}"
|
92
94
|
Sqreen.log.debug ">> #{@klass} #{@method} #{Thread.current}"
|
93
95
|
end
|
@@ -101,7 +103,7 @@ module Sqreen
|
|
101
103
|
@block = block
|
102
104
|
end
|
103
105
|
|
104
|
-
def pre(_inst,
|
106
|
+
def pre(_inst, _args, _budget = nil, &_block)
|
105
107
|
# FIXME: implement this removal
|
106
108
|
@remove_me = true
|
107
109
|
@block.call
|
data/lib/sqreen/conditionable.rb
CHANGED
@@ -26,19 +26,19 @@ module Sqreen
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def pre_with_conditions(inst, *args, &block)
|
29
|
-
eargs = [
|
29
|
+
eargs = [nil, framework, inst, args, @data, nil]
|
30
30
|
return nil if !pre_conditions.nil? && !pre_conditions.evaluate(*eargs)
|
31
31
|
pre_without_conditions(inst, *args, &block)
|
32
32
|
end
|
33
33
|
|
34
34
|
def post_with_conditions(rv, inst, *args, &block)
|
35
|
-
eargs = [
|
35
|
+
eargs = [nil, framework, inst, args, @data, rv]
|
36
36
|
return nil if !post_conditions.nil? && !post_conditions.evaluate(*eargs)
|
37
37
|
post_without_conditions(rv, inst, *args, &block)
|
38
38
|
end
|
39
39
|
|
40
40
|
def failing_with_conditions(rv, inst, *args, &block)
|
41
|
-
eargs = [
|
41
|
+
eargs = [nil, framework, inst, args, @data, rv]
|
42
42
|
return nil if !failing_conditions.nil? && !failing_conditions.evaluate(*eargs)
|
43
43
|
failing_without_conditions(rv, inst, *args, &block)
|
44
44
|
end
|
@@ -234,6 +234,7 @@ module Sqreen
|
|
234
234
|
SharedStorage.set(:request, Rack::Request.new(object))
|
235
235
|
SharedStorage.inc(:stored_requests)
|
236
236
|
SharedStorage.set(:xss_params, nil)
|
237
|
+
SharedStorage.set(:whitelisted, nil)
|
237
238
|
SharedStorage.set(:request_overtime, nil)
|
238
239
|
end
|
239
240
|
|
@@ -250,6 +251,7 @@ module Sqreen
|
|
250
251
|
self.remaining_perf_budget = nil
|
251
252
|
SharedStorage.set(:request, nil)
|
252
253
|
SharedStorage.set(:xss_params, nil)
|
254
|
+
SharedStorage.set(:whitelisted, nil)
|
253
255
|
SharedStorage.set(:request_overtime, nil)
|
254
256
|
end
|
255
257
|
|
@@ -338,14 +340,13 @@ module Sqreen
|
|
338
340
|
Dir.getwd
|
339
341
|
end
|
340
342
|
|
341
|
-
WHITELIST_KEY = 'sqreen.whitelisted_request'.freeze
|
342
|
-
|
343
343
|
# Return the current item that whitelist this request
|
344
344
|
# returns nil if request is not whitelisted
|
345
345
|
def whitelisted_match
|
346
346
|
return nil unless request
|
347
|
-
|
348
|
-
|
347
|
+
whitelisted = whitelisted_ip || whitelisted_path
|
348
|
+
SharedStorage.set(:whitelisted, !whitelisted.nil?)
|
349
|
+
whitelisted
|
349
350
|
end
|
350
351
|
|
351
352
|
# Returns the current path that whitelist the request
|
@@ -369,21 +370,40 @@ module Sqreen
|
|
369
370
|
request.env['REMOTE_ADDR']
|
370
371
|
end
|
371
372
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
373
|
+
if Regexp.new('').respond_to?(:match?)
|
374
|
+
def xss_params(regexp = nil)
|
375
|
+
p = SharedStorage.get(:xss_params)
|
376
|
+
return p unless p.nil?
|
377
|
+
p = request_params
|
378
|
+
parm = Set.new
|
379
|
+
each_key_value_for_hash(p) do |value|
|
380
|
+
next unless value.is_a?(String)
|
381
|
+
next if value.size < 5
|
382
|
+
next if regexp && !regexp.match?(value)
|
383
|
+
parm << value
|
384
|
+
end
|
385
|
+
p = parm.to_a
|
386
|
+
Sqreen.log.debug { "Filtered XSS params: #{p.inspect}" }
|
387
|
+
SharedStorage.set(:xss_params, p)
|
388
|
+
p
|
389
|
+
end
|
390
|
+
else
|
391
|
+
def xss_params(regexp = nil)
|
392
|
+
p = SharedStorage.get(:xss_params)
|
393
|
+
return p unless p.nil?
|
394
|
+
p = request_params
|
395
|
+
parm = Set.new
|
396
|
+
each_key_value_for_hash(p) do |value|
|
397
|
+
next unless value.is_a?(String)
|
398
|
+
next if value.size < 5
|
399
|
+
next if regexp && !regexp.match(value)
|
400
|
+
parm << value
|
401
|
+
end
|
402
|
+
p = parm.to_a
|
403
|
+
Sqreen.log.debug { "Filtered XSS params: #{p.inspect}" }
|
404
|
+
SharedStorage.set(:xss_params, p)
|
405
|
+
p
|
382
406
|
end
|
383
|
-
p = parm.to_a
|
384
|
-
Sqreen.log.debug { "Filtered XSS params: #{p.inspect}" }
|
385
|
-
SharedStorage.set(:xss_params, p)
|
386
|
-
p
|
387
407
|
end
|
388
408
|
|
389
409
|
protected
|
@@ -47,8 +47,6 @@ module Sqreen
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def close_request_record(queue, observations_queue, payload_creator)
|
50
|
-
Sqreen::PerformanceNotifications::LogPerformance.next_request
|
51
|
-
Sqreen::PerformanceNotifications::NewRelic.next_request
|
52
50
|
clean_request_record if observed_items.nil?
|
53
51
|
if only_metric_observation
|
54
52
|
push_metrics(observations_queue, queue)
|
@@ -3,12 +3,13 @@
|
|
3
3
|
|
4
4
|
require 'sqreen/callback_tree'
|
5
5
|
require 'sqreen/log'
|
6
|
-
require 'sqreen/stats'
|
7
6
|
require 'sqreen/exception'
|
8
7
|
require 'sqreen/performance_notifications'
|
9
8
|
require 'sqreen/call_countable'
|
10
9
|
require 'sqreen/events/remote_exception'
|
11
10
|
require 'sqreen/rules_signature'
|
11
|
+
require 'sqreen/shared_storage'
|
12
|
+
require 'sqreen/rules_callbacks/record_request_context'
|
12
13
|
require 'set'
|
13
14
|
|
14
15
|
# How to override a class method:
|
@@ -33,13 +34,16 @@ require 'set'
|
|
33
34
|
|
34
35
|
module Sqreen
|
35
36
|
class Instrumentation
|
36
|
-
|
37
|
-
|
37
|
+
OVERTIME_METRIC = 'request_overtime'.freeze
|
38
|
+
MGMT_COST = 0.000025
|
38
39
|
@@override_semaphore = Mutex.new
|
40
|
+
@@overriden_singleton_methods = false
|
39
41
|
|
40
42
|
## Overriden methods and callbacks globals
|
41
43
|
@@overriden_methods = []
|
42
44
|
@@registered_callbacks = CBTree.new
|
45
|
+
@@unovertimable_hookpoints = Set.new
|
46
|
+
@@record_request_hookpoints = Set.new
|
43
47
|
@@instrumented_pid = nil
|
44
48
|
|
45
49
|
def self.semaphore
|
@@ -57,33 +61,29 @@ module Sqreen
|
|
57
61
|
def self.overriden
|
58
62
|
@@overriden_methods
|
59
63
|
end
|
60
|
-
|
61
|
-
|
62
|
-
Instrumentation.guard_call(method, []) do
|
63
|
-
callbacks = @@registered_callbacks.get(klass, method, :pre)
|
64
|
-
if callbacks.any?(&:whitelisted?)
|
65
|
-
callbacks = callbacks.reject(&:whitelisted?)
|
66
|
-
end
|
67
|
-
|
68
|
-
cb_with_framework = callbacks.find(&:framework)
|
69
|
-
budget = nil
|
70
|
-
budget = cb_with_framework.framework.remaining_perf_budget if cb_with_framework
|
64
|
+
def self.callback_wrapper_pre(callbacks, framework, budget, _klass, method, instance, args, &block)
|
65
|
+
all_start = Sqreen::PerformanceNotifications.time
|
66
|
+
#Instrumentation.guard_call(method, []) do
|
71
67
|
returns = []
|
72
68
|
callbacks.each do |cb|
|
73
69
|
# If record_request is part of callbacks we should filter after it ran
|
74
70
|
next if cb.whitelisted?
|
75
71
|
rule = cb.rule_name if cb.respond_to?(:rule_name)
|
76
|
-
if
|
77
|
-
next if cb.
|
72
|
+
if budget && budget <= 0.0
|
73
|
+
next if cb.overtimeable
|
78
74
|
end
|
79
75
|
Sqreen.log.debug { "running pre cb #{cb}" }
|
80
76
|
begin
|
81
77
|
start = Sqreen::PerformanceNotifications.time
|
82
|
-
res = cb.
|
78
|
+
res = cb.pre(instance, args, budget, &block)
|
83
79
|
stop = Sqreen::PerformanceNotifications.time
|
84
80
|
# The first few pre callbacks could not have a request & hence a budget just yet so we try harder to find it
|
85
|
-
budget =
|
86
|
-
|
81
|
+
budget = framework.remaining_perf_budget if framework && !budget && Sqreen.performance_budget
|
82
|
+
if budget
|
83
|
+
budget -= (stop - start)
|
84
|
+
cb.overtime! if budget <= 0.0
|
85
|
+
end
|
86
|
+
all_start += (stop - start)
|
87
87
|
if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
|
88
88
|
Sqreen.log.debug do
|
89
89
|
"#{cb} cannot block, overriding return value"
|
@@ -94,7 +94,7 @@ module Sqreen
|
|
94
94
|
end
|
95
95
|
returns << res
|
96
96
|
rescue StandardError => e
|
97
|
-
Sqreen.log.warn "we catch an exception: #{e.inspect}"
|
97
|
+
Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
|
98
98
|
Sqreen.log.debug e.backtrace
|
99
99
|
if cb.respond_to?(:record_exception)
|
100
100
|
cb.record_exception(e)
|
@@ -105,36 +105,37 @@ module Sqreen
|
|
105
105
|
end
|
106
106
|
Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/pre", start, stop)
|
107
107
|
end
|
108
|
-
|
108
|
+
all_stop = Sqreen::PerformanceNotifications.time
|
109
|
+
framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size if framework && budget
|
110
|
+
Sqreen::PerformanceNotifications.notify('Callbacks/hooks_pre/pre', all_start, all_stop)
|
109
111
|
returns
|
110
|
-
end
|
112
|
+
#end
|
111
113
|
rescue StandardError => e
|
112
|
-
Sqreen.log.warn "we catched an exception between cbs: #{e.inspect}"
|
114
|
+
Sqreen.log.warn { "we catched an exception between cbs: #{e.inspect}" }
|
113
115
|
Sqreen::RemoteException.record(e)
|
116
|
+
[]
|
114
117
|
end
|
115
118
|
|
116
|
-
def self.callback_wrapper_post(
|
117
|
-
|
118
|
-
|
119
|
-
if callbacks.any?(&:whitelisted?)
|
120
|
-
callbacks = callbacks.reject(&:whitelisted?)
|
121
|
-
end
|
122
|
-
|
123
|
-
cb_with_framework = callbacks.find(&:framework)
|
124
|
-
budget = nil
|
125
|
-
budget = cb_with_framework.framework.remaining_perf_budget if cb_with_framework
|
119
|
+
def self.callback_wrapper_post(callbacks, framework, budget, _klass, method, return_val, instance, args, &block)
|
120
|
+
all_start = Sqreen::PerformanceNotifications.time
|
121
|
+
#Instrumentation.guard_call(method, []) do
|
126
122
|
returns = []
|
127
123
|
callbacks.reverse_each do |cb|
|
124
|
+
next if cb.whitelisted?
|
128
125
|
rule = cb.rule_name if cb.respond_to?(:rule_name)
|
129
|
-
if
|
130
|
-
next if cb.
|
126
|
+
if budget && budget <= 0.0
|
127
|
+
next if cb.overtimeable
|
131
128
|
end
|
132
129
|
Sqreen.log.debug { "running post cb #{cb}" }
|
133
130
|
begin
|
134
131
|
start = Sqreen::PerformanceNotifications.time
|
135
|
-
res = cb.
|
132
|
+
res = cb.post(return_val, instance, args, budget, &block)
|
136
133
|
stop = Sqreen::PerformanceNotifications.time
|
137
|
-
|
134
|
+
if budget
|
135
|
+
budget -= (stop - start)
|
136
|
+
cb.overtime! if budget <= 0.0
|
137
|
+
end
|
138
|
+
all_start += (stop - start)
|
138
139
|
if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
|
139
140
|
Sqreen.log.debug do
|
140
141
|
"#{cb} cannot block, overriding return value"
|
@@ -144,8 +145,8 @@ module Sqreen
|
|
144
145
|
res[:rule_name] = rule
|
145
146
|
end
|
146
147
|
returns << res
|
147
|
-
rescue => e
|
148
|
-
Sqreen.log.warn "we catch an exception: #{e.inspect}"
|
148
|
+
rescue StandardError => e
|
149
|
+
Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
|
149
150
|
Sqreen.log.debug e.backtrace
|
150
151
|
if cb.respond_to?(:record_exception)
|
151
152
|
cb.record_exception(e)
|
@@ -156,36 +157,39 @@ module Sqreen
|
|
156
157
|
end
|
157
158
|
Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/post", start, stop)
|
158
159
|
end
|
159
|
-
|
160
|
+
all_stop = Sqreen::PerformanceNotifications.time
|
161
|
+
if framework && budget && framework.remaining_perf_budget
|
162
|
+
framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size
|
163
|
+
end
|
164
|
+
Sqreen::PerformanceNotifications.notify('Callbacks/hooks_post/post', all_start, all_stop)
|
160
165
|
returns
|
161
|
-
end
|
166
|
+
#end
|
162
167
|
rescue StandardError => e
|
163
|
-
Sqreen.log.warn "we catched an exception between cbs: #{e.inspect}"
|
168
|
+
Sqreen.log.warn { "we catched an exception between cbs: #{e.inspect}" }
|
164
169
|
Sqreen::RemoteException.record(e)
|
170
|
+
[]
|
165
171
|
end
|
166
172
|
|
167
|
-
def self.callback_wrapper_failing(exception,
|
168
|
-
|
169
|
-
|
170
|
-
if callbacks.any?(&:whitelisted?)
|
171
|
-
callbacks = callbacks.reject(&:whitelisted?)
|
172
|
-
end
|
173
|
-
|
174
|
-
cb_with_framework = callbacks.find(&:framework)
|
175
|
-
budget = nil
|
176
|
-
budget = cb_with_framework.framework.remaining_perf_budget if cb_with_framework
|
173
|
+
def self.callback_wrapper_failing(callbacks, framework, budget, exception, _klass, method, instance, args, &block)
|
174
|
+
all_start = Sqreen::PerformanceNotifications.time
|
175
|
+
#Instrumentation.guard_call(method, []) do
|
177
176
|
returns = []
|
178
177
|
callbacks.each do |cb|
|
178
|
+
next if cb.whitelisted?
|
179
179
|
rule = cb.rule_name if cb.respond_to?(:rule_name)
|
180
|
-
if
|
181
|
-
next if cb.
|
180
|
+
if budget && budget <= 0.0
|
181
|
+
next if cb.overtimeable
|
182
182
|
end
|
183
183
|
Sqreen.log.debug { "running failing cb #{cb}" }
|
184
184
|
begin
|
185
185
|
start = Sqreen::PerformanceNotifications.time
|
186
|
-
res = cb.
|
186
|
+
res = cb.failing(exception, instance, args, budget, &block)
|
187
187
|
stop = Sqreen::PerformanceNotifications.time
|
188
|
-
|
188
|
+
if budget
|
189
|
+
budget -= (stop - start)
|
190
|
+
cb.overtime! if budget <= 0.0
|
191
|
+
end
|
192
|
+
all_start += (stop - start)
|
189
193
|
if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
|
190
194
|
Sqreen.log.debug do
|
191
195
|
"#{cb} cannot block, overriding return value"
|
@@ -195,9 +199,9 @@ module Sqreen
|
|
195
199
|
res[:rule_name] = rule
|
196
200
|
end
|
197
201
|
returns << res
|
198
|
-
rescue => e
|
199
|
-
Sqreen.log.warn "we catch an exception: #{e.inspect}"
|
200
|
-
Sqreen.log.debug e.backtrace
|
202
|
+
rescue StandardError => e
|
203
|
+
Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
|
204
|
+
Sqreen.log.debug { e.backtrace }
|
201
205
|
if cb.respond_to?(:record_exception)
|
202
206
|
cb.record_exception(e)
|
203
207
|
else
|
@@ -207,24 +211,27 @@ module Sqreen
|
|
207
211
|
end
|
208
212
|
Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/failing", start, stop)
|
209
213
|
end
|
210
|
-
|
214
|
+
all_stop = Sqreen::PerformanceNotifications.time
|
215
|
+
if framework && budget && framework.remaining_perf_budget
|
216
|
+
framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size
|
217
|
+
end
|
218
|
+
Sqreen::PerformanceNotifications.notify('Callbacks/hooks_failing/failing', all_start, all_stop)
|
211
219
|
returns
|
212
|
-
|
220
|
+
# end
|
213
221
|
rescue StandardError => e
|
214
222
|
Sqreen.log.warn "we catched an exception between cbs: #{e.inspect}"
|
215
223
|
Sqreen::RemoteException.record(e)
|
224
|
+
[]
|
216
225
|
end
|
217
226
|
|
218
227
|
def self.guard_multi_call(instance, method, original_method, args, block)
|
219
|
-
|
228
|
+
return yield unless @@overriden_singleton_methods
|
220
229
|
key = [method]
|
221
|
-
|
222
|
-
args.each{|e| key.push(e.object_id)}
|
223
|
-
end
|
230
|
+
args.each { |e| key.push(e.object_id) }
|
224
231
|
if key && @sqreen_multi_instr && @sqreen_multi_instr[instance.object_id].member?(key)
|
225
232
|
return instance.send(original_method, *args, &block)
|
226
233
|
end
|
227
|
-
@sqreen_multi_instr ||= Hash.new {|h, k| h[k]=Set.new } # TODO this should probably be a thread local
|
234
|
+
@sqreen_multi_instr ||= Hash.new { |h, k| h[k] = Set.new } # TODO: this should probably be a thread local
|
228
235
|
@sqreen_multi_instr[instance.object_id].add(key)
|
229
236
|
r = yield
|
230
237
|
return r
|
@@ -234,103 +241,152 @@ module Sqreen
|
|
234
241
|
end
|
235
242
|
end
|
236
243
|
|
237
|
-
def self.guard_call(method, retval)
|
238
|
-
@sqreen_in_instr ||= nil
|
239
|
-
return retval if @sqreen_in_instr && @sqreen_in_instr.member?(method)
|
240
|
-
@sqreen_in_instr ||= Set.new # TODO this should probably be a thread local
|
241
|
-
@sqreen_in_instr.add(method)
|
242
|
-
r = yield
|
243
|
-
@sqreen_in_instr.delete(method)
|
244
|
-
return r
|
245
|
-
rescue Exception => e
|
246
|
-
@sqreen_in_instr.delete(method)
|
247
|
-
raise e
|
248
|
-
end
|
249
|
-
|
250
244
|
def self.define_callback_method(meth, original_meth, klass_name)
|
245
|
+
@sqreen_multi_instr ||= nil
|
251
246
|
proc do |*args, &block|
|
252
|
-
|
247
|
+
budget = nil
|
248
|
+
skip_call = Thread.current[:sqreen_in_use]
|
249
|
+
begin
|
250
|
+
if !skip_call && Sqreen.performance_budget
|
251
|
+
# Not using framework here to try to get a bit more perf by not loading cbs
|
252
|
+
budget = Sqreen::SharedStorage.get(:performance_budget)
|
253
|
+
skip_call = budget && budget <= 0.0 && !@@unovertimable_hookpoints.include?([klass_name, meth])
|
254
|
+
end
|
255
|
+
rescue StandardError => e
|
256
|
+
Sqreen.log.warn "we catched an exception looking for overtime: #{e.inspect}"
|
257
|
+
Sqreen::RemoteException.record(e)
|
258
|
+
end
|
259
|
+
if !skip_call && Process.pid != Instrumentation.instrumented_pid
|
253
260
|
Sqreen.log.debug do
|
254
261
|
"Instrumented #{Instrumentation.instrumented_pid} != PID #{Process.pid}"
|
255
262
|
end
|
256
|
-
|
263
|
+
skip_call = true
|
257
264
|
end
|
265
|
+
# If we are already overbudget let's not work at all
|
266
|
+
return send(original_meth, *args, &block) if skip_call
|
258
267
|
Instrumentation.guard_multi_call(self, meth, original_meth, args, block) do
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
when :raise, 'raise'
|
280
|
-
fail Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
|
268
|
+
precbs, postcbs, failcbs = Instrumentation.callbacks.get(klass_name, meth)
|
269
|
+
Thread.current[:sqreen_in_use] = true
|
270
|
+
begin
|
271
|
+
if budget && budget <= 0.0
|
272
|
+
precbs = precbs.reject(&:overtimeable) if precbs
|
273
|
+
end
|
274
|
+
# We need the framework to set budget remaining time else don't look for it
|
275
|
+
if budget
|
276
|
+
cb_with_framework = nil
|
277
|
+
cb_with_framework = precbs.find(&:framework) if precbs
|
278
|
+
cb_with_framework = postcbs.find(&:framework) unless cb_with_framework || !postcbs
|
279
|
+
cb_with_framework = failcbs.find(&:framework) unless cb_with_framework || !failcbs
|
280
|
+
framework = cb_with_framework ? cb_with_framework.framework : nil
|
281
|
+
else
|
282
|
+
framework = nil
|
283
|
+
end
|
284
|
+
rescue StandardError => e
|
285
|
+
Sqreen.log.warn "we catched an exception looking for framework: #{e.inspect}"
|
286
|
+
Sqreen::RemoteException.record(e)
|
287
|
+
framework = nil
|
281
288
|
end
|
282
|
-
end
|
283
289
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
290
|
+
skip = false
|
291
|
+
result = nil
|
292
|
+
|
293
|
+
if precbs && !precbs.empty?
|
294
|
+
# pre callback
|
295
|
+
returns = Instrumentation.callback_wrapper_pre(precbs, framework,
|
296
|
+
budget,
|
297
|
+
klass_name,
|
298
|
+
meth,
|
299
|
+
self,
|
300
|
+
args,
|
301
|
+
&block)
|
302
|
+
returns.each do |ret|
|
303
|
+
next unless ret.is_a? Hash
|
304
|
+
case ret[:status]
|
305
|
+
when :skip, 'skip'
|
306
|
+
skip = true
|
307
|
+
result = ret[:new_return_value] if ret.key? :new_return_value
|
308
|
+
next
|
309
|
+
when :modify_args, 'modify_args'
|
310
|
+
args = ret[:args]
|
311
|
+
when :raise, 'raise'
|
312
|
+
Thread.current[:sqreen_in_use] = false
|
313
|
+
raise Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
|
314
|
+
end
|
305
315
|
end
|
306
316
|
end
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
317
|
+
Thread.current[:sqreen_in_use] = false
|
318
|
+
return result if skip
|
319
|
+
begin
|
320
|
+
result = send(original_meth, *args, &block)
|
321
|
+
rescue StandardError => e
|
322
|
+
Thread.current[:sqreen_in_use] = true
|
323
|
+
budget = Sqreen.performance_budget && framework && framework.remaining_perf_budget
|
324
|
+
failcbs = failcbs.reject(&:overtimeable) if failcbs && budget && budget <= 0.0
|
325
|
+
raise e unless failcbs && !failcbs.empty?
|
326
|
+
returns = Instrumentation.callback_wrapper_failing(failcbs,
|
327
|
+
framework,
|
328
|
+
budget,
|
329
|
+
e,
|
330
|
+
klass_name,
|
331
|
+
meth,
|
332
|
+
self,
|
333
|
+
args,
|
334
|
+
&block)
|
335
|
+
will_retry = false
|
336
|
+
will_raise = returns.empty?
|
337
|
+
returns.each do |ret|
|
338
|
+
will_raise = true if ret.nil?
|
339
|
+
next unless ret.is_a? Hash
|
340
|
+
case ret[:status]
|
341
|
+
when :override, 'override'
|
342
|
+
result = ret[:new_return_value] if ret.key? :new_return_value
|
343
|
+
when :retry, 'retry'
|
344
|
+
will_retry = true
|
345
|
+
else # :reraise, 'reraise'
|
346
|
+
will_raise = true
|
347
|
+
end
|
348
|
+
end
|
349
|
+
raise e if will_raise
|
350
|
+
retry if will_retry
|
351
|
+
result
|
352
|
+
else
|
353
|
+
Thread.current[:sqreen_in_use] = true
|
354
|
+
budget = Sqreen.performance_budget && framework && framework.remaining_perf_budget
|
355
|
+
postcbs = postcbs.reject(&:overtimeable) if postcbs && budget && budget <= 0.0
|
356
|
+
return result unless postcbs && !postcbs.empty?
|
357
|
+
# post callback
|
358
|
+
returns = Instrumentation.callback_wrapper_post(postcbs,
|
359
|
+
framework,
|
360
|
+
budget,
|
361
|
+
klass_name,
|
362
|
+
meth,
|
363
|
+
result,
|
364
|
+
self,
|
365
|
+
args,
|
366
|
+
&block)
|
367
|
+
returns.each do |ret|
|
368
|
+
next unless ret.is_a? Hash
|
369
|
+
case ret[:status]
|
370
|
+
when :raise, 'raise'
|
371
|
+
raise Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
|
372
|
+
when :override, 'override'
|
373
|
+
result = ret[:new_return_value]
|
374
|
+
else
|
375
|
+
next
|
376
|
+
end
|
328
377
|
end
|
378
|
+
result
|
379
|
+
ensure
|
380
|
+
if @@record_request_hookpoints.include?([klass_name, meth]) && Sqreen::PerformanceNotifications.listen_for?
|
381
|
+
Sqreen::PerformanceNotifications.instrument('Callbacks/hooks_reporting/pre') do
|
382
|
+
Sqreen::PerformanceNotifications::LogPerformance.next_request
|
383
|
+
Sqreen::PerformanceNotifications::NewRelic.next_request
|
384
|
+
end
|
385
|
+
end
|
386
|
+
Thread.current[:sqreen_in_use] = false
|
329
387
|
end
|
330
|
-
result
|
331
388
|
end
|
332
389
|
end
|
333
|
-
end
|
334
390
|
end
|
335
391
|
|
336
392
|
def override_class_method(klass, meth)
|
@@ -372,12 +428,11 @@ module Sqreen
|
|
372
428
|
method_kind = nil
|
373
429
|
obj.class_eval do
|
374
430
|
# Note: As a lambda the following will crash ruby 2.2.3p173
|
375
|
-
|
376
|
-
when public_method_defined?(meth)
|
431
|
+
if public_method_defined?(meth)
|
377
432
|
method_kind = :public
|
378
|
-
|
433
|
+
elsif protected_method_defined?(meth)
|
379
434
|
method_kind = :protected
|
380
|
-
|
435
|
+
elsif private_method_defined?(meth)
|
381
436
|
method_kind = :private
|
382
437
|
end
|
383
438
|
alias_method meth, saved_meth_name
|
@@ -385,7 +440,7 @@ module Sqreen
|
|
385
440
|
end
|
386
441
|
end
|
387
442
|
|
388
|
-
def get_saved_method_name(meth, suffix=nil)
|
443
|
+
def get_saved_method_name(meth, suffix = nil)
|
389
444
|
"#{meth}_sq#{suffix}_not_modified".to_sym
|
390
445
|
end
|
391
446
|
|
@@ -401,12 +456,11 @@ module Sqreen
|
|
401
456
|
|
402
457
|
define_method(new_method, p)
|
403
458
|
|
404
|
-
|
405
|
-
when public_method_defined?(meth)
|
459
|
+
if public_method_defined?(meth)
|
406
460
|
method_kind = :public
|
407
|
-
|
461
|
+
elsif protected_method_defined?(meth)
|
408
462
|
method_kind = :protected
|
409
|
-
|
463
|
+
elsif private_method_defined?(meth)
|
410
464
|
method_kind = :private
|
411
465
|
end
|
412
466
|
alias_method meth, new_method
|
@@ -465,6 +519,7 @@ module Sqreen
|
|
465
519
|
|
466
520
|
# Override a singleton method on an instance
|
467
521
|
def override_singleton_method(instance, klass_name, meth)
|
522
|
+
@@overriden_singleton_methods = true
|
468
523
|
saved_meth_name = get_saved_method_name(meth, 'singleton')
|
469
524
|
if instance.respond_to?(saved_meth_name, true)
|
470
525
|
Sqreen.log.debug { "#{saved_meth_name} found #{instance.class}##{instance.object_id} already instrumented" }
|
@@ -533,6 +588,8 @@ module Sqreen
|
|
533
588
|
end
|
534
589
|
|
535
590
|
@@registered_callbacks.add(cb)
|
591
|
+
@@unovertimable_hookpoints << key unless cb.overtimeable
|
592
|
+
@@record_request_hookpoints << key if cb.is_a?(Sqreen::Rules::RecordRequestContext)
|
536
593
|
@@instrumented_pid = Process.pid
|
537
594
|
end
|
538
595
|
end
|
@@ -555,7 +612,7 @@ module Sqreen
|
|
555
612
|
return
|
556
613
|
end
|
557
614
|
|
558
|
-
defined_cbs = @@registered_callbacks.get(klass, method)
|
615
|
+
defined_cbs = @@registered_callbacks.get(klass, method).flatten
|
559
616
|
|
560
617
|
nb_removed = 0
|
561
618
|
defined_cbs.each do |found_cb|
|
@@ -607,7 +664,7 @@ module Sqreen
|
|
607
664
|
# @param metrics_engine [MetricsStore] Metric storage facility
|
608
665
|
def instrument!(rules, framework)
|
609
666
|
verifier = nil
|
610
|
-
if Sqreen.features['rules_signature']
|
667
|
+
if Sqreen.features['rules_signature'] &&
|
611
668
|
Sqreen.config_get(:rules_verify_signature) == true &&
|
612
669
|
!defined?(::JRUBY_VERSION)
|
613
670
|
verifier = Sqreen::SqreenSignedVerifier.new
|
@@ -630,7 +687,7 @@ module Sqreen
|
|
630
687
|
metrics_engine.create_metric('name' => CallCountable::COUNT_CALLS,
|
631
688
|
'period' => 60,
|
632
689
|
'kind' => 'Sum')
|
633
|
-
metrics_engine.create_metric('name' => WHITELISTED_METRIC,
|
690
|
+
metrics_engine.create_metric('name' => Sqreen::Rules::RecordRequestContext::WHITELISTED_METRIC,
|
634
691
|
'period' => 60,
|
635
692
|
'kind' => 'Sum')
|
636
693
|
metrics_engine.create_metric('name' => OVERTIME_METRIC,
|