sqreen-alt 1.10.0
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 +7 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/README.md +77 -0
- data/Rakefile +20 -0
- data/lib/sqreen-alt.rb +1 -0
- data/lib/sqreen.rb +68 -0
- data/lib/sqreen/attack_detected.html +2 -0
- data/lib/sqreen/binding_accessor.rb +288 -0
- data/lib/sqreen/ca.crt +72 -0
- data/lib/sqreen/call_countable.rb +67 -0
- data/lib/sqreen/callback_tree.rb +78 -0
- data/lib/sqreen/callbacks.rb +100 -0
- data/lib/sqreen/capped_queue.rb +23 -0
- data/lib/sqreen/condition_evaluator.rb +235 -0
- data/lib/sqreen/conditionable.rb +50 -0
- data/lib/sqreen/configuration.rb +168 -0
- data/lib/sqreen/context.rb +26 -0
- data/lib/sqreen/deliveries/batch.rb +84 -0
- data/lib/sqreen/deliveries/simple.rb +39 -0
- data/lib/sqreen/event.rb +16 -0
- data/lib/sqreen/events/attack.rb +61 -0
- data/lib/sqreen/events/remote_exception.rb +54 -0
- data/lib/sqreen/events/request_record.rb +62 -0
- data/lib/sqreen/exception.rb +34 -0
- data/lib/sqreen/frameworks.rb +40 -0
- data/lib/sqreen/frameworks/generic.rb +446 -0
- data/lib/sqreen/frameworks/rails.rb +148 -0
- data/lib/sqreen/frameworks/rails3.rb +36 -0
- data/lib/sqreen/frameworks/request_recorder.rb +69 -0
- data/lib/sqreen/frameworks/sinatra.rb +57 -0
- data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
- data/lib/sqreen/instrumentation.rb +542 -0
- data/lib/sqreen/log.rb +119 -0
- data/lib/sqreen/metrics.rb +6 -0
- data/lib/sqreen/metrics/average.rb +39 -0
- data/lib/sqreen/metrics/base.rb +45 -0
- data/lib/sqreen/metrics/collect.rb +22 -0
- data/lib/sqreen/metrics/sum.rb +20 -0
- data/lib/sqreen/metrics_store.rb +96 -0
- data/lib/sqreen/middleware.rb +34 -0
- data/lib/sqreen/payload_creator.rb +137 -0
- data/lib/sqreen/performance_notifications.rb +86 -0
- data/lib/sqreen/performance_notifications/log.rb +36 -0
- data/lib/sqreen/performance_notifications/metrics.rb +36 -0
- data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
- data/lib/sqreen/remote_command.rb +93 -0
- data/lib/sqreen/rule_attributes.rb +26 -0
- data/lib/sqreen/rule_callback.rb +108 -0
- data/lib/sqreen/rules.rb +126 -0
- data/lib/sqreen/rules_callbacks.rb +29 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
- data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
- data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
- data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
- data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
- data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
- data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
- data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
- data/lib/sqreen/rules_signature.rb +151 -0
- data/lib/sqreen/runner.rb +365 -0
- data/lib/sqreen/runtime_infos.rb +138 -0
- data/lib/sqreen/safe_json.rb +60 -0
- data/lib/sqreen/sdk.rb +22 -0
- data/lib/sqreen/serializer.rb +46 -0
- data/lib/sqreen/session.rb +317 -0
- data/lib/sqreen/shared_storage.rb +31 -0
- data/lib/sqreen/stats.rb +18 -0
- data/lib/sqreen/version.rb +5 -0
- metadata +148 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_callback'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Rules
|
8
|
+
# Display sqreen presence
|
9
|
+
class HeadersInsertCB < RuleCB
|
10
|
+
def post(rv, _inst, *_args, &_block)
|
11
|
+
return unless rv && rv.respond_to?(:[]) && rv[1].is_a?(Hash)
|
12
|
+
return nil unless @data
|
13
|
+
headers = @data['values'] || []
|
14
|
+
return if headers.empty?
|
15
|
+
headers.each do |name, value|
|
16
|
+
rv[1][name] = value
|
17
|
+
end
|
18
|
+
advise_action(nil)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_callback'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Rules
|
8
|
+
class InspectRuleCB < RuleCB
|
9
|
+
def pre(_inst, *args, &_block)
|
10
|
+
Sqreen.log.debug { "<< #{@klass} #{@method} #{Thread.current}" }
|
11
|
+
Sqreen.log.debug { args.map(&:inspect).join(' ') }
|
12
|
+
end
|
13
|
+
|
14
|
+
def post(rv, _inst, *_args, &_block)
|
15
|
+
Sqreen.log.debug { ">> #{rv.inspect} #{@klass} #{@method} #{Thread.current}" }
|
16
|
+
byebug if defined? byebug && @data.is_a?(Hash) && @data[:break] == 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def failing(rv, _inst, *_args, &_block)
|
20
|
+
Sqreen.log.debug { "># #{rv.inspect} #{@klass} #{@method} #{Thread.current}" }
|
21
|
+
byebug if defined? byebug && @data.is_a?(Hash) && @data[:break] == 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_callback'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Rules
|
8
|
+
# matcher behavior
|
9
|
+
module Matcher
|
10
|
+
attr_reader :min_size
|
11
|
+
def self.prepare_re_pattern(value, options, case_sensitive)
|
12
|
+
res = 0
|
13
|
+
res |= Regexp::MULTILINE if options.include?('multiline')
|
14
|
+
res |= Regexp::IGNORECASE unless case_sensitive
|
15
|
+
r = Regexp.compile(value, res)
|
16
|
+
r.match("")
|
17
|
+
r
|
18
|
+
end
|
19
|
+
|
20
|
+
ANYWHERE_OPT = 'anywhere'.freeze
|
21
|
+
def prepare(patterns)
|
22
|
+
@string = {}
|
23
|
+
@regexp_patterns = []
|
24
|
+
|
25
|
+
if patterns.nil?
|
26
|
+
msg = "no key 'values' in data (had #{@data.keys})"
|
27
|
+
raise Sqreen::Exception, msg
|
28
|
+
end
|
29
|
+
|
30
|
+
@funs = {
|
31
|
+
ANYWHERE_OPT => lambda { |value, str| str.include?(value) },
|
32
|
+
'starts_with'.freeze => lambda { |value, str| str.start_with?(value) },
|
33
|
+
'ends_with'.freeze => lambda { |value, str| str.end_with?(value) },
|
34
|
+
'equals'.freeze => lambda { |value, str| str == value },
|
35
|
+
}
|
36
|
+
|
37
|
+
sizes = []
|
38
|
+
patterns.each do |entry|
|
39
|
+
next unless entry
|
40
|
+
type = entry['type']
|
41
|
+
val = entry['value']
|
42
|
+
opts = entry['options']
|
43
|
+
opt = ANYWHERE_OPT
|
44
|
+
opt = opts.first.freeze if opts && opts.first && opts.first != ''
|
45
|
+
case_sensitive = entry['case_sensitive'] || false
|
46
|
+
case type
|
47
|
+
when 'string'
|
48
|
+
if case_sensitive
|
49
|
+
case_type = :cs
|
50
|
+
else
|
51
|
+
case_type = :ci
|
52
|
+
val.downcase!
|
53
|
+
end
|
54
|
+
|
55
|
+
unless @funs.keys.include?(opt)
|
56
|
+
Sqreen.log.debug { "Error: unknown string option '#{opt}' " }
|
57
|
+
next
|
58
|
+
end
|
59
|
+
@string[opt] = { :ci => [], :cs => [] } unless @string.key?(opt)
|
60
|
+
@string[opt][case_type] << val
|
61
|
+
sizes << entry.fetch('min_length') { val.size }
|
62
|
+
when 'regexp'
|
63
|
+
pattern = Matcher.prepare_re_pattern(val, opt, case_sensitive)
|
64
|
+
next unless pattern
|
65
|
+
@regexp_patterns << pattern
|
66
|
+
sizes << entry['min_length']
|
67
|
+
else
|
68
|
+
raise Sqreen::Exception, "No such matcher type #{type}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
@min_size = sizes.min unless sizes.any?(&:nil?)
|
73
|
+
|
74
|
+
return unless [@regexp_patterns, @string].map(&:empty?).all?
|
75
|
+
msg = "no key 'regexp' nor 'match' in data (had #{@data.keys})"
|
76
|
+
raise Sqreen::Exception, msg
|
77
|
+
end
|
78
|
+
|
79
|
+
def match(str)
|
80
|
+
return if str.nil? || str.empty? || !str.is_a?(String)
|
81
|
+
str = enforce_encoding(str) unless str.ascii_only?
|
82
|
+
istr = str.downcase unless @string.empty?
|
83
|
+
|
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
|
89
|
+
cases.each do |case_type, patterns|
|
90
|
+
input_str = if case_type == :ci
|
91
|
+
istr
|
92
|
+
else
|
93
|
+
str
|
94
|
+
end
|
95
|
+
patterns.each do |pat|
|
96
|
+
return pat if fun.call(pat, input_str)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if defined?(Encoding)
|
102
|
+
@regexp_patterns.each do |p|
|
103
|
+
next unless Encoding.compatible?(p, str)
|
104
|
+
return p if p.match(str)
|
105
|
+
end
|
106
|
+
else
|
107
|
+
@regexp_patterns.each do |p|
|
108
|
+
return p if p.match(str)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def enforce_encoding(str)
|
117
|
+
encoded8bit = str.encoding.name == 'ASCII-8BIT'
|
118
|
+
return str if !encoded8bit && str.valid_encoding?
|
119
|
+
str.chars.map do |v|
|
120
|
+
if !v.valid_encoding? || (encoded8bit && !v.ascii_only?)
|
121
|
+
''
|
122
|
+
else
|
123
|
+
v
|
124
|
+
end
|
125
|
+
end.join
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# A configurable matcher rule
|
130
|
+
class MatcherRuleCB < RuleCB
|
131
|
+
def initialize(*args)
|
132
|
+
super(*args)
|
133
|
+
prepare(@data['values'])
|
134
|
+
end
|
135
|
+
include Matcher
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_callback'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Rules
|
8
|
+
class RailsParametersCB < RuleCB
|
9
|
+
def pre(_inst, *_args, &_block)
|
10
|
+
advise_action(nil)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_callback'
|
5
|
+
require 'sqreen/instrumentation'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# Save request context for handling further down
|
10
|
+
class RecordRequestContext < RuleCB
|
11
|
+
def whitelisted?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def pre(_inst, *args, &_block)
|
16
|
+
framework.store_request(args[0])
|
17
|
+
wh = framework.whitelisted_match
|
18
|
+
if wh
|
19
|
+
unless Sqreen.features.key?('whitelisted_metric') &&
|
20
|
+
!Sqreen.features['whitelisted_metric']
|
21
|
+
record_observation(Instrumentation::WHITELISTED_METRIC, wh, 1)
|
22
|
+
end
|
23
|
+
Sqreen.log.debug { "Request was whitelisted because of #{wh}" }
|
24
|
+
end
|
25
|
+
advise_action(nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
def post(_rv, _inst, *_args, &_block)
|
29
|
+
framework.clean_request
|
30
|
+
advise_action(nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
def failing(_exception, _inst, *_args, &_block)
|
34
|
+
framework.clean_request
|
35
|
+
advise_action(nil)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
require 'sqreen/rule_callback'
|
7
|
+
require 'sqreen/rules_callbacks/regexp_rule'
|
8
|
+
|
9
|
+
# Sqreen module
|
10
|
+
module Sqreen
|
11
|
+
# Sqreen rules
|
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 false unless found
|
22
|
+
infos = {
|
23
|
+
:found => found,
|
24
|
+
:payload => value,
|
25
|
+
}
|
26
|
+
record_event(infos)
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
class ReflectedUnsafeXSSCB < XSSCB
|
31
|
+
def pre(_inst, *args, &_block)
|
32
|
+
value = args[0]
|
33
|
+
|
34
|
+
return unless value.is_a?(String)
|
35
|
+
|
36
|
+
# Sqreen::log.debug value
|
37
|
+
|
38
|
+
return unless framework.params_include?(value)
|
39
|
+
|
40
|
+
Sqreen.log.debug { format('Found unescaped user param: %s', value) }
|
41
|
+
|
42
|
+
saved_value = value.dup
|
43
|
+
return unless report_dangerous_xss?(saved_value)
|
44
|
+
|
45
|
+
# potential XSS! let's escape
|
46
|
+
if block
|
47
|
+
args[0].replace(CGI.escape_html(value))
|
48
|
+
end
|
49
|
+
|
50
|
+
advise_action(nil)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# look for reflected XSS with erb template engine
|
54
|
+
class ReflectedXSSCB < XSSCB
|
55
|
+
def pre(_inst, *args, &_block)
|
56
|
+
value = args[0]
|
57
|
+
|
58
|
+
return unless value.is_a?(String)
|
59
|
+
|
60
|
+
# If the value is not marked as html_safe, it will be escaped later
|
61
|
+
return unless value.html_safe?
|
62
|
+
|
63
|
+
# Sqreen::log.debug value
|
64
|
+
|
65
|
+
return unless framework.params_include?(value)
|
66
|
+
|
67
|
+
Sqreen.log.debug { format('Found unescaped user param: %s', value) }
|
68
|
+
|
69
|
+
saved_value = value.dup
|
70
|
+
return unless report_dangerous_xss?(saved_value)
|
71
|
+
|
72
|
+
# potential XSS! let's escape
|
73
|
+
if block
|
74
|
+
args[0].replace(CGI.escape_html(value))
|
75
|
+
end
|
76
|
+
|
77
|
+
advise_action(nil)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# look for reflected XSS with haml template engine
|
81
|
+
# hook function arguments of
|
82
|
+
# Haml::Buffer.format_script(result, preserve_script, in_tag, preserve_tag,
|
83
|
+
# escape_html, nuke_inner_whitespace,
|
84
|
+
# interpolated, ugly)
|
85
|
+
class ReflectedXSSHamlCB < XSSCB
|
86
|
+
def post(ret, _inst, *_args, &_block)
|
87
|
+
value = ret
|
88
|
+
return if value.nil?
|
89
|
+
|
90
|
+
# Sqreen::log.debug value
|
91
|
+
|
92
|
+
return unless framework.full_params_include?(value)
|
93
|
+
|
94
|
+
Sqreen.log.debug { format('Found unescaped user param: %s', value) }
|
95
|
+
|
96
|
+
return unless value.is_a?(String)
|
97
|
+
|
98
|
+
return unless report_dangerous_xss?(value)
|
99
|
+
|
100
|
+
return unless block
|
101
|
+
# potential XSS! let's escape
|
102
|
+
advise_action(:override, :new_return_value => CGI.escape_html(value))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Hook into haml4 script parser
|
107
|
+
class Haml4ParserScriptHookCB < RuleCB
|
108
|
+
def pre(_inst, *args, &_block)
|
109
|
+
return unless args.size > 1
|
110
|
+
return unless Haml::VERSION < '5'
|
111
|
+
text = args[0]
|
112
|
+
escape_html = args[1]
|
113
|
+
if escape_html == false &&
|
114
|
+
text.respond_to?(:include?) &&
|
115
|
+
!text.include?('html_escape')
|
116
|
+
args[0].replace("Sqreen.escape_haml((#{args[0]}))")
|
117
|
+
end
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Hook into haml4 tag parser
|
123
|
+
class Haml4ParserTagHookCB < RuleCB
|
124
|
+
def post(ret, _inst, *_args, &_block)
|
125
|
+
return unless Haml::VERSION < '5'
|
126
|
+
tag = ret
|
127
|
+
if tag.value[:escape_html] == false &&
|
128
|
+
tag.value[:value].respond_to?(:include?) &&
|
129
|
+
!tag.value[:value].include?('html_escape')
|
130
|
+
tag.value[:value] = "Sqreen.escape_haml((#{tag.value[:value]}))"
|
131
|
+
return { :status => :override, :new_return_value => tag }
|
132
|
+
end
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Haml4UtilInterpolationHookCB < RuleCB
|
138
|
+
def pre(_inst, *args, &_block)
|
139
|
+
# Also work in haml5
|
140
|
+
str = args[0]
|
141
|
+
escape_html = args[1]
|
142
|
+
# Original code from HAML tuned up to insert escape_haml call
|
143
|
+
res = ''
|
144
|
+
rest = Haml::Util.handle_interpolation str.dump do |scan|
|
145
|
+
escapes = (scan[2].size - 1) / 2
|
146
|
+
res << scan.matched[0...-3 - escapes]
|
147
|
+
if escapes.odd?
|
148
|
+
res << '#{'
|
149
|
+
else
|
150
|
+
content = eval('"' + Haml::Util.balance(scan, '{', '}', 1)[0][0...-1] + '"')
|
151
|
+
content = "Haml::Helpers.html_escape((#{content}))" if escape_html
|
152
|
+
res << '#{Sqreen.escape_haml((' + content + '))}' # Use eval to get rid of string escapes
|
153
|
+
end
|
154
|
+
end
|
155
|
+
{ :status => :skip, :new_return_value => res + rest }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Hook build attributes
|
160
|
+
class Haml4CompilerBuildAttributeCB < XSSCB
|
161
|
+
def pre(inst, *args, &_block)
|
162
|
+
return unless Haml::VERSION < '5'
|
163
|
+
attrs = args[-1]
|
164
|
+
new_attrs, found_xss = Haml4CompilerBuildAttributeCB.clean_hash_key(attrs) do |key|
|
165
|
+
if !key.nil? && key.is_a?(String) && framework.full_params_include?(key) && report_dangerous_xss?(key)
|
166
|
+
Sqreen.log.debug { format('Found unescaped user param: %s', key) }
|
167
|
+
[CGI.escape_html(key), true]
|
168
|
+
else
|
169
|
+
[key, false]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
return if !found_xss || !block
|
174
|
+
# potential XSS! let's escape
|
175
|
+
args[-1] = new_attrs
|
176
|
+
r = inst.send(method, *args)
|
177
|
+
{ :status => :skip, :new_return_value => r }
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.clean_hash_key(hash, limit = 10, seen = [], &block)
|
181
|
+
seen << hash.object_id
|
182
|
+
has_xss = false
|
183
|
+
new_h = {}
|
184
|
+
return if limit <= 0
|
185
|
+
hash.each do |k, v|
|
186
|
+
if seen.include?(v.object_id)
|
187
|
+
new_h[k] = nil
|
188
|
+
next
|
189
|
+
end
|
190
|
+
seen << v.object_id
|
191
|
+
new_key, found_xss = yield k
|
192
|
+
has_xss |= found_xss
|
193
|
+
if v.is_a?(Hash)
|
194
|
+
new_h[new_key], found_xss = Haml4CompilerBuildAttributeCB.clean_hash_key(v, limit - 1, seen, &block)
|
195
|
+
has_xss |= found_xss
|
196
|
+
else
|
197
|
+
new_h[new_key] = v
|
198
|
+
end
|
199
|
+
end
|
200
|
+
[new_h, has_xss]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Haml5EscapableHookCB < RuleCB
|
205
|
+
def pre(_inst, *args, &_block)
|
206
|
+
args[0] = "Sqreen.escape_haml((#{args[0]}))"
|
207
|
+
{ :status => :modify_args, :args => args }
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Hook into temple template rendering
|
212
|
+
class TempleEscapableHookCB < RuleCB
|
213
|
+
def post(ret, _inst, *_args, &_block)
|
214
|
+
ret[1] = "Sqreen.escape_temple((#{ret[1]}))"
|
215
|
+
{ :status => :override, :new_return_value => ret }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Hook into temple template rendering
|
220
|
+
class SlimSplatBuilderCB < XSSCB
|
221
|
+
def pre(inst, *args, &_block)
|
222
|
+
value = args[0]
|
223
|
+
return if value.nil?
|
224
|
+
|
225
|
+
return unless framework.full_params_include?(value)
|
226
|
+
|
227
|
+
Sqreen.log.debug { format('Found unescaped user param: %s', value) }
|
228
|
+
|
229
|
+
return unless value.is_a?(String)
|
230
|
+
|
231
|
+
return unless report_dangerous_xss?(value)
|
232
|
+
|
233
|
+
return unless block
|
234
|
+
# potential XSS! let's escape
|
235
|
+
if block
|
236
|
+
args[0] = CGI.escape_html(value)
|
237
|
+
r = inst.send(method, *args)
|
238
|
+
return { :status => :skip, :new_return_value => r }
|
239
|
+
end
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Escape HAML when instrumented to do it
|
246
|
+
def self.escape_haml(x)
|
247
|
+
x
|
248
|
+
end
|
249
|
+
|
250
|
+
# Escape Temple when instrumented to do it
|
251
|
+
def self.escape_temple(x)
|
252
|
+
x
|
253
|
+
end
|
254
|
+
end
|