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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sqreen.rb +0 -1
  3. data/lib/sqreen/callback_tree.rb +38 -20
  4. data/lib/sqreen/callbacks.rb +6 -4
  5. data/lib/sqreen/conditionable.rb +3 -3
  6. data/lib/sqreen/frameworks/generic.rb +38 -18
  7. data/lib/sqreen/frameworks/request_recorder.rb +0 -2
  8. data/lib/sqreen/instrumentation.rb +217 -160
  9. data/lib/sqreen/performance_notifications.rb +10 -36
  10. data/lib/sqreen/performance_notifications/log.rb +1 -2
  11. data/lib/sqreen/performance_notifications/log_performance.rb +1 -2
  12. data/lib/sqreen/performance_notifications/metrics.rb +1 -2
  13. data/lib/sqreen/performance_notifications/newrelic.rb +1 -2
  14. data/lib/sqreen/remote_command.rb +7 -7
  15. data/lib/sqreen/rule_callback.rb +4 -0
  16. data/lib/sqreen/rules.rb +2 -2
  17. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +7 -1
  18. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +3 -3
  19. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +1 -1
  20. data/lib/sqreen/rules_callbacks/count_http_codes.rb +7 -6
  21. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +1 -1
  22. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +1 -1
  23. data/lib/sqreen/rules_callbacks/custom_error.rb +2 -5
  24. data/lib/sqreen/rules_callbacks/execjs.rb +76 -37
  25. data/lib/sqreen/rules_callbacks/headers_insert.rb +1 -1
  26. data/lib/sqreen/rules_callbacks/inspect_rule.rb +3 -3
  27. data/lib/sqreen/rules_callbacks/matcher_rule.rb +18 -17
  28. data/lib/sqreen/rules_callbacks/rails_parameters.rb +1 -1
  29. data/lib/sqreen/rules_callbacks/record_request_context.rb +9 -8
  30. data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -34
  31. data/lib/sqreen/rules_callbacks/regexp_rule.rb +13 -4
  32. data/lib/sqreen/rules_callbacks/shell_env.rb +2 -2
  33. data/lib/sqreen/rules_callbacks/url_matches.rb +1 -1
  34. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +1 -1
  35. data/lib/sqreen/session.rb +1 -1
  36. data/lib/sqreen/shared_storage.rb +16 -12
  37. data/lib/sqreen/{stats.rb → shared_storage23.rb} +3 -11
  38. data/lib/sqreen/version.rb +1 -1
  39. metadata +4 -4
@@ -7,7 +7,7 @@ module Sqreen
7
7
  module Rules
8
8
  # Display sqreen presence
9
9
  class HeadersInsertCB < RuleCB
10
- def post(rv, _inst, *_args, &_block)
10
+ def post(rv, _inst, _args, _budget = nil, &_block)
11
11
  return unless rv && rv.respond_to?(:[]) && rv[1].is_a?(Hash)
12
12
  return nil unless @data
13
13
  headers = @data['values'] || []
@@ -6,17 +6,17 @@ require 'sqreen/rule_callback'
6
6
  module Sqreen
7
7
  module Rules
8
8
  class InspectRuleCB < RuleCB
9
- def pre(_inst, *args, &_block)
9
+ def pre(_inst, args, _budget = nil, &_block)
10
10
  Sqreen.log.debug { "<< #{@klass} #{@method} #{Thread.current}" }
11
11
  Sqreen.log.debug { args.map(&:inspect).join(' ') }
12
12
  end
13
13
 
14
- def post(rv, _inst, *_args, &_block)
14
+ def post(rv, _inst, _args, _budget = nil, &_block)
15
15
  Sqreen.log.debug { ">> #{rv.inspect} #{@klass} #{@method} #{Thread.current}" }
16
16
  byebug if defined? byebug && @data.is_a?(Hash) && @data[:break] == 1
17
17
  end
18
18
 
19
- def failing(rv, _inst, *_args, &_block)
19
+ def failing(rv, _inst, _args, _budget = nil, &_block)
20
20
  Sqreen.log.debug { "># #{rv.inspect} #{@klass} #{@method} #{Thread.current}" }
