sqreen-alt 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|