sqreen 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sqreen/attack_detected.html +2 -0
- data/lib/sqreen/binding_accessor.rb +247 -157
- data/lib/sqreen/condition_evaluator.rb +191 -189
- data/lib/sqreen/frameworks/generic.rb +6 -0
- data/lib/sqreen/frameworks/rails.rb +1 -0
- data/lib/sqreen/frameworks/sinatra.rb +18 -0
- data/lib/sqreen/middleware.rb +10 -0
- data/lib/sqreen/rules_callbacks.rb +3 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +75 -0
- data/lib/sqreen/rules_callbacks/custom_error.rb +57 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +4 -7
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +26 -20
- data/lib/sqreen/version.rb +1 -1
- metadata +6 -3
data/lib/sqreen/middleware.rb
CHANGED
@@ -21,5 +21,8 @@ require 'sqreen/rules_callbacks/reflected_xss'
|
|
21
21
|
require 'sqreen/rules_callbacks/execjs'
|
22
22
|
|
23
23
|
require 'sqreen/rules_callbacks/binding_accessor_metrics'
|
24
|
+
require 'sqreen/rules_callbacks/binding_accessor_matcher'
|
24
25
|
require 'sqreen/rules_callbacks/count_http_codes'
|
25
26
|
require 'sqreen/rules_callbacks/crawler_user_agent_matches_metrics'
|
27
|
+
|
28
|
+
require 'sqreen/rules_callbacks/custom_error'
|
@@ -0,0 +1,75 @@
|
|
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/binding_accessor'
|
6
|
+
require 'sqreen/rules_callbacks/matcher_rule'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Callback that match on a list or matcher+binding accessor
|
11
|
+
class BindingAccessorMatcherCB < RuleCB
|
12
|
+
attr_reader :rules
|
13
|
+
|
14
|
+
# matcher on one elem
|
15
|
+
class MatcherElem
|
16
|
+
def initialize(expr)
|
17
|
+
prepare([expr])
|
18
|
+
end
|
19
|
+
include Matcher
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(klass, method, rule_hash)
|
23
|
+
super(klass, method, rule_hash)
|
24
|
+
@rules = []
|
25
|
+
if @data.empty? || @data['values'].nil? || @data['values'].empty?
|
26
|
+
msg = "no rules in data (had #{@data.keys})"
|
27
|
+
raise Sqreen::Exception, msg
|
28
|
+
end
|
29
|
+
prepare_rules(@data['values'])
|
30
|
+
end
|
31
|
+
|
32
|
+
def prepare_rules(rules)
|
33
|
+
accessors = Hash.new do |hash, key|
|
34
|
+
hash[key] = BindingAccessor.new(key, true)
|
35
|
+
end
|
36
|
+
@rules = rules.map do |r|
|
37
|
+
if r['binding_accessor'].empty?
|
38
|
+
raise Sqreen::Exception, "no accessors #{r['id']}"
|
39
|
+
end
|
40
|
+
[
|
41
|
+
r['id'],
|
42
|
+
r['binding_accessor'].map { |expression| accessors[expression] },
|
43
|
+
MatcherElem.new(r['matcher']),
|
44
|
+
r['matcher']['value'],
|
45
|
+
]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def pre(inst, *args, &_block)
|
50
|
+
resol_cache = Hash.new do |hash, accessor|
|
51
|
+
hash[accessor] = accessor.resolve(binding, framework, inst, args)
|
52
|
+
end
|
53
|
+
@rules.each do |id, accessors, matcher, matcher_ref|
|
54
|
+
accessors.each do |accessor|
|
55
|
+
val = resol_cache[accessor]
|
56
|
+
val = [val] if val.is_a?(String)
|
57
|
+
next unless val.respond_to?(:each)
|
58
|
+
val.each do |v|
|
59
|
+
next if matcher.match(v).nil?
|
60
|
+
infos = {
|
61
|
+
'id' => id,
|
62
|
+
'binding_accessor' => accessor.expression,
|
63
|
+
'matcher' => matcher_ref,
|
64
|
+
'found' => v,
|
65
|
+
}
|
66
|
+
record_event(infos)
|
67
|
+
return advise_action(:raise, :infos => infos)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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/exception'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# Display sqreen presence
|
10
|
+
class CustomErrorCB < RuleCB
|
11
|
+
attr_reader :status_code, :redirect_url
|
12
|
+
def initialize(klass, method, rule_hash)
|
13
|
+
@redirect_url = nil
|
14
|
+
@status_code = nil
|
15
|
+
super(klass, method, rule_hash)
|
16
|
+
if @data.nil? || @data['values'].empty?
|
17
|
+
raise Sqreen::Exception, 'No data'
|
18
|
+
end
|
19
|
+
configure_custom_error(@data['values'][0])
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_custom_error(custom_error)
|
23
|
+
case custom_error['type']
|
24
|
+
when 'custom_error_page' then
|
25
|
+
@status_code = custom_error['status_code'].to_i
|
26
|
+
when 'redirection' then
|
27
|
+
@redirect_url = custom_error['redirection_url']
|
28
|
+
@status_code = custom_error.fetch('status_code', 303).to_i
|
29
|
+
else
|
30
|
+
raise Sqreen::Exception, "No custom error #{custom_error['type']}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def failing(except, _inst, *_args, &_block)
|
35
|
+
return advise_action(nil) unless except.is_a?(Sqreen::AttackBlocked)
|
36
|
+
if @redirect_url
|
37
|
+
advise_action(:override, :new_return_value => respond_redirect)
|
38
|
+
else
|
39
|
+
advise_action(:override, :new_return_value => respond_page)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def respond_redirect
|
44
|
+
[@status_code, { 'Location' => @redirect_url }, ['']]
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_page
|
48
|
+
page = open(File.join(File.dirname(__FILE__), '../attack_detected.html'))
|
49
|
+
headers = {
|
50
|
+
'Content-Type' => 'text/html',
|
51
|
+
'Content-Length' => page.size.to_s,
|
52
|
+
}
|
53
|
+
[@status_code, headers, page]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -93,7 +93,7 @@ module Sqreen
|
|
93
93
|
accessor.resolve(binding, framework, inst, args, @data, rv)
|
94
94
|
end
|
95
95
|
Sqreen.log.debug { [name, arguments].inspect }
|
96
|
-
ret = @compiled.call(
|
96
|
+
ret = @compiled.call(name, *arguments)
|
97
97
|
unless record_and_continue?(ret)
|
98
98
|
return nil if ret.nil?
|
99
99
|
return advise_action(ret[:status], ret)
|
@@ -118,13 +118,12 @@ module Sqreen
|
|
118
118
|
|
119
119
|
def build_runnable(callbacks)
|
120
120
|
@argument_requirements = {}
|
121
|
-
@source = '
|
121
|
+
@source = ''
|
122
122
|
@js_pre = !callbacks['pre'].nil?
|
123
123
|
@js_post = !callbacks['post'].nil?
|
124
124
|
@js_failing = !callbacks['failing'].nil?
|
125
125
|
callbacks.each do |name, args_or_func|
|
126
|
-
@source << name
|
127
|
-
@source << ': '
|
126
|
+
@source << "var #{name} = "
|
128
127
|
if args_or_func.is_a?(Array)
|
129
128
|
@source << args_or_func.pop
|
130
129
|
@argument_requirements[name] = build_accessor(args_or_func)
|
@@ -132,10 +131,8 @@ module Sqreen
|
|
132
131
|
@source << args_or_func
|
133
132
|
@argument_requirements[name] = []
|
134
133
|
end
|
135
|
-
@source << "
|
134
|
+
@source << ";\n"
|
136
135
|
end
|
137
|
-
@source << "\n"
|
138
|
-
@source << '}'
|
139
136
|
end
|
140
137
|
end
|
141
138
|
end
|
@@ -5,33 +5,29 @@ require 'sqreen/rule_callback'
|
|
5
5
|
|
6
6
|
module Sqreen
|
7
7
|
module Rules
|
8
|
-
#
|
9
|
-
|
10
|
-
def initialize(*args)
|
11
|
-
super(*args)
|
12
|
-
prepare
|
13
|
-
end
|
14
|
-
|
8
|
+
# matcher behavior
|
9
|
+
module Matcher
|
15
10
|
def self.prepare_re_pattern(value, options, case_sensitive)
|
16
11
|
res = 0
|
17
12
|
res |= Regexp::MULTILINE if options.include?('multiline')
|
18
13
|
res |= Regexp::IGNORECASE unless case_sensitive
|
19
|
-
Regexp.compile(value, res)
|
14
|
+
r = Regexp.compile(value, res)
|
15
|
+
r.match("")
|
16
|
+
r
|
20
17
|
end
|
21
18
|
|
22
19
|
ANYWHERE_OPT = 'anywhere'.freeze
|
23
|
-
def prepare
|
20
|
+
def prepare(patterns)
|
24
21
|
@string = {}
|
25
|
-
@
|
22
|
+
@regexp_patterns = []
|
26
23
|
|
27
|
-
patterns = @data['values']
|
28
24
|
if patterns.nil?
|
29
25
|
msg = "no key 'values' in data (had #{@data.keys})"
|
30
26
|
raise Sqreen::Exception, msg
|
31
27
|
end
|
32
28
|
|
33
29
|
@funs = {
|
34
|
-
ANYWHERE_OPT
|
30
|
+
ANYWHERE_OPT => lambda { |value, str| str.include?(value) },
|
35
31
|
'starts_with'.freeze => lambda { |value, str| str.start_with?(value) },
|
36
32
|
'ends_with'.freeze => lambda { |value, str| str.end_with?(value) },
|
37
33
|
'equals'.freeze => lambda { |value, str| str == value },
|
@@ -61,17 +57,18 @@ module Sqreen
|
|
61
57
|
@string[opt] = { :ci => [], :cs => [] } unless @string.key?(opt)
|
62
58
|
@string[opt][case_type] << val
|
63
59
|
|
64
|
-
when '
|
65
|
-
pattern =
|
60
|
+
when 'regexp'
|
61
|
+
pattern = Matcher.prepare_re_pattern(val, opt, case_sensitive)
|
66
62
|
next unless pattern
|
67
|
-
@
|
63
|
+
@regexp_patterns << pattern
|
64
|
+
else
|
65
|
+
raise Sqreen::Exception, "No such matcher type #{type}"
|
68
66
|
end
|
69
67
|
end
|
70
68
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
69
|
+
return unless [@regexp_patterns, @string].map(&:empty?).all?
|
70
|
+
msg = "no key 'regexp' nor 'match' in data (had #{@data.keys})"
|
71
|
+
raise Sqreen::Exception, msg
|
75
72
|
end
|
76
73
|
|
77
74
|
def match(str)
|
@@ -95,11 +92,20 @@ module Sqreen
|
|
95
92
|
end
|
96
93
|
end
|
97
94
|
|
98
|
-
@
|
95
|
+
@regexp_patterns.each do |p|
|
99
96
|
return p if p.match(str)
|
100
97
|
end
|
101
98
|
nil
|
102
99
|
end
|
103
100
|
end
|
101
|
+
|
102
|
+
# A configurable matcher rule
|
103
|
+
class MatcherRuleCB < RuleCB
|
104
|
+
def initialize(*args)
|
105
|
+
super(*args)
|
106
|
+
prepare(@data['values'])
|
107
|
+
end
|
108
|
+
include Matcher
|
109
|
+
end
|
104
110
|
end
|
105
111
|
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
|
+
version: 1.6.0
|
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-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: execjs
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- README.md
|
50
50
|
- Rakefile
|
51
51
|
- lib/sqreen.rb
|
52
|
+
- lib/sqreen/attack_detected.html
|
52
53
|
- lib/sqreen/binding_accessor.rb
|
53
54
|
- lib/sqreen/ca.crt
|
54
55
|
- lib/sqreen/call_countable.rb
|
@@ -90,10 +91,12 @@ files:
|
|
90
91
|
- lib/sqreen/rule_callback.rb
|
91
92
|
- lib/sqreen/rules.rb
|
92
93
|
- lib/sqreen/rules_callbacks.rb
|
94
|
+
- lib/sqreen/rules_callbacks/binding_accessor_matcher.rb
|
93
95
|
- lib/sqreen/rules_callbacks/binding_accessor_metrics.rb
|
94
96
|
- lib/sqreen/rules_callbacks/count_http_codes.rb
|
95
97
|
- lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb
|
96
98
|
- lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb
|
99
|
+
- lib/sqreen/rules_callbacks/custom_error.rb
|
97
100
|
- lib/sqreen/rules_callbacks/execjs.rb
|
98
101
|
- lib/sqreen/rules_callbacks/headers_insert.rb
|
99
102
|
- lib/sqreen/rules_callbacks/inspect_rule.rb
|
@@ -132,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
135
|
version: '0'
|
133
136
|
requirements: []
|
134
137
|
rubyforge_project:
|
135
|
-
rubygems_version: 2.6.
|
138
|
+
rubygems_version: 2.6.11
|
136
139
|
signing_key:
|
137
140
|
specification_version: 4
|
138
141
|
summary: Sqreen Ruby agent
|