21
21
  byebug if defined? byebug && @data.is_a?(Hash) && @data[:break] == 1
22
22
  end
@@ -13,11 +13,12 @@ module Sqreen
13
13
  res |= Regexp::MULTILINE if options.include?('multiline')
14
14
  res |= Regexp::IGNORECASE unless case_sensitive
15
15
  r = Regexp.compile(value, res)
16
- r.match("")
16
+ r.match('')
17
17
  r
18
18
  end
19
19
 
20
20
  ANYWHERE_OPT = 'anywhere'.freeze
21
+ MATCH_PREDICATE = Regexp.new('').respond_to?(:match?)
21
22
  def prepare(patterns)
22
23
  @string = {}
23
24
  @regexp_patterns = []
@@ -52,12 +53,13 @@ module Sqreen
52
53
  val.downcase!
53
54
  end
54
55
 
55
- unless @funs.keys.include?(opt)
56
+ way = @funs[opt]
57
+ unless way
56
58
  Sqreen.log.debug { "Error: unknown string option '#{opt}' " }
57
59
  next
58
60
  end
59
- @string[opt] = { :ci => [], :cs => [] } unless @string.key?(opt)
60
- @string[opt][case_type] << val
61
+ @string[way] = { :ci => [], :cs => [] } unless @string.key?(way)
62
+ @string[way][case_type] << val
61
63
  sizes << entry.fetch('min_length') { val.size }
62
64
  when 'regexp'
63
65
  pattern = Matcher.prepare_re_pattern(val, opt, case_sensitive)
@@ -81,17 +83,9 @@ module Sqreen
81
83
  str = enforce_encoding(str) unless str.ascii_only?
82
84
  istr = str.downcase unless @string.empty?
83
85
 
84
- @string.each do |type, cases|
85
- fun = @funs[type]
86
- if fun.nil?
87
- Sqreen.log.debug { "no matching function found for type #{type}" }
88
- end
86
+ @string.each do |fun, cases|
89
87
  cases.each do |case_type, patterns|
90
- input_str = if case_type == :ci
91
- istr
92
- else
93
- str
94
- end
88
+ input_str = case_type == :ci ? istr : str
95
89
  patterns.each do |pat|
96
90
  return pat if fun.call(pat, input_str)
97
91
  end
@@ -99,9 +93,16 @@ module Sqreen
99
93
  end
100
94
 
101
95
  if defined?(Encoding)
102
- @regexp_patterns.each do |p|
103
- next unless Encoding.compatible?(p, str)
104
- return p if p.match(str)
96
+ if MATCH_PREDICATE
97
+ @regexp_patterns.each do |p|
98
+ next unless Encoding.compatible?(p, str)
99
+ return p if p.match?(str)
100
+ end
101
+ else
102
+ @regexp_patterns.each do |p|
103
+ next unless Encoding.compatible?(p, str)
104
+ return p if p.match(str)
105
+ end
105
106
  end
106
107
  else
107
108
  @regexp_patterns.each do |p|
@@ -6,7 +6,7 @@ require 'sqreen/rule_callback'
6
6
  module Sqreen
7
7
  module Rules
8
8
  class RailsParametersCB < RuleCB
9
- def pre(_inst, *_args, &_block)
9
+ def pre(_inst, _args, _budget = nil, &_block)
10
10
  advise_action(nil)
11
11
  end
12
12
  end
@@ -2,39 +2,40 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'sqreen/rule_callback'
5
- require 'sqreen/instrumentation'
6
5
 
7
6
  module Sqreen
8
7
  module Rules
9
8
  # Save request context for handling further down
10
9
  class RecordRequestContext < RuleCB
11
- def whitelisted?
12
- false
10
+ WHITELISTED_METRIC = 'whitelisted'.freeze
11
+ def initialize(*args)
12
+ super(*args)
13
+ @overtimeable = false
13
14
  end
14
15
 
15
- def overtime!
16
+ def whitelisted?
16
17
  false
17
18
  end
18
19
 
