sqreen 1.4.2 → 1.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b13dece251d9c305d0f1f79ea551b1b51a35c20
4
- data.tar.gz: 00acadad553d40803de98b2bb70c81c274e6aa83
3
+ metadata.gz: 36d9acaf03b582ce9120a6425278ccd50d113a9d
4
+ data.tar.gz: 18ac50baadde2c653751bcff36952738ddadf602
5
5
  SHA512:
6
- metadata.gz: e61529e9cb80bf86226272f0843f9a8e9fc1d8fafaa71f29ec36ff0e7254f16d039ea5b083a6475c58b3ea2c49f4e9b112b616a3236c1e4a271c034a09a28b15
7
- data.tar.gz: 6dd83b5fd2809940dcd9875c7321ef4efcad0b711fdeadb652197607e80003afe0d018c3bdc86643a1bc47839487a83cc6aeb5ca6f0812b4808e3033b6baa59c
6
+ metadata.gz: bad005dac888fccba0429e29c22f0bf2f471f53dd1745b5fe210af74a13742fedb3d78f4b091f17356d7f3fc7c8717a848ecaea325044ecb80ab443b23ed89ae
7
+ data.tar.gz: 7b8708e9c8d5f4c5a6d6428a12416a7904b3fa2e862f0384ccd4ff18865588d3e476a060ea448df15e86e5bd7e9aa75c8dae20cecb6aeed1686fc79f7dfaa60e
@@ -56,6 +56,34 @@ class ConditionEvaluator
56
56
  end
57
57
  end
58
58
 
59
+ # Predicate: Is one of values deeply present in keys of hash
60
+ # @params value [Array] Array of objects to find
61
+ # @params hash [Hash] Hash to search into
62
+ # @params min_value_size [Fixnum] to compare against
63
+ def self.hash_key_include?(values, hash, min_value_size, rem = 10)
64
+ return true if rem <= 0
65
+ if hash.is_a?(Array)
66
+ return hash.any? do |v|
67
+ ConditionEvaluator.hash_key_include?(values, v, min_value_size, rem - 1)
68
+ end
69
+ end
70
+
71
+ return false unless hash.is_a?(Hash)
72
+
73
+ hash.any? do |hkey, hval|
74
+ case hkey
75
+ when NilClass
76
+ false
77
+ else
78
+ if hkey.respond_to?(:empty?) && hkey.empty?
79
+ false
80
+ else
81
+ values.include?(hkey.to_s) || ConditionEvaluator.hash_key_include?(values, hval, min_value_size, rem - 1)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
59
87
  # Test is a str contains what. Rencode if necessary
60
88
  def self.str_include?(str, what)
61
89
  str1 = if str.encoding != Encoding::UTF_8
@@ -128,12 +156,14 @@ class ConditionEvaluator
128
156
  GT_OPERATOR = '%gt'.freeze
129
157
  LT_OPERATOR = '%lt'.freeze
130
158
  HASH_INC_OPERATOR = '%hash_val_include'.freeze
159
+ HASH_KEY_OPERATOR = '%hash_key_include'.freeze
131
160
  INC_OPERATOR = '%include'.freeze
132
161
  OR_OPERATOR = '%or'.freeze
133
162
  AND_OPERATOR = '%and'.freeze
134
163
 
