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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36d9acaf03b582ce9120a6425278ccd50d113a9d
|
4
|
+
data.tar.gz: 18ac50baadde2c653751bcff36952738ddadf602
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 <
|
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 <
|
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.
|
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
|
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.
|
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-
|
11
|
+
date: 2017-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: execjs
|