19
- def pre(_inst, *args, &_block)
20
+ def pre(_inst, args, _budget = nil, &_block)
20
21
  framework.store_request(args[0])
21
22
  wh = framework.whitelisted_match
22
23
  if wh
23
24
  unless Sqreen.features.key?('whitelisted_metric') &&
24
25
  !Sqreen.features['whitelisted_metric']
25
- record_observation(Instrumentation::WHITELISTED_METRIC, wh, 1)
26
+ record_observation(WHITELISTED_METRIC, wh, 1)
26
27
  end
27
28
  Sqreen.log.debug { "Request was whitelisted because of #{wh}" }
28
29
  end
29
30
  advise_action(nil)
30
31
  end
31
32
 
32
- def post(_rv, _inst, *_args, &_block)
33
+ def post(_rv, _inst, _args, _budget = nil, &_block)
33
34
  framework.clean_request
34
35
  advise_action(nil)
35
36
  end
36
37
 
37
- def failing(_exception, _inst, *_args, &_block)
38
+ def failing(_exception, _inst, _args, _budget = nil, &_block)
38
39
  framework.clean_request
39
40
  advise_action(nil)
40
41
  end
@@ -37,7 +37,7 @@ module Sqreen
37
37
  end
38
38
  end
39
39
  class ReflectedUnsafeXSSCB < XSSCB
40
- def pre(_inst, *args, &_block)
40
+ def pre(_inst, args, _budget = nil, &_block)
41
41
  value = args[0]
42
42
 
43
43
  return unless value.is_a?(String)
@@ -61,7 +61,7 @@ module Sqreen
61
61
  end
62
62
  # look for reflected XSS with erb template engine
63
63
  class ReflectedXSSCB < XSSCB
64
- def pre(_inst, *args, &_block)
64
+ def pre(_inst, args, _budget = nil, &_block)
65
65
  value = args[0]
66
66
 
67
67
  return unless value.is_a?(String)
@@ -90,7 +90,7 @@ module Sqreen
90
90
  # escape_html, nuke_inner_whitespace,
91
91
  # interpolated, ugly)
92
92
  class ReflectedXSSHamlCB < XSSCB
93
- def post(ret, _inst, *_args, &_block)
93
+ def post(ret, _inst, _args, _budget = nil, &_block)
94
94
  value = ret
95
95
  return unless value.is_a?(String)
96
96
 
@@ -109,7 +109,12 @@ module Sqreen
109
109
 
110
110
  # Hook into haml4 script parser
111
111
  class Haml4ParserScriptHookCB < RuleCB
112
- def pre(_inst, *args, &_block)
112
+ def initialize(*args)
113
+ super(*args)
114
+ @overtimeable = false
115
+ end
116
+
117
+ def pre(_inst, args, _budget = nil, &_block)
113
118
  return unless args.size > 1
114
119
  return unless Haml::VERSION < '5'
115
120
  text = args[0]
@@ -121,15 +126,16 @@ module Sqreen
121
126
  end
122
127
  nil
123
128
  end
124
-
125
- def overtime!
126
- false
127
- end
128
129
  end
129
130
 
130
131
  # Hook into haml4 tag parser
131
132
  class Haml4ParserTagHookCB < RuleCB
132
- def post(ret, _inst, *_args, &_block)
133
+ def initialize(*args)
134
+ super(*args)
135
+ @overtimeable = false
136
+ end
137
+
138
+ def post(ret, _inst, _args, _budget = nil, &_block)
133
139
  return unless Haml::VERSION < '5'
134
140
  tag = ret
135
141
  if tag.value[:escape_html] == false &&
@@ -140,14 +146,15 @@ module Sqreen
140
146
  end
141
147
  nil
142
148
  end
143
-
144
- def overtime!
145
- false
146
- end
147
149
  end
148
150
 
149
151
  class Haml4UtilInterpolationHookCB < RuleCB
150
- def pre(_inst, *args, &_block)
152
+ def initialize(*args)
153
+ super(*args)
154
+ @overtimeable = false
155
+ end
156
+
157
+ def pre(_inst, args, _budget = nil, &_block)
151
158
  # Also work in haml5