135
164
  OPERATORS_ARITY = {
136
165
  HASH_INC_OPERATOR => 3,
166
+ HASH_KEY_OPERATOR => 3,
137
167
  EQ_OPERATOR => 2,
138
168
  NEQ_OPERATOR => 2,
139
169
  INC_OPERATOR => 2,
@@ -191,6 +221,8 @@ class ConditionEvaluator
191
221
  end
192
222
  when HASH_INC_OPERATOR
193
223
  ConditionEvaluator.hash_val_include?(res[0], res[1], res[2])
224
+ when HASH_KEY_OPERATOR
225
+ ConditionEvaluator.hash_key_include?(res[0], res[1], res[2])
194
226
  else
195
227
  # FIXME: this should be check in compile
196
228
  raise(Sqreen::Exception, "unknown op #{op})")
@@ -167,7 +167,7 @@ module Sqreen
167
167
  @wait << block
168
168
  end
169
169
 
170
- # Does the parameters include this value
170
+ # Does the parameters value include this value
171
171
  def params_include?(value)
172
172
  params = request_params
173
173
  return false if params.nil?
@@ -177,6 +177,16 @@ module Sqreen
177
177
  false
178
178
  end
179
179
 
180
+ # Does the parameters key/value include this value
181
+ def full_params_include?(value)
182
+ params = request_params
183
+ return false if params.nil?
184
+ each_key_value_for_hash(params) do |param|
185
+ return true if param == value
186
+ end
187
+ false
188
+ end
189
+
180
190
  # Fetch and store the current request object
181
191
  # Nota: cleanup should be performed at end of request (see clean_request)
182
192
  def store_request(object)
@@ -313,6 +323,18 @@ module Sqreen
313
323
  end
314
324
  end
315
325
 
326
+ def each_key_value_for_hash(params, &block)
327
+ case params
328
+ when Hash then params.each do |k, v|
329
+ yield k
330
+ each_key_value_for_hash(v, &block)
331
+ end
332
+ when Array then params.each { |v| each_key_value_for_hash(v, &block) }
333
+ else
334
+ yield params
335
+ end
336
+ end
337
+
316
338
  def ensure_rack_loaded
317
339
  @cannot_load_rack ||= false
318
340
  return false if @cannot_load_rack
@@ -1,6 +1,7 @@
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
 
4
+ require 'sqreen/rule_attributes'
4
5
  require 'sqreen/rule_callback'
5
6
 
6
7
  module Sqreen
@@ -14,5 +15,25 @@ module Sqreen
14
15
  advise_action(nil)
15
16
  end
16
17
  end
18
+
19
+ # Count 1 for each things located by the binding accessor
20
+ class BindingAccessorCounter < RuleCB
21
+ def initialize(klass, method, rule_hash)
22
+ super(klass, method, rule_hash)
23
+ @accessors = @data['values'].map do |expr|
24
+ BindingAccessor.new(expr, true)
25
+ end
26
+ @metric_category = rule_hash[Attrs::METRICS].first['name']
27
+ end
28
+
29
+ def post(rv, inst, *args, &_block)
30
+ return unless rv.is_a?(Array) && !rv.empty?
31
+ key = @accessors.map do |accessor|
32
+ accessor.resolve(binding, framework, inst, args, @data, rv)
33
+ end
34
+ record_observation(@metric_category, JSON.dump(key), 1)
35
+ advise_action(nil)
36
+ end
37
+ end
17
38
  end
18
39
  end
@@ -10,8 +10,24 @@ require 'sqreen/rules_callbacks/regexp_rule'
10
10
  module Sqreen
11
11
  # Sqreen rules
12
12
  module Rules
13
+ # XSSCB abstract common behaviour of tpls
14
+ class XSSCB < RegexpRuleCB
15
+ # The remaining code is only to find out if user entry was an attack,
16
+ # and record it. Since we don't rely on it to respond to user, it would
17
+ # be better to do it in background.
18
+ def report_dangerous_xss(value)
19
+ found = match_regexp(value)
20
+
21
+ return unless found
22
+ infos = {
23
+ :found => found,
24
+ :payload => value,
25
+ }
26
+ record_event(infos)
27
+ end
28
+ end
13
29
  # look for reflected XSS with erb template engine
14
- class ReflectedXSSCB < RegexpRuleCB
30
+ class ReflectedXSSCB < XSSCB
15
31
  def pre(_inst, *args, &_block)
16
32
  value = args[0]
17
33
 
@@ -38,33 +54,20 @@ module Sqreen
38
54
  advise_action(nil)
39
55
  end
40
56
 
41
- # The remaining code is only to find out if user entry was an attack,
42
- # and record it. Since we don't rely on it to respond to user, it would
43
- # be better to do it in background.
44
- def report_dangerous_xss(value)
45
- found = match_regexp(value)
46
-
47
- return unless found
48
- infos = {
49
- :found => found,
50
- :payload => value,
51
- }
52
- record_event(infos)
53
- end
54
57
  end
55
58
  # look for reflected XSS with haml template engine
56
59
  # hook function arguments of
57
60
  # Haml::Buffer.format_script(result, preserve_script, in_tag, preserve_tag,
58
61
  # escape_html, nuke_inner_whitespace,
59
62
  # interpolated, ugly)
60
- class ReflectedXSSHamlCB < ReflectedXSSCB
63
+ class ReflectedXSSHamlCB < XSSCB
61
64
  def post(ret, _inst, *_args, &_block)
62
65
  value = ret
63
66
  return if value.nil?
64
67
 
65
68
  # Sqreen::log.debug value
66
69
 
67
- return unless framework.params_include?(value)
70
+ return unless framework.full_params_include?(value)
68
71
 
69
72
  Sqreen.log.debug { format('Found unescaped user param: %s', value) }
70
73
 
@@ -103,10 +106,117 @@ module Sqreen
103
106
  nil
104
107
  end
105
108
  end
109
+
110
+ class Haml4UtilInterpolationHookCB < RuleCB
111
+ def pre(_inst, *args, &_block)
112
+ str = args[0]
113
+ escape_html = args[1]
114
+ # Original code from HAML tuned up to insert escape_haml call
115
+ res = ''
116
+ rest = Haml::Util.handle_interpolation str.dump do |scan|
117
+ escapes = (scan[2].size - 1) / 2
118
+ res << scan.matched[0...-3 - escapes]
119
+ if escapes.odd?
120
+ res << '#{'
121
+ else
122
+ content = eval('"' + Haml::Util.balance(scan, '{', '}', 1)[0][0...-1] + '"')
123
+ content = "Haml::Helpers.html_escape((#{content}))" if escape_html
124
+ res << '#{Sqreen.escape_haml(' + content + ')}' # Use eval to get rid of string escapes
125
+ end
126
+ end
127
+ { :status => :skip, :new_return_value => res + rest }
128
+ end
129
+ end
130
+
131
+ # Hook build attributes
132
+ class Haml4CompilerBuildAttributeCB < XSSCB
133
+ def pre(inst, *args, &_block)
134
+ attrs = args[-1]
135
+ new_attrs, found_xss = Haml4CompilerBuildAttributeCB.clean_hash_key(attrs) do |key|
136
+ if !key.nil? && key.is_a?(String) && framework.full_params_include?(key)
137
+ Sqreen.log.debug { format('Found unescaped user param: %s', key) }
138
+ report_dangerous_xss(key)
139
+ [CGI.escape_html(key), true]
140
+ else
141
+ [key, false]
142
+ end
143
+ end
144
+
145
+ return if !found_xss || !block
146
+ # potential XSS! let's escape
147
+ if !framework || !find_whitelisted_path(framework.request_path.to_s)
148
+ args[-1] = new_attrs
149
+ r = inst.send(method, *args)
150
+ return { :status => :skip, :new_return_value => r }
151
+ end
152
+ end
153
+
154
+ def self.clean_hash_key(hash, limit = 10, seen = [], &block)
155
+ seen << hash.object_id
156
+ has_xss = false
157
+ new_h = {}
158
+ return if limit <= 0
159
+ hash.each do |k, v|
160
+ if seen.include?(v.object_id)
161
+ new_h[k] = nil
162
+ next
163
+ end
164
+ seen << v.object_id
165
+ new_key, found_xss = yield k
166
+ has_xss |= found_xss
167
+ if v.is_a?(Hash)
168
+ new_h[new_key], found_xss = Haml4CompilerBuildAttributeCB.clean_hash_key(v, limit - 1, seen, &block)
169
+ has_xss |= found_xss
170
+ else
171
+ new_h[new_key] = v
172
+ end
173
+ end
174
+ [new_h, has_xss]
175
+ end
176
+ end
177
+
178
+ # Hook into temple template rendering
179
+ class TempleEscapableHookCB < RuleCB
180
+ def post(ret, _inst, *_args, &_block)
181
+ ret[1] = "Sqreen.escape_temple(#{ret[1]})"
182
+ { :status => :override, :new_return_value => ret }
183
+ end
184
+ end
185
+
186
+ # Hook into temple template rendering
187
+ class SlimSplatBuilderCB < XSSCB
188
+ def pre(inst, *args, &_block)
189
+ value = args[0]
190
+ return if value.nil?
191
+
192
+ return unless framework.full_params_include?(value)
193
+
194
+ Sqreen.log.debug { format('Found unescaped user param: %s', value) }
195
+
196
+ return unless value.is_a?(String)
197
+
198
+ report_dangerous_xss(value)
199
+
200
+ return unless block
201
+ # potential XSS! let's escape
202
+ if block &&
203
+ (!framework || !find_whitelisted_path(framework.request_path.to_s))
204
+ args[0] = CGI.escape_html(value)
205
+ r = inst.send(method, *args)
206
+ return { :status => :skip, :new_return_value => r }
207
+ end
208
+ nil
209
+ end
210
+ end
106
211
  end
107
212
 
108
213
  # Escape HAML when instrumented to do it
109
214
  def self.escape_haml(x)
110
215
  x
111
216
  end
217
+
218
+ # Escape Temple when instrumented to do it
219
+ def self.escape_temple(x)
220
+ x
221
+ end
112
222
  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.4.2'.freeze
4
+ VERSION = '1.4.3'.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.4.2
4
+ version: 1.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-28 00:00:00.000000000 Z
11
+ date: 2017-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: execjs