sqreen-alt 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +20 -0
  5. data/lib/sqreen-alt.rb +1 -0
  6. data/lib/sqreen.rb +68 -0
  7. data/lib/sqreen/attack_detected.html +2 -0
  8. data/lib/sqreen/binding_accessor.rb +288 -0
  9. data/lib/sqreen/ca.crt +72 -0
  10. data/lib/sqreen/call_countable.rb +67 -0
  11. data/lib/sqreen/callback_tree.rb +78 -0
  12. data/lib/sqreen/callbacks.rb +100 -0
  13. data/lib/sqreen/capped_queue.rb +23 -0
  14. data/lib/sqreen/condition_evaluator.rb +235 -0
  15. data/lib/sqreen/conditionable.rb +50 -0
  16. data/lib/sqreen/configuration.rb +168 -0
  17. data/lib/sqreen/context.rb +26 -0
  18. data/lib/sqreen/deliveries/batch.rb +84 -0
  19. data/lib/sqreen/deliveries/simple.rb +39 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +61 -0
  22. data/lib/sqreen/events/remote_exception.rb +54 -0
  23. data/lib/sqreen/events/request_record.rb +62 -0
  24. data/lib/sqreen/exception.rb +34 -0
  25. data/lib/sqreen/frameworks.rb +40 -0
  26. data/lib/sqreen/frameworks/generic.rb +446 -0
  27. data/lib/sqreen/frameworks/rails.rb +148 -0
  28. data/lib/sqreen/frameworks/rails3.rb +36 -0
  29. data/lib/sqreen/frameworks/request_recorder.rb +69 -0
  30. data/lib/sqreen/frameworks/sinatra.rb +57 -0
  31. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  32. data/lib/sqreen/instrumentation.rb +542 -0
  33. data/lib/sqreen/log.rb +119 -0
  34. data/lib/sqreen/metrics.rb +6 -0
  35. data/lib/sqreen/metrics/average.rb +39 -0
  36. data/lib/sqreen/metrics/base.rb +45 -0
  37. data/lib/sqreen/metrics/collect.rb +22 -0
  38. data/lib/sqreen/metrics/sum.rb +20 -0
  39. data/lib/sqreen/metrics_store.rb +96 -0
  40. data/lib/sqreen/middleware.rb +34 -0
  41. data/lib/sqreen/payload_creator.rb +137 -0
  42. data/lib/sqreen/performance_notifications.rb +86 -0
  43. data/lib/sqreen/performance_notifications/log.rb +36 -0
  44. data/lib/sqreen/performance_notifications/metrics.rb +36 -0
  45. data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
  46. data/lib/sqreen/remote_command.rb +93 -0
  47. data/lib/sqreen/rule_attributes.rb +26 -0
  48. data/lib/sqreen/rule_callback.rb +108 -0
  49. data/lib/sqreen/rules.rb +126 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
  52. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  53. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
  54. data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
  55. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  56. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
  57. data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
  58. data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
  59. data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
  60. data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
  61. data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
  62. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  63. data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
  64. data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
  65. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  66. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  67. data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
  68. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
  69. data/lib/sqreen/rules_signature.rb +151 -0
  70. data/lib/sqreen/runner.rb +365 -0
  71. data/lib/sqreen/runtime_infos.rb +138 -0
  72. data/lib/sqreen/safe_json.rb +60 -0
  73. data/lib/sqreen/sdk.rb +22 -0
  74. data/lib/sqreen/serializer.rb +46 -0
  75. data/lib/sqreen/session.rb +317 -0
  76. data/lib/sqreen/shared_storage.rb +31 -0
  77. data/lib/sqreen/stats.rb +18 -0
  78. data/lib/sqreen/version.rb +5 -0
  79. 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