152
159
  str = args[0]
153
160
  escape_html = args[1]
@@ -167,15 +174,16 @@ module Sqreen
167
174
  end
168
175
  { :status => :skip, :new_return_value => res + rest }
169
176
  end
170
-
171
- def overtime!
172
- false
173
- end
174
177
  end
175
178
 
176
179
  # Hook build attributes
177
180
  class Haml4CompilerBuildAttributeCB < XSSCB
178
- def pre(inst, *args, &_block)
181
+ def initialize(*args)
182
+ super(*args)
183
+ @overtimeable = false
184
+ end
185
+
186
+ def pre(inst, args, _budget = nil, &_block)
179
187
  return unless Haml::VERSION < '5'
180
188
  attrs = args[-1]
181
189
  params = xss_params
@@ -217,38 +225,36 @@ module Sqreen
217
225
  end
218
226
  [new_h, has_xss]
219
227
  end
220
-
221
- def overtime!
222
- false
223
- end
224
228
  end
225
229
 
226
230
  class Haml5EscapableHookCB < RuleCB
227
- def pre(_inst, *args, &_block)
228
- args[0] = "Sqreen.escape_haml((#{args[0]}))"
229
- { :status => :modify_args, :args => args }
231
+ def initialize(*args)
232
+ super(*args)
233
+ @overtimeable = false
230
234
  end
231
235
 
232
- def overtime!
233
- false
236
+ def pre(_inst, args, _budget = nil, &_block)
237
+ args[0] = "Sqreen.escape_haml((#{args[0]}))"
238
+ { :status => :modify_args, :args => args }
234
239
  end
235
240
  end
236
241
 
237
242
  # Hook into temple template rendering
238
243
  class TempleEscapableHookCB < RuleCB
239
- def post(ret, _inst, *_args, &_block)
240
- ret[1] = "Sqreen.escape_temple((#{ret[1]}))"
241
- { :status => :override, :new_return_value => ret }
244
+ def initialize(*args)
245
+ super(*args)
246
+ @overtimeable = false
242
247
  end
243
248
 
244
- def overtime!
245
- false
249
+ def post(ret, _inst, _args, _budget = nil, &_block)
250
+ ret[1] = "Sqreen.escape_temple((#{ret[1]}))"
251
+ { :status => :override, :new_return_value => ret }
246
252
  end
247
253
  end
248
254
 
249
255
  # Hook into temple template rendering
250
256
  class SlimSplatBuilderCB < XSSCB
251
- def pre(inst, *args, &_block)
257
+ def pre(inst, args, _budget = nil, &_block)
252
258
  value = args[0]
253
259
  return if value.nil?
254
260
 
@@ -25,11 +25,20 @@ module Sqreen
25
25
  end
26
26
  end
27
27
 
28
- def match_regexp(str)
29
- @patterns.each do |pattern|
30
- return pattern if pattern.match(str)
28
+ if Regexp.new('').respond_to?(:match?)
29
+ def match_regexp(str)
30
+ @patterns.each do |pattern|
31
+ return pattern if pattern.match?(str)
32
+ end
33
+ nil
34
+ end
35
+ else
36
+ def match_regexp(str)
37
+ @patterns.each do |pattern|
38
+ return pattern if pattern.match(str)
39
+ end
40
+ nil
31
41
  end
32
- nil
33
42
  end
34
43
  end
35
44
  end
@@ -7,7 +7,7 @@ module Sqreen
7
7
  module Rules
8
8
  # Callback that detect nifty env in system calls
9
9
  class ShellEnvCB < RegexpRuleCB
10
- def pre(_inst, *args, &_block)
10
+ def pre(_inst, args, _budget = nil, &_block)
11
11
  return if args.size == 0
12
12
  env = args.first
13
13
  return unless env.is_a?(Hash)
@@ -23,7 +23,7 @@ module Sqreen
23
23
  :variable_value => value,
24
24
  :found => found,
25
25
  }
26
- Sqreen.log.warn "presence of a shell env tampering: #{infos.inspect}"
26
+ Sqreen.log.warn { "presence of a shell env tampering: #{infos.inspect}" }
27
27
  record_event(infos)
28
28
  advise_action(:raise)
29
29
  end
@@ -11,7 +11,7 @@ module Sqreen
11
11
  # - the path is a typical bot scanning request
12
12
  # Then we deny the ressource and record the attack.
13
13
  class URLMatchesCB < RegexpRuleCB
14
- def post(rv, _inst, *args, &_block)
14
+ def post(rv, _inst, args, _budget = nil, &_block)
15
15
  return unless rv.is_a?(Array) && rv.size > 0 && rv[0] == 404
16
16
  env = args[0]
17
17
  path = env['SCRIPT_NAME'].to_s + env['PATH_INFO'].to_s
@@ -7,7 +7,7 @@ module Sqreen
7
7
  module Rules
8
8
  # Look for badly behaved clients
9
9
  class UserAgentMatchesCB < RegexpRuleCB
10
- def pre(_inst, *_args, &_block)
10
+ def pre(_inst, _args, _budget = nil, &_block)
11
11
  ua = framework.client_user_agent
12
12
  return unless ua
13
13
  found = match_regexp(ua)
@@ -134,7 +134,7 @@ module Sqreen
134
134
  def resiliently(retry_request_seconds, max_retry, current_retry = 0)
135
135
  return yield
136
136
  rescue => e
137
- Sqreen.log.debug(e.inspect)
137
+ Sqreen.log.debug { e.inspect }
138
138
 
139
139
  current_retry += 1
140
140
 
@@ -2,30 +2,34 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  module Sqreen
5
+ # dedicated local storage
5
6
  module SharedStorage
6
-
7
- def self::get(key, default = nil)
8
- h = Thread.current["SQREEN_SHARED_STORAGE_#{self.object_id}"]
9
- return h.fetch(key, default) if h
10
- default
7
+ unless RUBY_VERSION >= '2.3.0'
8
+ def self.get(key)
9
+ h = Thread.current[:sqreen_shared_storage]
10
+ h[key] if h
11
+ end
11
12
  end
12
13
 
13
- def self::set(key, obj)
14
- Thread.current["SQREEN_SHARED_STORAGE_#{self.object_id}"] ||= {}
15
- Thread.current["SQREEN_SHARED_STORAGE_#{self.object_id}"][key] = obj
14
+ def self.set(key, obj)
15
+ Thread.current[:sqreen_shared_storage] ||= {}
16
+ Thread.current[:sqreen_shared_storage][key] = obj
16
17
  end
17
18
 
18
19
  def self.clear
19
- return unless Thread.current["SQREEN_SHARED_STORAGE_#{self.object_id}"].is_a?(Hash)
20
- Thread.current["SQREEN_SHARED_STORAGE_#{self.object_id}"].clear
20
+ return unless Thread.current[:sqreen_shared_storage].is_a?(Hash)
21
+ Thread.current[:sqreen_shared_storage].clear
21
22
  end
22
23
 
23
24
  def self.inc(value)
24
- set(value, get(value, 0) + 1)
25
+ set(value, (get(value) || 0) + 1)
25
26
  end
26
27
 
27
28
  def self.dec(value)
28
- set(value, get(value, 0) - 1)
29
+ set(value, (get(value) || 0) - 1)
29
30
  end
30
31
  end
31
32
  end
33
+
34
+ # Change SharedStorage.get if ruby is actually newer
35
+ require 'sqreen/shared_storage23' if RUBY_VERSION >= '2.3.0'
@@ -2,17 +2,9 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  module Sqreen
5
- @@stats = nil
6
-
7
- def self::stats
8
- @@stats ||= Stats.new
9
- end
10
-
11
- class Stats
12
- attr_accessor :callbacks_calls
13
-
14
- def initialize
15
- @callbacks_calls = 0
5
+ module SharedStorage
6
+ def self.get(key)
7
+ Thread.current[:sqreen_shared_storage]&.[](key)
16
8
  end
17
9
  end
18
10
  end